From 0ae484529d0626e06bc6bf50634adf937e767399 Mon Sep 17 00:00:00 2001 From: KubaPro010 Date: Tue, 24 Feb 2026 09:52:39 +0100 Subject: [PATCH] we're up to date with the terrible --- package-lock.json | 576 +++++++++++++++++++-------------------- package.json | 21 +- server/datahandler.js | 5 + server/endpoints.js | 72 +++-- server/index.js | 38 ++- server/plugin_api.js | 137 ++++++++++ server/server_config.js | 3 + server/tuner_profiles.js | 84 ++++++ web/_bwSwitch.ejs | 62 +---- web/css/breadcrumbs.css | 4 +- web/index.ejs | 27 +- web/js/dropdown.js | 8 + web/js/main.js | 10 +- web/js/setup.js | 11 +- web/js/ver.js | 4 +- web/setup.ejs | 48 ++-- web/wizard.ejs | 22 +- 17 files changed, 673 insertions(+), 459 deletions(-) create mode 100644 server/plugin_api.js create mode 100644 server/tuner_profiles.js diff --git a/package-lock.json b/package-lock.json index 01a5050..009bf83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,24 +1,25 @@ { "name": "fm-dx-webserver", - "version": "1.3.12", + "version": "1.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "fm-dx-webserver", - "version": "1.3.12", + "version": "1.4.0", "license": "ISC", "dependencies": { - "@mapbox/node-pre-gyp": "2.0.0", - "body-parser": "2.2.0", - "ejs": "3.1.10", - "express": "5.1.0", - "express-session": "1.18.2", - "ffmpeg-static": "5.2.0", + "@mapbox/node-pre-gyp": "2.0.3", + "body-parser": "2.2.2", + "ejs": "4.0.1", + "express": "5.2.1", + "express-session": "1.19.0", + "ffmpeg-static": "5.3.0", "http": "0.0.1-security", + "koffi": "2.7.2", "net": "1.0.2", - "serialport": "12.0.0", - "ws": "8.18.1" + "serialport": "13.0.0", + "ws": "8.19.0" } }, "node_modules/@derhuerst/http-basic": { @@ -48,9 +49,9 @@ } }, "node_modules/@mapbox/node-pre-gyp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.0.tgz", - "integrity": "sha512-llMXd39jtP0HpQLVI37Bf1m2ADlEb35GYSh1SDSLsBhR+5iCxiNGlT31yqbNtVHygHAtMy6dWFERpU2JgufhPg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.3.tgz", + "integrity": "sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==", "license": "BSD-3-Clause", "dependencies": { "consola": "^3.2.3", @@ -147,28 +148,30 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/@serialport/bindings-cpp": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-12.0.1.tgz", - "integrity": "sha512-r2XOwY2dDvbW7dKqSPIk2gzsr6M6Qpe9+/Ngs94fNaNlcTRCV02PfaoDmRgcubpNVVcLATlxSxPTIDw12dbKOg==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-13.0.0.tgz", + "integrity": "sha512-r25o4Bk/vaO1LyUfY/ulR6hCg/aWiN6Wo2ljVlb4Pj5bqWGcSRC4Vse4a9AcapuAu/FeBzHCbKMvRQeCuKjzIQ==", "hasInstallScript": true, + "license": "MIT", "dependencies": { "@serialport/bindings-interface": "1.2.2", - "@serialport/parser-readline": "11.0.0", - "debug": "4.3.4", - "node-addon-api": "7.0.0", - "node-gyp-build": "4.6.0" + "@serialport/parser-readline": "12.0.0", + "debug": "4.4.0", + "node-addon-api": "8.3.0", + "node-gyp-build": "4.8.4" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "funding": { "url": "https://opencollective.com/serialport/donate" } }, "node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-delimiter": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-11.0.0.tgz", - "integrity": "sha512-aZLJhlRTjSmEwllLG7S4J8s8ctRAS0cbvCpO87smLvl3e4BgzbVgF6Z6zaJd3Aji2uSiYgfedCdNc4L6W+1E2g==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-12.0.0.tgz", + "integrity": "sha512-gu26tVt5lQoybhorLTPsH2j2LnX3AOP2x/34+DUSTNaUTzu2fBXw+isVjQJpUBFWu6aeQRZw5bJol5X9Gxjblw==", + "license": "MIT", "engines": { "node": ">=12.0.0" }, @@ -177,11 +180,12 @@ } }, "node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-readline": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-11.0.0.tgz", - "integrity": "sha512-rRAivhRkT3YO28WjmmG4FQX6L+KMb5/ikhyylRfzWPw0nSXy97+u07peS9CbHqaNvJkMhH1locp2H36aGMOEIA==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-12.0.0.tgz", + "integrity": "sha512-O7cywCWC8PiOMvo/gglEBfAkLjp/SENEML46BXDykfKP5mTPM46XMaX1L0waWU6DXJpBgjaL7+yX6VriVPbN4w==", + "license": "MIT", "dependencies": { - "@serialport/parser-delimiter": "11.0.0" + "@serialport/parser-delimiter": "12.0.0" }, "engines": { "node": ">=12.0.0" @@ -191,11 +195,12 @@ } }, "node_modules/@serialport/bindings-cpp/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -207,19 +212,10 @@ } }, "node_modules/@serialport/bindings-cpp/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==" - }, - "node_modules/@serialport/bindings-cpp/node_modules/node-gyp-build": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", - "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/@serialport/bindings-interface": { "version": "1.2.2", @@ -230,136 +226,148 @@ } }, "node_modules/@serialport/parser-byte-length": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-12.0.0.tgz", - "integrity": "sha512-0ei0txFAj+s6FTiCJFBJ1T2hpKkX8Md0Pu6dqMrYoirjPskDLJRgZGLqoy3/lnU1bkvHpnJO+9oJ3PB9v8rNlg==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-13.0.0.tgz", + "integrity": "sha512-32yvqeTAqJzAEtX5zCrN1Mej56GJ5h/cVFsCDPbF9S1ZSC9FWjOqNAgtByseHfFTSTs/4ZBQZZcZBpolt8sUng==", + "license": "MIT", "engines": { - "node": ">=12.0.0" + "node": ">=20.0.0" }, "funding": { "url": "https://opencollective.com/serialport/donate" } }, "node_modules/@serialport/parser-cctalk": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-12.0.0.tgz", - "integrity": "sha512-0PfLzO9t2X5ufKuBO34DQKLXrCCqS9xz2D0pfuaLNeTkyGUBv426zxoMf3rsMRodDOZNbFblu3Ae84MOQXjnZw==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-13.0.0.tgz", + "integrity": "sha512-RErAe57g9gvnlieVYGIn1xymb1bzNXb2QtUQd14FpmbQQYlcrmuRnJwKa1BgTCujoCkhtaTtgHlbBWOxm8U2uA==", + "license": "MIT", "engines": { - "node": ">=12.0.0" + "node": ">=20.0.0" }, "funding": { "url": "https://opencollective.com/serialport/donate" } }, "node_modules/@serialport/parser-delimiter": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-12.0.0.tgz", - "integrity": "sha512-gu26tVt5lQoybhorLTPsH2j2LnX3AOP2x/34+DUSTNaUTzu2fBXw+isVjQJpUBFWu6aeQRZw5bJol5X9Gxjblw==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-13.0.0.tgz", + "integrity": "sha512-Qqyb0FX1avs3XabQqNaZSivyVbl/yl0jywImp7ePvfZKLwx7jBZjvL+Hawt9wIG6tfq6zbFM24vzCCK7REMUig==", + "license": "MIT", "engines": { - "node": ">=12.0.0" + "node": ">=20.0.0" }, "funding": { "url": "https://opencollective.com/serialport/donate" } }, "node_modules/@serialport/parser-inter-byte-timeout": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-12.0.0.tgz", - "integrity": "sha512-GnCh8K0NAESfhCuXAt+FfBRz1Cf9CzIgXfp7SdMgXwrtuUnCC/yuRTUFWRvuzhYKoAo1TL0hhUo77SFHUH1T/w==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-13.0.0.tgz", + "integrity": "sha512-a0w0WecTW7bD2YHWrpTz1uyiWA2fDNym0kjmPeNSwZ2XCP+JbirZt31l43m2ey6qXItTYVuQBthm75sPVeHnGA==", + "license": "MIT", "engines": { - "node": ">=12.0.0" + "node": ">=20.0.0" }, "funding": { "url": "https://opencollective.com/serialport/donate" } }, "node_modules/@serialport/parser-packet-length": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-12.0.0.tgz", - "integrity": "sha512-p1hiCRqvGHHLCN/8ZiPUY/G0zrxd7gtZs251n+cfNTn+87rwcdUeu9Dps3Aadx30/sOGGFL6brIRGK4l/t7MuQ==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-13.0.0.tgz", + "integrity": "sha512-60ZDDIqYRi0Xs2SPZUo4Jr5LLIjtb+rvzPKMJCohrO6tAqSDponcNpcB1O4W21mKTxYjqInSz+eMrtk0LLfZIg==", + "license": "MIT", "engines": { "node": ">=8.6.0" } }, "node_modules/@serialport/parser-readline": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-12.0.0.tgz", - "integrity": "sha512-O7cywCWC8PiOMvo/gglEBfAkLjp/SENEML46BXDykfKP5mTPM46XMaX1L0waWU6DXJpBgjaL7+yX6VriVPbN4w==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-13.0.0.tgz", + "integrity": "sha512-dov3zYoyf0dt1Sudd1q42VVYQ4WlliF0MYvAMA3MOyiU1IeG4hl0J6buBA2w4gl3DOCC05tGgLDN/3yIL81gsA==", + "license": "MIT", "dependencies": { - "@serialport/parser-delimiter": "12.0.0" + "@serialport/parser-delimiter": "13.0.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=20.0.0" }, "funding": { "url": "https://opencollective.com/serialport/donate" } }, "node_modules/@serialport/parser-ready": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-12.0.0.tgz", - "integrity": "sha512-ygDwj3O4SDpZlbrRUraoXIoIqb8sM7aMKryGjYTIF0JRnKeB1ys8+wIp0RFMdFbO62YriUDextHB5Um5cKFSWg==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-13.0.0.tgz", + "integrity": "sha512-JNUQA+y2Rfs4bU+cGYNqOPnNMAcayhhW+XJZihSLQXOHcZsFnOa2F9YtMg9VXRWIcnHldHYtisp62Etjlw24bw==", + "license": "MIT", "engines": { - "node": ">=12.0.0" + "node": ">=20.0.0" }, "funding": { "url": "https://opencollective.com/serialport/donate" } }, "node_modules/@serialport/parser-regex": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-12.0.0.tgz", - "integrity": "sha512-dCAVh4P/pZrLcPv9NJ2mvPRBg64L5jXuiRxIlyxxdZGH4WubwXVXY/kBTihQmiAMPxbT3yshSX8f2+feqWsxqA==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-13.0.0.tgz", + "integrity": "sha512-m7HpIf56G5XcuDdA3DB34Z0pJiwxNRakThEHjSa4mG05OnWYv0IG8l2oUyYfuGMowQWaVnQ+8r+brlPxGVH+eA==", + "license": "MIT", "engines": { - "node": ">=12.0.0" + "node": ">=20.0.0" }, "funding": { "url": "https://opencollective.com/serialport/donate" } }, "node_modules/@serialport/parser-slip-encoder": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-12.0.0.tgz", - "integrity": "sha512-0APxDGR9YvJXTRfY+uRGhzOhTpU5akSH183RUcwzN7QXh8/1jwFsFLCu0grmAUfi+fItCkR+Xr1TcNJLR13VNA==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-13.0.0.tgz", + "integrity": "sha512-fUHZEExm6izJ7rg0A1yjXwu4sOzeBkPAjDZPfb+XQoqgtKAk+s+HfICiYn7N2QU9gyaeCO8VKgWwi+b/DowYOg==", + "license": "MIT", "engines": { - "node": ">=12.0.0" + "node": ">=20.0.0" }, "funding": { "url": "https://opencollective.com/serialport/donate" } }, "node_modules/@serialport/parser-spacepacket": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-spacepacket/-/parser-spacepacket-12.0.0.tgz", - "integrity": "sha512-dozONxhPC/78pntuxpz/NOtVps8qIc/UZzdc/LuPvVsqCoJXiRxOg6ZtCP/W58iibJDKPZPAWPGYeZt9DJxI+Q==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-spacepacket/-/parser-spacepacket-13.0.0.tgz", + "integrity": "sha512-DoXJ3mFYmyD8X/8931agJvrBPxqTaYDsPoly9/cwQSeh/q4EjQND9ySXBxpWz5WcpyCU4jOuusqCSAPsbB30Eg==", + "license": "MIT", "engines": { - "node": ">=12.0.0" + "node": ">=20.0.0" }, "funding": { "url": "https://opencollective.com/serialport/donate" } }, "node_modules/@serialport/stream": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-12.0.0.tgz", - "integrity": "sha512-9On64rhzuqKdOQyiYLYv2lQOh3TZU/D3+IWCR5gk0alPel2nwpp4YwDEGiUBfrQZEdQ6xww0PWkzqth4wqwX3Q==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-13.0.0.tgz", + "integrity": "sha512-F7xLJKsjGo2WuEWMSEO1SimRcOA+WtWICsY13r0ahx8s2SecPQH06338g28OT7cW7uRXI7oEQAk62qh5gHJW3g==", + "license": "MIT", "dependencies": { "@serialport/bindings-interface": "1.2.2", - "debug": "4.3.4" + "debug": "4.4.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=20.0.0" }, "funding": { "url": "https://opencollective.com/serialport/donate" } }, "node_modules/@serialport/stream/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -371,9 +379,10 @@ } }, "node_modules/@serialport/stream/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==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/@types/node": { "version": "10.17.60", @@ -434,54 +443,49 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "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==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "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==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", - "debug": "^4.4.0", + "debug": "^4.4.3", "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", + "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "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/body-parser/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -502,12 +506,15 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/buffer-from": { @@ -558,21 +565,6 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "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/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", @@ -582,27 +574,6 @@ "node": ">=18" } }, - "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==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "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==" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, "node_modules/concat-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", @@ -708,18 +679,18 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-4.0.1.tgz", + "integrity": "sha512-krvQtxc0btwSm/nvnt1UpnaFDFVJpJ0fdckmALpCgShsr/iGYHTnJiUliZTgmzq/UxTX33TtOQVKaNigMQp/6Q==", "license": "Apache-2.0", "dependencies": { - "jake": "^10.8.5" + "jake": "^10.9.1" }, "bin": { "ejs": "bin/cli.js" }, "engines": { - "node": ">=0.10.0" + "node": ">=0.12.18" } }, "node_modules/encodeurl": { @@ -785,18 +756,19 @@ } }, "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.2.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", @@ -827,22 +799,26 @@ } }, "node_modules/express-session": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz", - "integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.19.0.tgz", + "integrity": "sha512-0csaMkGq+vaiZTmSMMGkfdCOabYv192VbytFypcvI0MANrp+4i/7yEkJ0sbAEhycQjntaKGzYfjfXQyVb7BHMA==", "license": "MIT", "dependencies": { - "cookie": "0.7.2", - "cookie-signature": "1.0.7", - "debug": "2.6.9", + "cookie": "~0.7.2", + "cookie-signature": "~1.0.7", + "debug": "~2.6.9", "depd": "~2.0.0", "on-headers": "~1.1.0", "parseurl": "~1.3.3", - "safe-buffer": "5.2.1", + "safe-buffer": "~5.2.1", "uid-safe": "~2.1.5" }, "engines": { "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express-session/node_modules/cookie": { @@ -883,10 +859,11 @@ "license": "MIT" }, "node_modules/ffmpeg-static": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ffmpeg-static/-/ffmpeg-static-5.2.0.tgz", - "integrity": "sha512-WrM7kLW+do9HLr+H6tk7LzQ7kPqbAgLjdzNE32+u3Ff11gXt9Kkkd2nusGFrlWMIe+XaA97t+I8JS7sZIrvRgA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ffmpeg-static/-/ffmpeg-static-5.3.0.tgz", + "integrity": "sha512-H+K6sW6TiIX6VGend0KQwthe+kaceeH/luE8dIZyOP35ik7ahYojDuqlTV1bOrtEwl01sy2HFNGQfi5IDJvotg==", "hasInstallScript": true, + "license": "GPL-3.0-or-later", "dependencies": { "@derhuerst/http-basic": "^8.2.0", "env-paths": "^2.2.0", @@ -898,30 +875,15 @@ } }, "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.5.tgz", + "integrity": "sha512-ct/ckWBV/9Dg3MlvCXsLcSUyoWwv9mCKqlhLNB2DAuXR/NZolSXlQqP5dyy6guWlPXBhodZyZ5lGPQcbQDxrEQ==", + "license": "Apache-2.0", "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dependencies": { - "brace-expansion": "^2.0.1" + "minimatch": "^10.2.1" }, "engines": { - "node": ">=10" + "node": "20 || >=22" } }, "node_modules/finalhandler": { @@ -1039,14 +1001,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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==", - "engines": { - "node": ">=8" - } - }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -1077,18 +1031,23 @@ "integrity": "sha512-RnDvP10Ty9FxqOtPZuxtebw1j4L/WiqNMDtuc1YMH1XQm5TgDRaR1G9u8upL6KD1bXHSp9eSXo/ED+8Q7FAr+g==" }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "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/http-response-object": { @@ -1133,15 +1092,19 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/inherits": { @@ -1164,14 +1127,14 @@ "license": "MIT" }, "node_modules/jake": { - "version": "10.8.7", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", - "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "license": "Apache-2.0", "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", + "async": "^3.2.6", "filelist": "^1.0.4", - "minimatch": "^3.1.2" + "picocolors": "^1.1.1" }, "bin": { "jake": "bin/cli.js" @@ -1180,6 +1143,13 @@ "node": ">=10" } }, + "node_modules/koffi": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.7.2.tgz", + "integrity": "sha512-AWcsEKETQuELxK0Wq/aXDkDiNFFY41TxZQSrKm2Nd6HO/KTHeohPOOIlh2OfQnBXJbRjx5etpWt8cbqMUZo2sg==", + "hasInstallScript": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -1243,14 +1213,18 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", + "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minipass": { @@ -1309,9 +1283,13 @@ "integrity": "sha512-kbhcj2SVVR4caaVnGLJKmlk2+f+oLkjqdKeQlmUtz6nGzOpbcobwVIeSURNgraV/v3tlmGIX82OcPCl0K6RbHQ==" }, "node_modules/node-addon-api": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz", - "integrity": "sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==" + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.0.tgz", + "integrity": "sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } }, "node_modules/node-fetch": { "version": "2.7.0", @@ -1332,6 +1310,17 @@ } } }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/nopt": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", @@ -1410,6 +1399,12 @@ "node": ">=16" } }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -1431,9 +1426,9 @@ } }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -1463,18 +1458,18 @@ } }, "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" } }, "node_modules/readable-stream": { @@ -1614,38 +1609,40 @@ "license": "MIT" }, "node_modules/serialport": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/serialport/-/serialport-12.0.0.tgz", - "integrity": "sha512-AmH3D9hHPFmnF/oq/rvigfiAouAKyK/TjnrkwZRYSFZxNggJxwvbAbfYrLeuvq7ktUdhuHdVdSjj852Z55R+uA==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/serialport/-/serialport-13.0.0.tgz", + "integrity": "sha512-PHpnTd8isMGPfFTZNCzOZp9m4mAJSNWle9Jxu6BPTcWq7YXl5qN7tp8Sgn0h+WIGcD6JFz5QDgixC2s4VW7vzg==", + "license": "MIT", "dependencies": { "@serialport/binding-mock": "10.2.2", - "@serialport/bindings-cpp": "12.0.1", - "@serialport/parser-byte-length": "12.0.0", - "@serialport/parser-cctalk": "12.0.0", - "@serialport/parser-delimiter": "12.0.0", - "@serialport/parser-inter-byte-timeout": "12.0.0", - "@serialport/parser-packet-length": "12.0.0", - "@serialport/parser-readline": "12.0.0", - "@serialport/parser-ready": "12.0.0", - "@serialport/parser-regex": "12.0.0", - "@serialport/parser-slip-encoder": "12.0.0", - "@serialport/parser-spacepacket": "12.0.0", - "@serialport/stream": "12.0.0", - "debug": "4.3.4" + "@serialport/bindings-cpp": "13.0.0", + "@serialport/parser-byte-length": "13.0.0", + "@serialport/parser-cctalk": "13.0.0", + "@serialport/parser-delimiter": "13.0.0", + "@serialport/parser-inter-byte-timeout": "13.0.0", + "@serialport/parser-packet-length": "13.0.0", + "@serialport/parser-readline": "13.0.0", + "@serialport/parser-ready": "13.0.0", + "@serialport/parser-regex": "13.0.0", + "@serialport/parser-slip-encoder": "13.0.0", + "@serialport/parser-spacepacket": "13.0.0", + "@serialport/stream": "13.0.0", + "debug": "4.4.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=20.0.0" }, "funding": { "url": "https://opencollective.com/serialport/donate" } }, "node_modules/serialport/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -1657,9 +1654,10 @@ } }, "node_modules/serialport/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==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/serve-static": { "version": "2.2.0", @@ -1754,9 +1752,10 @@ } }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -1769,17 +1768,6 @@ "safe-buffer": "~5.2.0" } }, - "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==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/tar": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", @@ -1892,9 +1880,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/package.json b/package.json index bf120fc..0f89f91 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fm-dx-webserver", - "version": "1.3.12", + "version": "1.4.0", "description": "FM DX Webserver", "main": "index.js", "scripts": { @@ -12,15 +12,16 @@ "author": "", "license": "ISC", "dependencies": { - "@mapbox/node-pre-gyp": "2.0.0", - "body-parser": "2.2.0", - "ejs": "3.1.10", - "express": "5.1.0", - "express-session": "1.18.2", - "ffmpeg-static": "5.2.0", + "@mapbox/node-pre-gyp": "2.0.3", + "body-parser": "2.2.2", + "ejs": "4.0.1", + "express": "5.2.1", + "express-session": "1.19.0", + "ffmpeg-static": "5.3.0", "http": "0.0.1-security", + "koffi": "2.7.2", "net": "1.0.2", - "serialport": "12.0.0", - "ws": "8.18.1" + "serialport": "13.0.0", + "ws": "8.19.0" } -} +} \ No newline at end of file diff --git a/server/datahandler.js b/server/datahandler.js index 58da9a7..aac93bd 100644 --- a/server/datahandler.js +++ b/server/datahandler.js @@ -28,6 +28,7 @@ var dataToSend = { rt_flag: '', ims: 0, eq: 0, + agc: 0, ant: 0, txInfo: { tx: '', @@ -128,6 +129,10 @@ function handleData(wss, receivedData, rdsWss) { initialData.ant = receivedLine.substring(1); rdsReset(); break; + case receivedLine.startsWith('A'): // AGC + dataToSend.agc = receivedLine.substring(1); + initialData.agc = receivedLine.substring(1); + break; case receivedLine.startsWith('G'): // EQ / iMS (RF+/IF+) const mapping = filterMappings[receivedLine]; if (mapping) { diff --git a/server/endpoints.js b/server/endpoints.js index d6ecfb6..d4f17c5 100644 --- a/server/endpoints.js +++ b/server/endpoints.js @@ -11,7 +11,8 @@ const { parseAudioDevice } = require('./stream/parser'); const { configName, serverConfig, configUpdate, configSave, configExists, configPath } = require('./server_config'); const helpers = require('./helpers'); const storage = require('./storage'); -const { logInfo, logDebug, logWarn, logError, logFfmpeg, logs } = require('./console'); +const tunerProfiles = require('./tuner_profiles'); +const { logInfo, logs } = require('./console'); const dataHandler = require('./datahandler'); const fmdxList = require('./fmdx_list'); const { allPluginConfigs } = require('./plugins'); @@ -48,7 +49,13 @@ router.get('/', (req, res) => { isAdminAuthenticated: true, videoDevices: result.audioDevices, audioDevices: result.videoDevices, - serialPorts: serialPorts + serialPorts: serialPorts, + serialPorts: serialPorts, + tunerProfiles: tunerProfiles.map((profile) => ({ + id: profile.id, + label: profile.label, + detailsHtml: helpers.parseMarkdown(profile.details || '') + })) }); }); }); @@ -68,6 +75,8 @@ router.get('/', (req, res) => { tuningUpperLimit: serverConfig.webserver.tuningUpperLimit, chatEnabled: serverConfig.webserver.chatEnabled, device: serverConfig.device, + tunerProfiles, + si47xxAgcControl: !!serverConfig.si47xx?.agcControl, noPlugins, plugins: serverConfig.plugins, fmlist_integration: serverConfig.extras.fmlistIntegration, @@ -101,7 +110,12 @@ router.get('/wizard', (req, res) => { isAdminAuthenticated: req.session.isAdminAuthenticated, videoDevices: result.audioDevices, audioDevices: result.videoDevices, - serialPorts: serialPorts + serialPorts: serialPorts, + tunerProfiles: tunerProfiles.map((profile) => ({ + id: profile.id, + label: profile.label, + detailsHtml: helpers.parseMarkdown(profile.details || '') + })) }); }); }) @@ -135,20 +149,25 @@ router.get('/wizard', (req, res) => { const updatedConfig = loadConfig(); // Reload the config every time res.render('setup', { - isAdminAuthenticated: req.session.isAdminAuthenticated, - videoDevices: result.audioDevices, - audioDevices: result.videoDevices, - serialPorts: serialPorts, - memoryUsage: (process.memoryUsage.rss() / 1024 / 1024).toFixed(1) + ' MB', - memoryHeap: (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(1) + ' MB', - processUptime: formattedProcessUptime, - consoleOutput: logs, - plugins: allPluginConfigs, - enabledPlugins: updatedConfig.plugins, - onlineUsers: dataHandler.dataToSend.users, - connectedUsers: storage.connectedUsers, - device: serverConfig.device, - banlist: updatedConfig.webserver.banlist // Updated banlist from the latest config + isAdminAuthenticated: req.session.isAdminAuthenticated, + videoDevices: result.audioDevices, + audioDevices: result.videoDevices, + serialPorts: serialPorts, + memoryUsage: (process.memoryUsage.rss() / 1024 / 1024).toFixed(1) + ' MB', + memoryHeap: (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(1) + ' MB', + processUptime: formattedProcessUptime, + consoleOutput: logs, + plugins: allPluginConfigs, + enabledPlugins: updatedConfig.plugins, + onlineUsers: dataHandler.dataToSend.users, + connectedUsers: storage.connectedUsers, + device: serverConfig.device, + banlist: updatedConfig.webserver.banlist, // Updated banlist from the latest config + tunerProfiles: tunerProfiles.map((profile) => ({ + id: profile.id, + label: profile.label, + detailsHtml: helpers.parseMarkdown(profile.details || '') + })) }); }); }) @@ -164,12 +183,13 @@ router.get('/rdsspy', (req, res) => { }); router.get('/api', (req, res) => { - const { ps_errors, rt0_errors, rt1_errors, ims, eq, ant, st_forced, previousFreq, txInfo, ...dataToSend } = dataHandler.dataToSend; + const { ps_errors, rt0_errors, rt1_errors, ims, eq, ant, st_forced, previousFreq, txInfo, rdsMode, ...dataToSend } = dataHandler.dataToSend; res.json({ ...dataToSend, txInfo: txInfo, ps_errors: ps_errors, - ant: ant + ant: ant, + rbds: serverConfig.webserver.rdsMode }); }); @@ -241,6 +261,7 @@ router.get('/kick', (req, res) => { }); router.get('/addToBanlist', (req, res) => { + if (!req.session.isAdminAuthenticated) return; const ipAddress = req.query.ip; const location = 'Unknown'; const date = Date.now(); @@ -252,17 +273,14 @@ router.get('/addToBanlist', (req, res) => { serverConfig.webserver.banlist = []; } - if (req.session.isAdminAuthenticated) { - serverConfig.webserver.banlist.push(userBanData); - configSave(); - res.json({ success: true, message: 'IP address added to banlist.' }); - helpers.kickClient(ipAddress); - } else { - res.status(403).json({ success: false, message: 'Unauthorized access.' }); - } + serverConfig.webserver.banlist.push(userBanData); + configSave(); + res.json({ success: true, message: 'IP address added to banlist.' }); + helpers.kickClient(ipAddress); }); router.get('/removeFromBanlist', (req, res) => { + if (!req.session.isAdminAuthenticated) return; const ipAddress = req.query.ip; if (typeof serverConfig.webserver.banlist !== 'object') { diff --git a/server/index.js b/server/index.js index a3e3d91..48addef 100644 --- a/server/index.js +++ b/server/index.js @@ -27,6 +27,7 @@ const fmdxList = require('./fmdx_list'); const { logError, logInfo, logWarn } = require('./console'); const storage = require('./storage'); const { serverConfig, configExists } = require('./server_config'); +const pluginsApi = require('./plugins_api'); const pjson = require('../package.json'); // Function to find server files based on the plugins listed in config @@ -155,8 +156,9 @@ function connectToSerial() { return; } - logInfo('Using COM device: ' + serverConfig.xdrd.comPort); + logInfo('Using serial port: ' + serverConfig.xdrd.comPort); dataHandler.state.isSerialportAlive = true; + pluginsApi.setOutput(serialport); setTimeout(() => { serialport.write('x\n'); }, 3000); @@ -175,7 +177,7 @@ function connectToSerial() { } else serialport.write('T87500\n'); dataHandler.state.isSerialportRetrying = false; - serialport.write('A0\n'); + if (serverConfig.device === 'si47xx') serialport.write('A0\n'); serialport.write('F-1\n'); serialport.write('W0\n'); serverConfig.webserver.rdsMode ? serialport.write('D1\n') : serialport.write('D0\n'); @@ -202,6 +204,7 @@ function connectToSerial() { // Handle port closure serialport.on('close', () => { + pluginsApi.setOutput(null); logWarn('Disconnected from ' + serverConfig.xdrd.comPort + '. Attempting to reconnect.'); setTimeout(() => { dataHandler.state.isSerialportRetrying = true; @@ -220,6 +223,7 @@ function connectToXdrd() { if (xdrd.wirelessConnection && configExists()) { client.connect(xdrd.xdrdPort, xdrd.xdrdIp, () => { logInfo('Connection to xdrd established successfully.'); + pluginsApi.setOutput(client); authFlags = { authMsg: false, @@ -277,7 +281,7 @@ client.on('data', (data) => { client.write(serverConfig.defaultFreq && serverConfig.enableDefaultFreq === true ? 'T' + Math.round(serverConfig.defaultFreq * 1000) + '\n' : 'T87500\n'); dataHandler.initialData.freq = serverConfig.defaultFreq && serverConfig.enableDefaultFreq === true ? Number(serverConfig.defaultFreq).toFixed(3) : (87.5).toFixed(3); dataHandler.dataToSend.freq = serverConfig.defaultFreq && serverConfig.enableDefaultFreq === true ? Number(serverConfig.defaultFreq).toFixed(3) : (87.5).toFixed(3); - client.write('A0\n'); + if (serverConfig.device === 'si47xx') serialport.write('A0\n'); client.write(serverConfig.audio.startupVolume ? 'Y' + (serverConfig.audio.startupVolume * 100).toFixed(0) + '\n' : 'Y100\n'); serverConfig.webserver.rdsMode ? client.write('D1\n') : client.write('D0\n'); return; @@ -287,6 +291,7 @@ client.on('data', (data) => { }); client.on('close', () => { + pluginsApi.setOutput(null); if(serverConfig.autoShutdown === false) { logWarn('Disconnected from xdrd. Attempting to reconnect.'); setTimeout(function () { @@ -572,7 +577,12 @@ pluginsWss.on('connection', (ws, request) => { }); // Websocket register for /text, /audio and /chat paths -httpServer.on('upgrade', (request, socket, head) => { +httpServer.on('upgrade', (request, socket, head) => { + const clientIp = request.headers['x-forwarded-for'] || request.connection.remoteAddress; + if (serverConfig.webserver.banlist?.includes(clientIp)) { + socket.destroy(); + return; + } if (request.url === '/text') { sessionMiddleware(request, {}, () => { wss.handleUpgrade(request, socket, head, (ws) => { @@ -595,23 +605,6 @@ httpServer.on('upgrade', (request, socket, head) => { sessionMiddleware(request, {}, () => { rdsWss.handleUpgrade(request, socket, head, (ws) => { rdsWss.emit('connection', ws, request); - - const clientIp = request.headers['x-forwarded-for'] || request.connection.remoteAddress; - const userCommandHistory = {}; - if (serverConfig.webserver.banlist?.includes(clientIp)) { - ws.close(1008, 'Banned IP'); - return; - } - - // Anti-spam tracking for each client - const userCommands = {}; - let lastWarn = { time: 0 }; - - ws.on('message', function incoming(message) { - // Anti-spam - const command = helpers.antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '5', 'rds'); - }); - }); }); } else if (request.url === '/data_plugins') { @@ -648,3 +641,6 @@ helpers.checkIPv6Support((isIPv6Supported) => { startServer(ipv6Address, true); // Start on IPv6 } else startServer(ipv4Address, false); // Start only on IPv4 }); + +pluginsApi.registerServerContext({ wss, pluginsWss, httpServer, serverConfig }); +module.exports = { wss, pluginsWss, httpServer, serverConfig }; \ No newline at end of file diff --git a/server/plugin_api.js b/server/plugin_api.js new file mode 100644 index 0000000..506fcb3 --- /dev/null +++ b/server/plugin_api.js @@ -0,0 +1,137 @@ +// plugins_api.js +// Shared API for server plugins: +// - Provides privileged/admin command access +// - Exposes server-side hooks for inter-plugin communication +// - Optionally broadcasts events to connected plugin WebSocket clients + +const { EventEmitter } = require('events'); +const { logInfo, logWarn, logError } = require('./console'); + +let output = null; +let wss = null; +let pluginsWss = null; +let httpServer = null; +let serverConfig = null; + +// ---- internal plugin event bus ---- + +const pluginEvents = new EventEmitter(); +// prevent accidental memory leak warnings +pluginEvents.setMaxListeners(50); + +// ---- registration server side ---- + +function registerServerContext(ctx) { + if (ctx.wss) wss = ctx.wss; + if (ctx.pluginsWss) pluginsWss = ctx.pluginsWss; + if (ctx.httpServer) httpServer = ctx.httpServer; + if (ctx.serverConfig) serverConfig = ctx.serverConfig; +} + +function setOutput(newOutput) { + output = newOutput; +} + +function clearOutput() { + output = null; +} + +// ---- accessors plugin side ---- + +function getWss() { + return wss; +} + +function getPluginsWss() { + return pluginsWss; +} + +function getHttpServer() { + return httpServer; +} + +function getServerConfig() { + return serverConfig; +} + +// ---- privileged command path ---- + +async function sendPrivilegedCommand(command, isPluginInternal = false) { + const maxWait = 10000; + const interval = 500; + let waited = 0; + + while (!output && waited < maxWait) { + await new Promise(resolve => setTimeout(resolve, interval)); + waited += interval; + } + + if (!output) { + logError(`[Privileged Send] Timeout waiting for output (${command})`); + return false; + } + + if (isPluginInternal) { + output.write(`${command}\n`); + //logInfo(`[Privileged Plugin] Command sent: ${command}`); // Debug + return true; + } + + logWarn(`[Privileged Send] Rejected (not internal): ${command.slice(0, 64)}`); + return false; +} + +// ---- plugin hook API ---- + +function emitPluginEvent(event, payload, opts = {}) { + pluginEvents.emit(event, payload); + + // Stop here unless option to broadcast to clients if true + if (opts.broadcast === false) return; + + // Broadcast to connected plugin WebSocket clients if available + if (pluginsWss) { + const message = JSON.stringify({ type: event, value: payload }); + pluginsWss.clients.forEach((client) => { + if (client.readyState === client.OPEN) { + try { + // Send event to client + client.send(message); + } catch (err) { + logWarn(`[plugins_api] Failed to send ${event} to client: ${err.message}`); + } + } + }); + } +} + +function onPluginEvent(event, handler) { + pluginEvents.on(event, handler); +} + +function offPluginEvent(event, handler) { + pluginEvents.off(event, handler); +} + +// ---- exports ---- + +module.exports = { + // server registration + registerServerContext, + setOutput, + clearOutput, + + // server context access + getWss, + getPluginsWss, + getHttpServer, + getServerConfig, + + // privileged control + sendPrivilegedCommand, + + // inter-plugin hooks + emitPluginEvent, + onPluginEvent, + offPluginEvent +}; \ No newline at end of file diff --git a/server/server_config.js b/server/server_config.js index 203a5ee..73bfb3c 100644 --- a/server/server_config.js +++ b/server/server_config.js @@ -90,6 +90,9 @@ let serverConfig = { fmlistAdminOnly: false, fmlistOmid: "", }, + si47xx: { + agcControl: false + }, tunnel: { enabled: false, username: "", diff --git a/server/tuner_profiles.js b/server/tuner_profiles.js new file mode 100644 index 0000000..453afb6 --- /dev/null +++ b/server/tuner_profiles.js @@ -0,0 +1,84 @@ +const tunerProfiles = [ + { + id: 'tef', + label: 'TEF668x', + fmBandwidths: [ + { value: 0, label: 'Auto' }, + { value: 56000, label: '56 kHz' }, + { value: 64000, label: '64 kHz' }, + { value: 72000, label: '72 kHz' }, + { value: 84000, label: '84 kHz' }, + { value: 97000, label: '97 kHz' }, + { value: 114000, label: '114 kHz' }, + { value: 133000, label: '133 kHz' }, + { value: 151000, label: '151 kHz' }, + { value: 184000, label: '184 kHz' }, + { value: 200000, label: '200 kHz' }, + { value: 217000, label: '217 kHz' }, + { value: 236000, label: '236 kHz' }, + { value: 254000, label: '254 kHz' }, + { value: 287000, label: '287 kHz' }, + { value: 311000, label: '311 kHz' } + ], + details: '' + }, + { + id: 'xdr', + label: 'XDR (F1HD / S10HDiP)', + fmBandwidths: [ + { value: 0, value2: -1, label: 'Auto' }, + { value: 55000, value2: 0, label: '55 kHz' }, + { value: 73000, value2: 1, label: '73 kHz' }, + { value: 90000, value2: 2, label: '90 kHz' }, + { value: 108000, value2: 3, label: '108 kHz' }, + { value: 125000, value2: 4, label: '125 kHz' }, + { value: 142000, value2: 5, label: '142 kHz' }, + { value: 159000, value2: 6, label: '159 kHz' }, + { value: 177000, value2: 7, label: '177 kHz' }, + { value: 194000, value2: 8, label: '194 kHz' }, + { value: 211000, value2: 9, label: '211 kHz' }, + { value: 229000, value2: 10, label: '229 kHz' }, + { value: 246000, value2: 11, label: '246 kHz' }, + { value: 263000, value2: 12, label: '263 kHz' }, + { value: 281000, value2: 13, label: '281 kHz' }, + { value: 298000, value2: 14, label: '298 kHz' }, + { value: 309000, value2: 15, label: '309 kHz' } + ], + details: '' + }, + { + id: 'sdr', + label: 'SDR (RTL-SDR / AirSpy)', + fmBandwidths: [ + { value: 0, label: 'Auto' }, + { value: 4000, label: '4 kHz' }, + { value: 8000, label: '8 kHz' }, + { value: 10000, label: '10 kHz' }, + { value: 20000, label: '20 kHz' }, + { value: 30000, label: '30 kHz' }, + { value: 50000, label: '50 kHz' }, + { value: 75000, label: '75 kHz' }, + { value: 100000, label: '100 kHz' }, + { value: 125000, label: '125 kHz' }, + { value: 150000, label: '150 kHz' }, + { value: 175000, label: '175 kHz' }, + { value: 200000, label: '200 kHz' }, + { value: 225000, label: '225 kHz' } + ], + details: '' + }, + { + id: 'si47xx', + label: 'Si47XX (Si4735 / Si4732)', + fmBandwidths: [ + { value: 0, label: 'Auto' }, + { value: 40000, label: '40 kHz' }, + { value: 60000, label: '60 kHz' }, + { value: 84000, label: '84 kHz' }, + { value: 110000, label: '110 kHz' } + ], + details: '' + } +]; + +module.exports = tunerProfiles; \ No newline at end of file diff --git a/web/_bwSwitch.ejs b/web/_bwSwitch.ejs index 351673e..ef9d2fa 100644 --- a/web/_bwSwitch.ejs +++ b/web/_bwSwitch.ejs @@ -1,62 +1,12 @@ <% let options = []; -if (device === 'tef') { - options = [ - { value: 0, label: 'Auto' }, - { value: 56000, label: '56 kHz' }, - { value: 64000, label: '64 kHz' }, - { value: 72000, label: '72 kHz' }, - { value: 84000, label: '84 kHz' }, - { value: 97000, label: '97 kHz' }, - { value: 114000, label: '114 kHz' }, - { value: 133000, label: '133 kHz' }, - { value: 151000, label: '151 kHz' }, - { value: 184000, label: '184 kHz' }, - { value: 200000, label: '200 kHz' }, - { value: 217000, label: '217 kHz' }, - { value: 236000, label: '236 kHz' }, - { value: 254000, label: '254 kHz' }, - { value: 287000, label: '287 kHz' }, - { value: 311000, label: '311 kHz' } - ]; -} else if (device === 'xdr') { - options = [ - { value: 0, value2: -1, label: 'Auto' }, - { value: 55000, value2: 0, label: '55 kHz' }, - { value: 73000, value2: 1, label: '73 kHz' }, - { value: 90000, value2: 2, label: '90 kHz' }, - { value: 108000, value2: 3, label: '108 kHz' }, - { value: 125000, value2: 4, label: '125 kHz' }, - { value: 142000, value2: 5, label: '142 kHz' }, - { value: 159000, value2: 6, label: '159 kHz' }, - { value: 177000, value2: 7, label: '177 kHz' }, - { value: 194000, value2: 8, label: '194 kHz' }, - { value: 211000, value2: 9, label: '211 kHz' }, - { value: 229000, value2: 10, label: '229 kHz' }, - { value: 246000, value2: 11, label: '246 kHz' }, - { value: 263000, value2: 12, label: '263 kHz' }, - { value: 281000, value2: 13, label: '281 kHz' }, - { value: 298000, value2: 14, label: '298 kHz' }, - { value: 309000, value2: 15, label: '309 kHz' } - ]; -} else if (device === 'sdr') { - options = [ - { value: 0, label: 'Auto' }, - { value: 4000, label: '4 kHz' }, - { value: 8000, label: '8 kHz' }, - { value: 10000, label: '10 kHz' }, - { value: 20000, label: '20 kHz' }, - { value: 30000, label: '30 kHz' }, - { value: 50000, label: '50 kHz' }, - { value: 75000, label: '75 kHz' }, - { value: 100000, label: '100 kHz' }, - { value: 125000, label: '125 kHz' }, - { value: 150000, label: '150 kHz' }, - { value: 175000, label: '175 kHz' }, - { value: 200000, label: '200 kHz' }, - { value: 225000, label: '225 kHz' } - ]; +const profile = Array.isArray(tunerProfiles) + ? tunerProfiles.find((item) => item.id === device) + : null; + +if (Array.isArray(profile?.fmBandwidths)) { + options = profile.fmBandwidths; } %> diff --git a/web/css/breadcrumbs.css b/web/css/breadcrumbs.css index 79ba36d..0ac8120 100644 --- a/web/css/breadcrumbs.css +++ b/web/css/breadcrumbs.css @@ -293,8 +293,8 @@ pre { top: 10px; } -.flex-container.contains-dropdown { - z-index: 999; +.contains-dropdown { + z-index: 990; position: relative; } diff --git a/web/index.ejs b/web/index.ejs index 473f166..14d0957 100644 --- a/web/index.ejs +++ b/web/index.ejs @@ -94,6 +94,7 @@ <% if (device == 'tef') { %>TEF668x<% } %> <% if (device == 'xdr') { %>Sony XDR<% } %> <% if (device == 'sdr') { %>SDR<% } %> + <% if (device == 'si47xx') { %>SI47XX<% } %>
@@ -228,6 +229,17 @@
<% if (device == 'tef') { %><% } %> <% if (device == 'xdr') { %><% } %> + <% if (device == 'si47xx' && si47xxAgcControl) { %> + + <% } %>
<% if (device == 'tef') { %><% } %> @@ -248,7 +260,7 @@ <% if (bwSwitch) { %> - <%- include('_bwSwitch', { device: device, id: 'data-bw', cssClass: 'panel-50 dropdown-up m-0 w-150 m-left-15', cssClassOptions: 'open-top' }) %> + <%- include('_bwSwitch', { device: device, tunerProfiles: tunerProfiles, id: 'data-bw', cssClass: 'panel-50 dropdown-up m-0 w-150 m-left-15', cssClassOptions: 'open-top' }) %> <% } %> <% if (fmlist_integration == true && (fmlist_adminOnly == false || isTuneAuthenticated)) { %> <% } %> <% if (device == 'xdr') { %><% } %> + <% if (device == 'si47xx' && si47xxAgcControl) { %> + + <% } %> <% if (device == 'tef') { %><% } %> <% if (device == 'xdr') { %><% } %>
diff --git a/web/js/dropdown.js b/web/js/dropdown.js index fa50253..7841ffc 100644 --- a/web/js/dropdown.js +++ b/web/js/dropdown.js @@ -42,6 +42,14 @@ $(document).ready(function() { socket.send("W" + $(event.currentTarget).attr('data-value')); $currentDropdown.find('input').val($(event.currentTarget).text()); break; + case 'data-agc': + socket.send("A" + $(event.currentTarget).attr('data-value')); + $currentDropdown.find('input').val($(event.currentTarget).text()); + break; + case 'data-agc-phone': + socket.send("A" + $(event.currentTarget).attr('data-value')); + $currentDropdown.find('input').val($(event.currentTarget).text()); + break; default: $currentDropdown.find('input') .val($(event.currentTarget).text()) diff --git a/web/js/main.js b/web/js/main.js index a11f0da..0789cba 100644 --- a/web/js/main.js +++ b/web/js/main.js @@ -890,6 +890,7 @@ const $dataSt = $('.data-st'); const $dataRt0 = $('#data-rt0 span'); const $dataRt1 = $('#data-rt1 span'); const $dataAntInput = $('.data-ant input'); +const $dataAgcInput = $('.data-agc input'); const $dataBwInput = $('.data-bw input'); const $dataStationContainer = $('#data-station-container'); const $dataTp = $('.data-tp'); @@ -1008,11 +1009,10 @@ const updateDataElements = throttle(function(parsedData) { $dataAntInput.val($('.data-ant li[data-value="' + parsedData.ant + '"]').first().text()); - if (parsedData.bw < 500) { - $dataBwInput.val($('.data-bw li[data-value2="' + parsedData.bw + '"]').first().text()); - } else { - $dataBwInput.val($('.data-bw li[data-value="' + parsedData.bw + '"]').first().text()); - } + if (typeof parsedData.agc !== 'undefined') $dataAgcInput.val($('.data-agc li[data-value="' + parsedData.agc + '"]').first().text()); + + if (parsedData.bw < 500) $dataBwInput.val($('.data-bw li[data-value2="' + parsedData.bw + '"]').first().text()); + else $dataBwInput.val($('.data-bw li[data-value="' + parsedData.bw + '"]').first().text()); if (parsedData.txInfo.tx.length > 1) { updateTextIfChanged($('#data-station-name'), parsedData.txInfo.tx.replace(/%/g, '%25')); diff --git a/web/js/setup.js b/web/js/setup.js index 16d737d..18f2b6e 100644 --- a/web/js/setup.js +++ b/web/js/setup.js @@ -24,7 +24,8 @@ function mapCreate() { if (!(typeof map == "object")) { map = L.map('map', { center: [40, 0], - zoom: 3 + zoom: 3, + worldCopyJump: true }); } else { map.setZoom(3).panTo([40, 0]); @@ -55,9 +56,9 @@ function mapCreate() { $('#identification-lon').val(ev.latlng.lng.toFixed(6)); if (typeof pin == "object") { - pin.setLatLng(ev.latlng); + pin.setLatLng(ev.latlng.wrap()); } else { - pin = L.marker(ev.latlng, { riseOnHover: true, draggable: true }).addTo(map); + pin = L.marker(ev.latlng.wrap(), { riseOnHover: true, draggable: true }).addTo(map); pin.on('dragend', function(ev) { $('#identification-lat').val(ev.target.getLatLng().lat.toFixed(6)); $('#identification-lon').val(ev.target.getLatLng().lng.toFixed(6)); @@ -259,8 +260,8 @@ function checkTunnelServers() { url: '/tunnelservers', method: 'GET', success: function(servers) { - const $options = $('#tunnel-server ul.options'); - const $input = $('#tunnel-serverSelect'); + const $options = $('#tunnel-regionselect ul.options'); + const $input = $('#tunnel-region'); const selectedValue = $input.val(); // currently selected value (label or value?) servers.forEach(server => { diff --git a/web/js/ver.js b/web/js/ver.js index 70af489..1625d34 100644 --- a/web/js/ver.js +++ b/web/js/ver.js @@ -1,2 +1,2 @@ -const versionDate = new Date('Nov 30, 2025 23:00:00'); -const currentVersion = `v1.3.12 [${versionDate.getDate()}/${versionDate.getMonth() + 1}/${versionDate.getFullYear()}]`; \ No newline at end of file +const versionDate = new Date('Feb 24, 2026 01:00:00'); +const currentVersion = `v1.4.0 [${versionDate.getDate()}/${versionDate.getMonth() + 1}/${versionDate.getFullYear()}]`; \ No newline at end of file diff --git a/web/setup.ejs b/web/setup.ejs index 9ef2c04..3e5ec69 100644 --- a/web/setup.ejs +++ b/web/setup.ejs @@ -215,8 +215,8 @@
-

Volume

-

This option will boost the audio volume globally, recommended for the Headless TEF.

+

Audio boost

+

This option will boost the audio volume. Use if the output is too quiet.

<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Audio Boost', id: 'audio-audioBoost'}) %>
@@ -351,23 +351,22 @@

Tuner settings

-
-
+

Device type

- <%- include('_components', { component: 'dropdown', id: 'device-selector', inputId: 'device', label: 'Device', cssClass: '', placeholder: 'TEF668x / TEA685x', - options: [ - { value: 'tef', label: 'TEF668x / TEA685x' }, - { value: 'xdr', label: 'XDR (F1HD / S10HDiP)' }, - { value: 'sdr', label: 'SDR (RTL-SDR / AirSpy)' }, - { value: 'other', label: 'Other' } - ] - }) %>
- +
+ <%- include('_components', { component: 'dropdown', id: 'device-selector', inputId: 'device', label: 'Device', cssClass: '', placeholder: 'TEF668x / TEA685x', + options: tunerProfiles.map(profile => ({ + value: profile.id, + label: profile.label + })) + }) %>
+
-
+
+

Connection type

-

If you want to choose the COM port directly, choose "Direct".
If you use xdrd or your receiver is connected via Wi-Fi, choose TCP/IP.

+

If you want to choose the serial port directly, choose "Direct".
If you use xdrd or your receiver is connected via Wi-Fi, choose TCP/IP.

-
-
-

Device / Server

-
-

Choose your desired COM port
 

+

Choose your desired serial port
 

<%- include('_components', { component: 'dropdown', id: 'deviceList', inputId: 'xdrd-comPort', - label: 'USB Device', + label: 'Serial port', cssClass: '', - placeholder: 'Choose your USB device', + placeholder: 'Choose your serial port', options: serialPorts.map(serialPort => ({ value: serialPort.path, label: `${serialPort.path} - ${serialPort.friendlyName}` @@ -433,6 +428,13 @@

Toggling this option will put the tuner to sleep when no clients are connected.

<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Auto-shutdown', id: 'autoShutdown'}) %>
+
+
+

SI47XX AGC control

+

Allow users to change SI47XX AGC mode from the main UI.

+ <%- include('_components', {component: 'checkbox', cssClass: '', label: 'Enable AGC control', id: 'si47xx-agcControl'}) %>
+
+
@@ -703,7 +705,7 @@

You can also get an tunnel from kuba201 discord, one of the contributors of this version of the application.

Main tunnel settings

<%- include('_components', {component: 'checkbox', cssClass: 'm-right-10', label: 'Enable tunnel', id: 'tunnel-enabled'}) %>
- <%- include('_components', { component: 'dropdown', id: 'tunnel-server', inputId: 'tunnel-serverSelect', label: 'Official server region', cssClass: '', placeholder: 'Europe', + <%- include('_components', { component: 'dropdown', id: 'tunnel-regionSelect', inputId: 'tunnel-region', label: 'Official server region', cssClass: '', placeholder: 'Europe', options: [ { value: 'eu', label: 'Europe' }, { value: 'us', label: 'Americas' }, diff --git a/web/wizard.ejs b/web/wizard.ejs index 3e57144..24472f4 100644 --- a/web/wizard.ejs +++ b/web/wizard.ejs @@ -47,15 +47,13 @@

Tuner type

Settings a proper device type ensures that the correct interface and settings will load.

-
- <%- include('_components', { component: 'dropdown', id: 'device-selector', inputId: 'device', label: 'Device', cssClass: '', placeholder: 'TEF668x / TEA685x', - options: [ - { value: 'tef', label: 'TEF668x / TEA685x' }, - { value: 'xdr', label: 'XDR (F1HD / S10HDiP)' }, - { value: 'sdr', label: 'SDR (RTL-SDR / AirSpy)' }, - { value: 'other', label: 'Other' } - ] - }) %>
+
+ <%- include('_components', { component: 'dropdown', id: 'device-selector', inputId: 'device', label: 'Device', cssClass: '', placeholder: 'TEF668x / TEA685x', + options: tunerProfiles.map(profile => ({ + value: profile.id, + label: profile.label + })) + }) %>

Tuner connection

@@ -70,16 +68,16 @@
-

It's time to choose your USB device.

+

It's time to choose your serial port.

<%- include('_components', { component: 'dropdown', id: 'deviceList', inputId: 'xdrd-comPort', - label: 'USB Device', + label: 'Serial port', cssClass: '', - placeholder: 'Choose your USB device', + placeholder: 'Choose your serial port', options: serialPorts.map(serialPort => ({ value: serialPort.path, label: `${serialPort.path} - ${serialPort.friendlyName}`