1
0
mirror of https://github.com/KubaPro010/fm-dx-webserver.git synced 2026-02-26 22:13:53 +01:00

we're up to date with the terrible

This commit is contained in:
2026-02-24 09:52:39 +01:00
parent 098b6ba4e9
commit 0ae484529d
17 changed files with 673 additions and 459 deletions

576
package-lock.json generated
View File

@@ -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"

View File

@@ -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"
}
}
}

View File

@@ -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) {

View File

@@ -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') {

View File

@@ -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 };

137
server/plugin_api.js Normal file
View File

@@ -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
};

View File

@@ -90,6 +90,9 @@ let serverConfig = {
fmlistAdminOnly: false,
fmlistOmid: "",
},
si47xx: {
agcControl: false
},
tunnel: {
enabled: false,
username: "",

84
server/tuner_profiles.js Normal file
View File

@@ -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;

View File

@@ -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;
}
%>

View File

@@ -293,8 +293,8 @@ pre {
top: 10px;
}
.flex-container.contains-dropdown {
z-index: 999;
.contains-dropdown {
z-index: 990;
position: relative;
}

View File

@@ -94,6 +94,7 @@
<% if (device == 'tef') { %>TEF668x<% } %>
<% if (device == 'xdr') { %>Sony XDR<% } %>
<% if (device == 'sdr') { %>SDR<% } %>
<% if (device == 'si47xx') { %>SI47XX<% } %>
</span>
</div>
<div class="color-3 m-10 text-medium">
@@ -228,6 +229,17 @@
<div class="panel-50 no-bg br-0 h-100 m-0 button-eq">
<% if (device == 'tef') { %><button style="border-radius: 15px 0px 0px 15px;" class="data-eq hide-phone tooltip" aria-label="EQ Filter" data-tooltip="<strong>The cEQ filter can reduce bandwidth below 56 kHz.</strong><br><br>Useful for weak stations next to strong ones,<br>although it may pick up more interference."><span class="text-bold">cEQ</span></button><% } %>
<% if (device == 'xdr') { %><button style="border-radius: 15px 0px 0px 15px;" class="data-eq hide-phone tooltip" aria-label="RF+ Filter" data-tooltip="<strong>The RF+ filter increases gain by 5dB</strong>"><span class="text-bold">RF+</span></button><% } %>
<% if (device == 'si47xx' && si47xxAgcControl) { %>
<div class="no-bg dropdown dropdown-up data-agc hide-phone w-150" id="data-agc" style="margin-right: 15px !important;">
<input type="text" placeholder="AGC" readonly tabindex="0">
<ul class="options open-top" tabindex="-1">
<li data-value="0" class="option" tabindex="0">Auto AGC</li>
<li data-value="1" class="option" tabindex="0">High</li>
<li data-value="3" class="option" tabindex="0">Medium</li>
<li data-value="2" class="option" tabindex="0">Low</li>
</ul>
</div>
<% } %>
</div>
<div class="panel-50 no-bg br-0 h-100 m-0 button-ims">
<% if (device == 'tef') { %><button style="border-radius: 0px 15px 15px 0px;" class="data-ims hide-phone tooltip" aria-label="iMS + Filter" data-tooltip="<strong>The iMS filter reduces multipath audio artifacts.</strong><br><br>It's recommended to leave it on most of the time."><span class="text-bold">iMS</span></button><% } %>
@@ -248,7 +260,7 @@
<input type="range" id="volumeSlider" min="0" max="1" step="0.01" value="1" aria-label="Volume slider">
</span>
<% 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)) { %>
<button class="tooltip bg-color-4 mini-popup log-fmlist" data-tooltip="<strong>LOG TO FMLIST</strong><br>Clicking this button logs the current station to FMLIST's visual logbook." aria-label="Log to FMLIST" style="width: 80px; height: 48px;margin-left: 15px !important;">
@@ -353,7 +365,7 @@
<div class="flex-phone">
<% if (bwSwitch) { %>
<div style="max-height: 48px;width: 50%;margin-right: 5px;">
<%- include('_bwSwitch', { device: device, id: 'data-bw-phone', cssClass: 'panel-100-real', cssClassOptions: 'text-center open-bottom' }) %>
<%- include('_bwSwitch', { device: device, tunerProfiles: tunerProfiles, id: 'data-bw-phone', cssClass: 'panel-100-real', cssClassOptions: 'text-center open-bottom' }) %>
</div>
<% } %>
@@ -363,6 +375,17 @@
<div class="flex-container flex-phone flex-center">
<% if (device == 'tef') { %><button class="data-eq tooltip p-10 m-right-5" style="height: 48px" aria-label="EQ Filter" data-tooltip="<strong>The cEQ filter can reduce bandwidth below 56 kHz.</strong><br><br>Useful for weak stations next to strong ones,<br>although it may pick up more interference."><span class="text-bold">cEQ</span></button><% } %>
<% if (device == 'xdr') { %><button class="data-eq tooltip p-10 m-right-5" aria-label="RF+ Filter" data-tooltip="<strong>The RF+ filter increases gain by 5dB</strong>"><span class="text-bold">RF+</span></button><% } %>
<% if (device == 'si47xx' && si47xxAgcControl) { %>
<div class="no-bg dropdown data-agc w-150" id="data-agc-phone" style="max-height: 48px;">
<input type="text" placeholder="AGC" readonly tabindex="0" style="border-radius: 15px;">
<ul class="options open-top" tabindex="-1">
<li data-value="0" class="option" tabindex="0">Auto AGC</li>
<li data-value="1" class="option" tabindex="0">High</li>
<li data-value="3" class="option" tabindex="0">Medium</li>
<li data-value="2" class="option" tabindex="0">Low</li>
</ul>
</div>
<% } %>
<% if (device == 'tef') { %><button class="data-ims tooltip p-10 m-left-5" style="height: 48px;" aria-label="iMS + Filter" data-tooltip="<strong>The iMS filter reduces multipath audio artifacts.</strong><br><br>It's recommended to leave it on most of the time."><span class="text-bold">iMS</span></button><% } %>
<% if (device == 'xdr') { %><button class="data-ims tooltip p-10 m-left-5" aria-label="IF+ Filter" data-tooltip="<strong>The IF+ filter increases gain by 6dB</strong>"><span class="text-bold">IF+</span></button><% } %>
</div>

View File

@@ -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())

View File

@@ -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'));

View File

@@ -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 => {

View File

@@ -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()}]`;
const versionDate = new Date('Feb 24, 2026 01:00:00');
const currentVersion = `v1.4.0 [${versionDate.getDate()}/${versionDate.getMonth() + 1}/${versionDate.getFullYear()}]`;

View File

@@ -215,8 +215,8 @@
<div class="flex-container">
<div class="panel-50 p-bottom-20">
<h3>Volume</h3>
<p>This option will boost the audio volume globally, recommended for the Headless TEF.</p>
<h3>Audio boost</h3>
<p>This option will boost the audio volume. Use if the output is too quiet.</p>
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Audio Boost', id: 'audio-audioBoost'}) %>
</div>
<div class="panel-50 p-bottom-20">
@@ -351,23 +351,22 @@
<div class="panel-full m-0 tab-content no-bg" id="tuner" role="tabpanel">
<h2>Tuner settings</h2>
<div class="flex-container contains-dropdown">
<div class="panel-33 p-bottom-20">
<div class="panel-100 p-bottom-20 contains-dropdown" style="z-index: 991;">
<h3>Device type</h3>
<%- 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' }
]
}) %><br>
<div class="flex-center" style="max-width: 520px; margin: 10px auto 0;">
<%- 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
}))
}) %><br>
</div>
</div>
<div class="panel-33 p-bottom-20" style="padding-right: 20px; padding-left: 20px;">
<div class="flex-container contains-dropdown">
<div class="panel-100 p-bottom-20" style="padding-right: 20px; padding-left: 20px;">
<h3>Connection type</h3>
<p class="text-gray">If you want to choose the COM port directly, choose "Direct".<br>If you use xdrd or your receiver is connected via Wi-Fi, choose TCP/IP.</p>
<p class="text-gray">If you want to choose the serial port directly, choose "Direct".<br>If you use xdrd or your receiver is connected via Wi-Fi, choose TCP/IP.</p>
<div class="auto top-10">
<label class="toggleSwitch nolabel" onclick="">
<input id="xdrd-wirelessConnection" type="checkbox" tabindex="0" aria-label="Connection type"/>
@@ -378,19 +377,15 @@
</span>
</label>
</div>
</div>
<div class="panel-33 p-bottom-20">
<h3>Device / Server</h3>
<div id="tuner-usb">
<p class="text-gray">Choose your desired <strong>COM port</strong><br>&nbsp;</p>
<p class="text-gray">Choose your desired <strong>serial port</strong><br>&nbsp;</p>
<%- 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 @@
<p>Toggling this option will put the tuner to sleep when no clients are connected.</p>
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Auto-shutdown', id: 'autoShutdown'}) %><br>
</div>
<div class="flex-container">
<div class="panel-50 no-bg">
<h4>SI47XX AGC control</h4>
<p>Allow users to change SI47XX AGC mode from the main UI.</p>
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Enable AGC control', id: 'si47xx-agcControl'}) %><br>
</div>
</div>
</div>
</div>
</div>
@@ -703,7 +705,7 @@
<p>You can also get an tunnel from kuba201 discord, one of the contributors of this version of the application.</p>
<h4>Main tunnel settings</h4>
<%- include('_components', {component: 'checkbox', cssClass: 'm-right-10', label: 'Enable tunnel', id: 'tunnel-enabled'}) %><br>
<%- 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' },

View File

@@ -47,15 +47,13 @@
<h3 class="settings-heading">Tuner type</h3>
<p class="m-0">Settings a proper device type ensures that the correct interface and settings will load.</p>
<div class="panel-100 no-bg flex-center">
<%- 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' }
]
}) %><br>
<div class="panel-100 no-bg flex-center" style="max-width: 520px; margin: 10px auto 0;">
<%- 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
}))
}) %><br>
</div>
<div class="clearfix"></div>
<h3 class="settings-heading">Tuner connection</h3>
@@ -70,16 +68,16 @@
</label>
</div>
<div id="tuner-usb" class="top-25">
<p>It's time to choose your USB device.</p>
<p>It's time to choose your serial port.</p>
<div class="panel-100 no-bg flex-center">
<%- 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}`