You've already forked fm-dx-webserver
mirror of
https://github.com/KubaPro010/fm-dx-webserver.git
synced 2026-02-26 22:13:53 +01:00
Compare commits
5 Commits
03ff93cd39
...
1d04719580
| Author | SHA1 | Date | |
|---|---|---|---|
|
1d04719580
|
|||
|
1f70b58295
|
|||
|
3080468415
|
|||
|
0ae484529d
|
|||
|
|
098b6ba4e9
|
595
package-lock.json
generated
595
package-lock.json
generated
@@ -1,24 +1,26 @@
|
||||
{
|
||||
"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",
|
||||
"figlet": "^1.10.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 +50,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 +149,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 +181,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 +196,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 +213,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 +227,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 +380,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 +444,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 +507,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 +566,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 +575,15 @@
|
||||
"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"
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "14.0.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
|
||||
"integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"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 +689,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 +766,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 +809,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 +869,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",
|
||||
@@ -897,31 +884,31 @@
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"node_modules/figlet": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/figlet/-/figlet-1.10.0.tgz",
|
||||
"integrity": "sha512-aktIwEZZ6Gp9AWdMXW4YCi0J2Ahuxo67fNJRUIWD81w8pQ0t9TS8FFpbl27ChlTLF06VkwjDesZSzEVzN75rzA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minimatch": "^5.0.1"
|
||||
}
|
||||
"commander": "^14.0.0"
|
||||
},
|
||||
"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"
|
||||
"bin": {
|
||||
"figlet": "bin/index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": ">= 17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/filelist": {
|
||||
"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": "^10.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/finalhandler": {
|
||||
@@ -1039,14 +1026,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 +1056,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 +1117,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 +1152,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 +1168,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 +1238,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 +1308,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 +1335,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 +1424,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 +1451,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 +1483,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 +1634,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 +1679,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 +1777,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 +1793,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 +1905,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"
|
||||
|
||||
20
package.json
20
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,17 @@
|
||||
"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",
|
||||
"figlet": "^1.10.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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,12 @@ const { logChat } = require('./console');
|
||||
const helpers = require('./helpers');
|
||||
|
||||
function createChatServer(storage) {
|
||||
if (!serverConfig.webserver.chatEnabled) {
|
||||
return null;
|
||||
}
|
||||
if (!serverConfig.webserver.chatEnabled) return null;
|
||||
|
||||
const chatWss = new WebSocket.Server({ noServer: true });
|
||||
|
||||
chatWss.on('connection', (ws, request) => {
|
||||
const clientIp = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
|
||||
const clientIp = request.headers['x-forwarded-for'] || request.socket.remoteAddress;
|
||||
const userCommandHistory = {};
|
||||
|
||||
if (serverConfig.webserver.banlist?.includes(clientIp)) {
|
||||
@@ -79,32 +77,19 @@ function createChatServer(storage) {
|
||||
|
||||
if (serverConfig.webserver.banlist?.includes(clientIp)) return;
|
||||
|
||||
if (request.session?.isAdminAuthenticated === true) {
|
||||
messageData.admin = true;
|
||||
}
|
||||
|
||||
if (messageData.nickname?.length > 32) {
|
||||
messageData.nickname = messageData.nickname.substring(0, 32);
|
||||
}
|
||||
|
||||
if (messageData.message?.length > 255) {
|
||||
messageData.message = messageData.message.substring(0, 255);
|
||||
}
|
||||
if (request.session?.isAdminAuthenticated === true) messageData.admin = true;
|
||||
if (messageData.nickname?.length > 32) messageData.nickname = messageData.nickname.substring(0, 32);
|
||||
if (messageData.message?.length > 255) messageData.message = messageData.message.substring(0, 255);
|
||||
|
||||
storage.chatHistory.push(messageData);
|
||||
if (storage.chatHistory.length > 50) {
|
||||
storage.chatHistory.shift();
|
||||
}
|
||||
if (storage.chatHistory.length > 50) storage.chatHistory.shift();
|
||||
|
||||
logChat(messageData);
|
||||
|
||||
chatWss.clients.forEach((client) => {
|
||||
if (client.readyState === WebSocket.OPEN) {
|
||||
const responseMessage = { ...messageData };
|
||||
|
||||
if (!request.session?.isAdminAuthenticated) {
|
||||
delete responseMessage.ip;
|
||||
}
|
||||
if (!request.session?.isAdminAuthenticated) delete responseMessage.ip;
|
||||
|
||||
client.send(JSON.stringify(responseMessage));
|
||||
}
|
||||
@@ -112,7 +97,7 @@ function createChatServer(storage) {
|
||||
});
|
||||
});
|
||||
|
||||
return chatWss; // ← VERY IMPORTANT
|
||||
return chatWss;
|
||||
}
|
||||
|
||||
module.exports = { createChatServer };
|
||||
@@ -30,10 +30,10 @@ const getCurrentTime = () => {
|
||||
|
||||
const removeANSIEscapeCodes = (str) => str.replace(ANSI_ESCAPE_CODE_PATTERN, ''); // Strip ANSI escape codes from a string
|
||||
|
||||
const logMessage = (type, messages, verbose = false) => {
|
||||
const logMessage = (type, messages) => {
|
||||
const logMessage = `${getCurrentTime()} ${MESSAGE_PREFIX[type]} ${messages.join(' ')}`;
|
||||
|
||||
if (type === 'DEBUG' && verboseMode || type === 'FFMPEG' && verboseModeFfmpeg || type !== 'DEBUG' && type !== 'FFMPEG') {
|
||||
if ((type === 'DEBUG' && verboseMode) || (type === 'FFMPEG' && verboseModeFfmpeg) || type !== 'DEBUG' && type !== 'FFMPEG') {
|
||||
logs.push(logMessage);
|
||||
if (logs.length > maxConsoleLogLines) logs.shift();
|
||||
console.log(logMessage);
|
||||
@@ -42,7 +42,7 @@ const logMessage = (type, messages, verbose = false) => {
|
||||
if(type !== 'FFMPEG') appendLogToBuffer(logMessage);
|
||||
};
|
||||
|
||||
const logDebug = (...messages) => logMessage('DEBUG', messages, verboseMode);
|
||||
const logDebug = (...messages) => logMessage('DEBUG', messages);
|
||||
const logChat = (message) => logMessage('CHAT', [`${message.nickname} (${message.ip}) sent a chat message: ${message.message}`]);
|
||||
const logError = (...messages) => logMessage('ERROR', messages);
|
||||
const logFfmpeg = (...messages) => logMessage('FFMPEG', messages, verboseModeFfmpeg);
|
||||
|
||||
@@ -8,7 +8,7 @@ const updateInterval = 75;
|
||||
// Initialize the data object
|
||||
var dataToSend = {
|
||||
pi: '?',
|
||||
freq: 87.500.toFixed(3),
|
||||
freq: (87.500).toFixed(3),
|
||||
sig: 0,
|
||||
sigRaw: '',
|
||||
sigTop: -Infinity,
|
||||
@@ -28,6 +28,7 @@ var dataToSend = {
|
||||
rt_flag: '',
|
||||
ims: 0,
|
||||
eq: 0,
|
||||
agc: 0,
|
||||
ant: 0,
|
||||
txInfo: {
|
||||
tx: '',
|
||||
@@ -70,9 +71,7 @@ function rdsReceived() {
|
||||
clearTimeout(rdsTimeoutTimer);
|
||||
rdsTimeoutTimer = null;
|
||||
}
|
||||
if (serverConfig.webserver.rdsTimeout && serverConfig.webserver.rdsTimeout != 0) {
|
||||
rdsTimeoutTimer = setTimeout(rdsReset, serverConfig.webserver.rdsTimeout * 1000);
|
||||
}
|
||||
if (serverConfig.webserver.rdsTimeout && serverConfig.webserver.rdsTimeout != 0) rdsTimeoutTimer = setTimeout(rdsReset, serverConfig.webserver.rdsTimeout * 1000);
|
||||
}
|
||||
|
||||
function rdsReset() {
|
||||
@@ -128,6 +127,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) {
|
||||
|
||||
@@ -11,14 +11,15 @@ 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');
|
||||
|
||||
// Endpoints
|
||||
router.get('/', (req, res) => {
|
||||
let requestIp = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
|
||||
let requestIp = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
|
||||
|
||||
const normalizedIp = requestIp?.replace(/^::ffff:/, '');
|
||||
const ipList = (normalizedIp || '').split(',').map(ip => ip.trim()).filter(Boolean); // in case there are multiple IPs (proxy), we need to check all of them
|
||||
@@ -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 || '')
|
||||
}))
|
||||
});
|
||||
});
|
||||
})
|
||||
@@ -148,7 +162,12 @@ router.get('/wizard', (req, res) => {
|
||||
onlineUsers: dataHandler.dataToSend.users,
|
||||
connectedUsers: storage.connectedUsers,
|
||||
device: serverConfig.device,
|
||||
banlist: updatedConfig.webserver.banlist // Updated banlist from the latest config
|
||||
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 || '')
|
||||
}))
|
||||
});
|
||||
});
|
||||
})
|
||||
@@ -156,43 +175,37 @@ router.get('/wizard', (req, res) => {
|
||||
|
||||
|
||||
router.get('/rds', (req, res) => {
|
||||
res.send('Please connect using a WebSocket compatible app to obtain RDS stream.');
|
||||
res.send('Please connect using a WebSocket compatible app to obtain the RDS stream.');
|
||||
});
|
||||
|
||||
router.get('/rdsspy', (req, res) => {
|
||||
res.send('Please connect using a WebSocket compatible app to obtain RDS stream.');
|
||||
res.send('Please connect using a WebSocket compatible app to obtain the RDS stream.');
|
||||
});
|
||||
|
||||
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
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
const loginAttempts = {}; // Format: { 'ip': { count: 1, lastAttempt: 1234567890 } }
|
||||
const MAX_ATTEMPTS = 25;
|
||||
const MAX_ATTEMPTS = 15;
|
||||
const WINDOW_MS = 15 * 60 * 1000;
|
||||
|
||||
const authenticate = (req, res, next) => {
|
||||
const ip = req.ip || req.connection.remoteAddress;
|
||||
const now = Date.now();
|
||||
|
||||
if (!loginAttempts[ip]) {
|
||||
loginAttempts[ip] = { count: 0, lastAttempt: now };
|
||||
} else if (now - loginAttempts[ip].lastAttempt > WINDOW_MS) {
|
||||
loginAttempts[ip] = { count: 0, lastAttempt: now };
|
||||
}
|
||||
if (!loginAttempts[ip]) loginAttempts[ip] = { count: 0, lastAttempt: now };
|
||||
else if (now - loginAttempts[ip].lastAttempt > WINDOW_MS) loginAttempts[ip] = { count: 0, lastAttempt: now };
|
||||
|
||||
if (loginAttempts[ip].count >= MAX_ATTEMPTS) {
|
||||
return res.status(403).json({
|
||||
message: 'Too many login attempts. Please try again later.'
|
||||
});
|
||||
}
|
||||
if (loginAttempts[ip].count >= MAX_ATTEMPTS) return res.status(403).json({message: 'Too many login attempts. Please try again later.'});
|
||||
|
||||
const { password } = req.body;
|
||||
|
||||
@@ -230,17 +243,16 @@ router.get('/logout', (req, res) => {
|
||||
});
|
||||
|
||||
router.get('/kick', (req, res) => {
|
||||
const ipAddress = req.query.ip; // Extract the IP address parameter from the query string
|
||||
const ipAddress = req.query.ip;
|
||||
// Terminate the WebSocket connection for the specified IP address
|
||||
if(req.session.isAdminAuthenticated) {
|
||||
helpers.kickClient(ipAddress);
|
||||
}
|
||||
if(req.session.isAdminAuthenticated) helpers.kickClient(ipAddress);
|
||||
setTimeout(() => {
|
||||
res.redirect('/setup');
|
||||
}, 500);
|
||||
});
|
||||
|
||||
router.get('/addToBanlist', (req, res) => {
|
||||
if (!req.session.isAdminAuthenticated) return;
|
||||
const ipAddress = req.query.ip;
|
||||
const location = 'Unknown';
|
||||
const date = Date.now();
|
||||
@@ -248,32 +260,24 @@ router.get('/addToBanlist', (req, res) => {
|
||||
|
||||
userBanData = [ipAddress, location, date, reason];
|
||||
|
||||
if (typeof serverConfig.webserver.banlist !== 'object') {
|
||||
serverConfig.webserver.banlist = [];
|
||||
}
|
||||
if (typeof serverConfig.webserver.banlist !== 'object') 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.' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/removeFromBanlist', (req, res) => {
|
||||
if (!req.session.isAdminAuthenticated) return;
|
||||
|
||||
const ipAddress = req.query.ip;
|
||||
|
||||
if (typeof serverConfig.webserver.banlist !== 'object') {
|
||||
serverConfig.webserver.banlist = [];
|
||||
}
|
||||
if (typeof serverConfig.webserver.banlist !== 'object') serverConfig.webserver.banlist = [];
|
||||
|
||||
const banIndex = serverConfig.webserver.banlist.findIndex(ban => ban[0] === ipAddress);
|
||||
|
||||
if (banIndex === -1) {
|
||||
return res.status(404).json({ success: false, message: 'IP address not found in banlist.' });
|
||||
}
|
||||
if (banIndex === -1) return res.status(404).json({ success: false, message: 'IP address not found in banlist.' });
|
||||
|
||||
serverConfig.webserver.banlist.splice(banIndex, 1);
|
||||
configSave();
|
||||
@@ -285,19 +289,14 @@ router.get('/removeFromBanlist', (req, res) => {
|
||||
router.post('/saveData', (req, res) => {
|
||||
const data = req.body;
|
||||
let firstSetup;
|
||||
if(req.session.isAdminAuthenticated || configExists() === false) {
|
||||
if(req.session.isAdminAuthenticated || !configExists()) {
|
||||
configUpdate(data);
|
||||
fmdxList.update();
|
||||
|
||||
if(configExists() === false) {
|
||||
firstSetup = true;
|
||||
}
|
||||
if(!configExists()) firstSetup = true;
|
||||
logInfo('Server config changed successfully.');
|
||||
if(firstSetup === true) {
|
||||
res.status(200).send('Data saved successfully!\nPlease, restart the server to load your configuration.');
|
||||
} else {
|
||||
res.status(200).send('Data saved successfully!\nSome settings may need a server restart to apply.');
|
||||
}
|
||||
if(firstSetup === true) res.status(200).send('Data saved successfully!\nPlease, restart the server to load your configuration.');
|
||||
else res.status(200).send('Data saved successfully!\nSome settings may need a server restart to apply.');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -309,9 +308,8 @@ router.get('/getData', (req, res) => {
|
||||
if(req.session.isAdminAuthenticated) {
|
||||
// Check if the file exists
|
||||
fs.access(configPath, fs.constants.F_OK, (err) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
} else {
|
||||
if (err) console.log(err);
|
||||
else {
|
||||
// File exists, send it as the response
|
||||
res.sendFile(path.join(__dirname, '../' + configName + '.json'));
|
||||
}
|
||||
@@ -324,9 +322,7 @@ router.get('/getDevices', (req, res) => {
|
||||
parseAudioDevice((result) => {
|
||||
res.json(result);
|
||||
});
|
||||
} else {
|
||||
res.status(403).json({ error: 'Unauthorized' });
|
||||
}
|
||||
} else res.status(403).json({ error: 'Unauthorized' });
|
||||
});
|
||||
|
||||
/* Static data are being sent through here on connection - these don't change when the server is running */
|
||||
@@ -371,9 +367,7 @@ function canLog(id) {
|
||||
}
|
||||
}
|
||||
|
||||
if (logHistory[id] && (now - logHistory[id]) < sixtyMinutes) {
|
||||
return false; // Deny logging if less than 60 minutes have passed
|
||||
}
|
||||
if (logHistory[id] && (now - logHistory[id]) < sixtyMinutes) return false; // Deny logging if less than 60 minutes have passed
|
||||
logHistory[id] = now; // Update with the current timestamp
|
||||
return true;
|
||||
}
|
||||
|
||||
124
server/index.js
124
server/index.js
@@ -19,6 +19,7 @@ const { SerialPort } = require('serialport');
|
||||
const tunnel = require('./tunnel');
|
||||
const { createChatServer } = require('./chat');
|
||||
const { createAudioServer } = require('./stream/ws.js');
|
||||
const figlet = require('figlet');
|
||||
|
||||
// File imports
|
||||
const helpers = require('./helpers');
|
||||
@@ -27,6 +28,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
|
||||
@@ -34,14 +36,10 @@ function findServerFiles(plugins) {
|
||||
let results = [];
|
||||
plugins.forEach(plugin => {
|
||||
// Remove .js extension if present
|
||||
if (plugin.endsWith('.js')) {
|
||||
plugin = plugin.slice(0, -3);
|
||||
}
|
||||
if (plugin.endsWith('.js')) plugin = plugin.slice(0, -3);
|
||||
|
||||
const pluginPath = path.join(__dirname, '..', 'plugins', `${plugin}_server.js`);
|
||||
if (fs.existsSync(pluginPath) && fs.statSync(pluginPath).isFile()) {
|
||||
results.push(pluginPath);
|
||||
}
|
||||
if (fs.existsSync(pluginPath) && fs.statSync(pluginPath).isFile()) results.push(pluginPath);
|
||||
});
|
||||
return results;
|
||||
}
|
||||
@@ -79,14 +77,14 @@ const terminalWidth = readline.createInterface({
|
||||
}).output.columns;
|
||||
|
||||
|
||||
// Couldn't get figlet.js or something like that?
|
||||
console.log(`\x1b[32m
|
||||
_____ __ __ ______ __ __ __ _
|
||||
| ___| \\/ | | _ \\ \\/ / \\ \\ / /__| |__ ___ ___ _ ____ _____ _ __
|
||||
| |_ | |\\/| |_____| | | \\ / \\ \\ /\\ / / _ \\ '_ \\/ __|/ _ \\ '__\\ \\ / / _ \\ '__|
|
||||
| _| | | | |_____| |_| / \\ \\ V V / __/ |_) \\__ \\ __/ | \\ V / __/ |
|
||||
|_| |_| |_| |____/_/\\_\\ \\_/\\_/ \\___|_.__/|___/\\___|_| \\_/ \\___|_|
|
||||
`);
|
||||
figlet("FM-DX Webserver", function (err, data) {
|
||||
if (err) {
|
||||
console.log("Something went wrong...");
|
||||
console.dir(err);
|
||||
return;
|
||||
}
|
||||
console.log('\x1b[32m' + data);
|
||||
});
|
||||
console.log('\x1b[32m\x1b[2mby Noobish @ \x1b[4mFMDX.org\x1b[0m');
|
||||
console.log("v" + pjson.version)
|
||||
console.log('\x1b[90m' + '─'.repeat(terminalWidth - 1) + '\x1b[0m');
|
||||
@@ -103,7 +101,7 @@ let timeoutAntenna;
|
||||
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
const sessionMiddleware = session({
|
||||
secret: 'GTce3tN6U8odMwoI',
|
||||
secret: 'GTce3tN6U8odMwoI', // Cool
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
});
|
||||
@@ -155,8 +153,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 +174,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 +201,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 +220,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 +278,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 +288,7 @@ client.on('data', (data) => {
|
||||
});
|
||||
|
||||
client.on('close', () => {
|
||||
pluginsApi.setOutput(null);
|
||||
if(serverConfig.autoShutdown === false) {
|
||||
logWarn('Disconnected from xdrd. Attempting to reconnect.');
|
||||
setTimeout(function () {
|
||||
@@ -319,9 +321,6 @@ app.set('view engine', 'ejs');
|
||||
app.set('views', path.join(__dirname, '../web'));
|
||||
app.use('/', endpoints);
|
||||
|
||||
/**
|
||||
* WEBSOCKET BLOCK
|
||||
*/
|
||||
const tunerLockTracker = new WeakMap();
|
||||
const ipConnectionCounts = new Map(); // Per-IP limit variables
|
||||
const ipLogTimestamps = new Map();
|
||||
@@ -344,7 +343,7 @@ setInterval(() => {
|
||||
|
||||
wss.on('connection', (ws, request) => {
|
||||
const output = serverConfig.xdrd.wirelessConnection ? client : serialport;
|
||||
let clientIp = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
|
||||
let clientIp = request.headers['x-forwarded-for'] || request.socket.remoteAddress;
|
||||
const userCommandHistory = {};
|
||||
const normalizedClientIp = clientIp?.replace(/^::ffff:/, '');
|
||||
|
||||
@@ -358,17 +357,10 @@ wss.on('connection', (ws, request) => {
|
||||
// Per-IP limit connection open
|
||||
if (clientIp) {
|
||||
const isLocalIp = (
|
||||
clientIp === '127.0.0.1' ||
|
||||
clientIp === '::1' ||
|
||||
clientIp === '::ffff:127.0.0.1' ||
|
||||
clientIp.startsWith('192.168.') ||
|
||||
clientIp.startsWith('10.') ||
|
||||
clientIp.startsWith('172.16.')
|
||||
);
|
||||
clientIp === '127.0.0.1' || clientIp === '::1' || clientIp === '::ffff:127.0.0.1' ||
|
||||
clientIp.startsWith('192.168.') || clientIp.startsWith('10.') || clientIp.startsWith('172.16.'));
|
||||
if (!isLocalIp) {
|
||||
if (!ipConnectionCounts.has(clientIp)) {
|
||||
ipConnectionCounts.set(clientIp, 0);
|
||||
}
|
||||
if (!ipConnectionCounts.has(clientIp)) ipConnectionCounts.set(clientIp, 0);
|
||||
const currentCount = ipConnectionCounts.get(clientIp);
|
||||
if (currentCount >= MAX_CONNECTIONS_PER_IP) {
|
||||
ws.close(1008, 'Too many open connections from this IP');
|
||||
@@ -384,7 +376,9 @@ wss.on('connection', (ws, request) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (clientIp !== '::ffff:127.0.0.1' || (request.connection && request.connection.remoteAddress && request.connection.remoteAddress !== '::ffff:127.0.0.1') || (request.headers && request.headers['origin'] && request.headers['origin'].trim() !== '')) {
|
||||
if (clientIp !== '::ffff:127.0.0.1' ||
|
||||
(request.socket && request.socket.remoteAddress && request.socket.remoteAddress !== '::ffff:127.0.0.1') ||
|
||||
(request.headers && request.headers['origin'] && request.headers['origin'].trim() !== '')) {
|
||||
currentUsers++;
|
||||
}
|
||||
|
||||
@@ -395,12 +389,9 @@ wss.on('connection', (ws, request) => {
|
||||
ws.close(1008, 'Banned IP');
|
||||
return;
|
||||
}
|
||||
|
||||
dataHandler.showOnlineUsers(currentUsers);
|
||||
|
||||
if (currentUsers === 1 && serverConfig.autoShutdown === true && serverConfig.xdrd.wirelessConnection) {
|
||||
serverConfig.xdrd.wirelessConnection ? connectToXdrd() : serialport.write('x\n');
|
||||
}
|
||||
if (currentUsers === 1 && serverConfig.autoShutdown === true && serverConfig.xdrd.wirelessConnection) serverConfig.xdrd.wirelessConnection ? connectToXdrd() : serialport.write('x\n');
|
||||
});
|
||||
|
||||
const userCommands = {};
|
||||
@@ -469,7 +460,9 @@ wss.on('connection', (ws, request) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (clientIp !== '::ffff:127.0.0.1' || (request.connection && request.connection.remoteAddress && request.connection.remoteAddress !== '::ffff:127.0.0.1') || (request.headers && request.headers['origin'] && request.headers['origin'].trim() !== '')) {
|
||||
if (clientIp !== '::ffff:127.0.0.1' ||
|
||||
(request.socket && request.socket.remoteAddress && request.socket.remoteAddress !== '::ffff:127.0.0.1') ||
|
||||
(request.headers && request.headers['origin'] && request.headers['origin'].trim() !== '')) {
|
||||
currentUsers--;
|
||||
}
|
||||
dataHandler.showOnlineUsers(currentUsers);
|
||||
@@ -535,7 +528,7 @@ wss.on('connection', (ws, request) => {
|
||||
|
||||
// Additional web socket for using plugins
|
||||
pluginsWss.on('connection', (ws, request) => {
|
||||
const clientIp = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
|
||||
const clientIp = request.headers['x-forwarded-for'] || request.socket.remoteAddress;
|
||||
const userCommandHistory = {};
|
||||
if (serverConfig.webserver.banlist?.includes(clientIp)) {
|
||||
ws.close(1008, 'Banned IP');
|
||||
@@ -573,51 +566,23 @@ pluginsWss.on('connection', (ws, request) => {
|
||||
|
||||
// Websocket register for /text, /audio and /chat paths
|
||||
httpServer.on('upgrade', (request, socket, head) => {
|
||||
if (request.url === '/text') {
|
||||
sessionMiddleware(request, {}, () => {
|
||||
wss.handleUpgrade(request, socket, head, (ws) => {
|
||||
wss.emit('connection', ws, request);
|
||||
});
|
||||
});
|
||||
} else if (request.url === '/audio') {
|
||||
sessionMiddleware(request, {}, () => {
|
||||
audioWss.handleUpgrade(request, socket, head, (ws) => {
|
||||
audioWss.emit('connection', ws, request);
|
||||
});
|
||||
});
|
||||
} else if (request.url === '/chat' && serverConfig.webserver.chatEnabled === true) {
|
||||
sessionMiddleware(request, {}, () => {
|
||||
chatWss.handleUpgrade(request, socket, head, (ws) => {
|
||||
chatWss.emit('connection', ws, request);
|
||||
});
|
||||
});
|
||||
} else if (request.url === '/rds' || request.url === '/rdsspy') {
|
||||
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 = {};
|
||||
const clientIp = request.headers['x-forwarded-for'] || request.socket.remoteAddress;
|
||||
if (serverConfig.webserver.banlist?.includes(clientIp)) {
|
||||
ws.close(1008, 'Banned IP');
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// Anti-spam tracking for each client
|
||||
const userCommands = {};
|
||||
let lastWarn = { time: 0 };
|
||||
var upgradeWss = undefined;
|
||||
if (request.url === '/text') upgradeWss = wss;
|
||||
else if (request.url === '/audio') upgradeWss = audioWss;
|
||||
else if (request.url === '/chat' && serverConfig.webserver.chatEnabled === true) upgradeWss = chatWss;
|
||||
else if (request.url === '/rds' || request.url === '/rdsspy') upgradeWss = rdsWss;
|
||||
else if (request.url === '/data_plugins') upgradeWss = pluginsWss;
|
||||
|
||||
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') {
|
||||
if(upgradeWss) {
|
||||
sessionMiddleware(request, {}, () => {
|
||||
pluginsWss.handleUpgrade(request, socket, head, (ws) => {
|
||||
pluginsWss.emit('connection', ws, request);
|
||||
upgradeWss.handleUpgrade(request, socket, head, (ws) => {
|
||||
upgradeWss.emit('connection', ws, request);
|
||||
});
|
||||
});
|
||||
} else socket.destroy();
|
||||
@@ -648,3 +613,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 };
|
||||
@@ -1,7 +1,6 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const consoleCmd = require('./console');
|
||||
const { serverConfig } = require('./server_config');
|
||||
|
||||
// Function to read all .js files in a directory
|
||||
function readJSFiles(dir) {
|
||||
@@ -11,7 +10,6 @@ function readJSFiles(dir) {
|
||||
|
||||
// Function to parse plugin config from a file
|
||||
function parsePluginConfig(filePath) {
|
||||
const fileContent = fs.readFileSync(filePath, 'utf8');
|
||||
const pluginConfig = {};
|
||||
|
||||
// Assuming pluginConfig is a JavaScript object defined in each .js file
|
||||
@@ -31,9 +29,7 @@ function parsePluginConfig(filePath) {
|
||||
}
|
||||
|
||||
// Check if the destination directory exists, if not, create it
|
||||
if (!fs.existsSync(destinationDir)) {
|
||||
fs.mkdirSync(destinationDir, { recursive: true }); // Create directory recursively
|
||||
}
|
||||
if (!fs.existsSync(destinationDir)) fs.mkdirSync(destinationDir, { recursive: true }); // Create directory recursively
|
||||
|
||||
const destinationFile = path.join(destinationDir, path.basename(sourcePath));
|
||||
|
||||
@@ -41,9 +37,7 @@ function parsePluginConfig(filePath) {
|
||||
if (process.platform !== 'win32') {
|
||||
// On Linux, create a symlink
|
||||
try {
|
||||
if (fs.existsSync(destinationFile)) {
|
||||
fs.unlinkSync(destinationFile); // Remove existing file/symlink
|
||||
}
|
||||
if (fs.existsSync(destinationFile)) fs.unlinkSync(destinationFile); // Remove existing file/symlink
|
||||
fs.symlinkSync(sourcePath, destinationFile);
|
||||
setTimeout(function() {
|
||||
consoleCmd.logInfo(`Plugin ${pluginConfig.name} ${pluginConfig.version} initialized successfully.`);
|
||||
@@ -52,9 +46,7 @@ function parsePluginConfig(filePath) {
|
||||
console.error(`Error creating symlink at ${destinationFile}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error(`Error: frontEndPath is not defined in ${filePath}`);
|
||||
}
|
||||
} else console.error(`Error: frontEndPath is not defined in ${filePath}`);
|
||||
} catch (err) {
|
||||
console.error(`Error parsing plugin config from ${filePath}: ${err.message}`);
|
||||
}
|
||||
@@ -71,9 +63,7 @@ function collectPluginConfigs() {
|
||||
jsFiles.forEach(file => {
|
||||
const filePath = path.join(pluginsDir, file);
|
||||
const config = parsePluginConfig(filePath);
|
||||
if (Object.keys(config).length > 0) {
|
||||
pluginConfigs.push(config);
|
||||
}
|
||||
if (Object.keys(config).length > 0) pluginConfigs.push(config);
|
||||
});
|
||||
|
||||
return pluginConfigs;
|
||||
@@ -81,9 +71,7 @@ function collectPluginConfigs() {
|
||||
|
||||
// Ensure the web/js/plugins directory exists
|
||||
const webJsPluginsDir = path.join(__dirname, '../web/js/plugins');
|
||||
if (!fs.existsSync(webJsPluginsDir)) {
|
||||
fs.mkdirSync(webJsPluginsDir, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(webJsPluginsDir)) fs.mkdirSync(webJsPluginsDir, { recursive: true });
|
||||
|
||||
// Main function to create symlinks/junctions for plugins
|
||||
function createLinks() {
|
||||
@@ -93,13 +81,8 @@ function createLinks() {
|
||||
if (process.platform === 'win32') {
|
||||
// On Windows, create a junction
|
||||
try {
|
||||
if (fs.existsSync(destinationPluginsDir)) {
|
||||
fs.rmSync(destinationPluginsDir, { recursive: true });
|
||||
}
|
||||
if (fs.existsSync(destinationPluginsDir)) fs.rmSync(destinationPluginsDir, { recursive: true });
|
||||
fs.symlinkSync(pluginsDir, destinationPluginsDir, 'junction');
|
||||
setTimeout(function() {
|
||||
//consoleCmd.logInfo(`Plugin ${pluginConfig.name} ${pluginConfig.version} initialized successfully.`);
|
||||
}, 500)
|
||||
} catch (err) {
|
||||
console.error(`Error creating junction at ${destinationPluginsDir}: ${err.message}`);
|
||||
}
|
||||
|
||||
137
server/plugins_api.js
Normal file
137
server/plugins_api.js
Normal 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 { 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
|
||||
};
|
||||
@@ -51,7 +51,7 @@ class RDSDecoder {
|
||||
const group = (blockB >> 12) & 0xF;
|
||||
const version = (blockB >> 11) & 0x1;
|
||||
this.data.tp = Number((blockB >> 10) & 1);
|
||||
this.data.pty = (blockB >> 5) & 0b11111;
|
||||
this.data.pty = (blockB >> 5) & 31;
|
||||
|
||||
if (group === 0) {
|
||||
this.data.ta = (blockB >> 4) & 1;
|
||||
@@ -87,7 +87,7 @@ class RDSDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
if(d_error === 3) return; // Don't risk it
|
||||
if(d_error > 2) return; // Don't risk it
|
||||
|
||||
const idx = blockB & 0x3;
|
||||
|
||||
@@ -99,6 +99,7 @@ class RDSDecoder {
|
||||
this.data.ps = this.ps.join('');
|
||||
this.data.ps_errors = this.ps_errors.join(',');
|
||||
} else if (group === 1 && version === 0) {
|
||||
if(c_error > 2) return;
|
||||
var variant_code = (blockC >> 12) & 0x7;
|
||||
switch (variant_code) {
|
||||
case 0:
|
||||
@@ -120,13 +121,13 @@ class RDSDecoder {
|
||||
this.rt1_to_clear = false;
|
||||
}
|
||||
|
||||
if(c_error !== 3 && multiplier !== 2) {
|
||||
if(c_error < 2 && multiplier !== 2) {
|
||||
this.rt1[idx * multiplier] = String.fromCharCode(blockC >> 8);
|
||||
this.rt1[idx * multiplier + 1] = String.fromCharCode(blockC & 0xFF);
|
||||
this.rt1_errors[idx * multiplier] = error;
|
||||
this.rt1_errors[idx * multiplier + 1] = error;
|
||||
}
|
||||
if(d_error !== 3) {
|
||||
if(d_error < 2) {
|
||||
var offset = (multiplier == 2) ? 0 : 2;
|
||||
this.rt1[idx * multiplier + offset] = String.fromCharCode(blockD >> 8);
|
||||
this.rt1[idx * multiplier + offset + 1] = String.fromCharCode(blockD & 0xFF);
|
||||
|
||||
@@ -90,6 +90,9 @@ let serverConfig = {
|
||||
fmlistAdminOnly: false,
|
||||
fmlistOmid: "",
|
||||
},
|
||||
si47xx: {
|
||||
agcControl: false
|
||||
},
|
||||
tunnel: {
|
||||
enabled: false,
|
||||
username: "",
|
||||
@@ -130,14 +133,10 @@ let serverConfig = {
|
||||
function addMissingFields(target, source) {
|
||||
Object.keys(source).forEach(function(key) {
|
||||
if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) {
|
||||
if (!target[key]) {
|
||||
target[key] = {}; // Create missing object
|
||||
}
|
||||
if (!target[key]) target[key] = {}; // Create missing object
|
||||
addMissingFields(target[key], source[key]); // Recursively add missing fields
|
||||
} else {
|
||||
if (target[key] === undefined) {
|
||||
target[key] = source[key]; // Add missing fields only
|
||||
}
|
||||
if (target[key] === undefined) target[key] = source[key]; // Add missing fields only
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -146,13 +145,9 @@ function addMissingFields(target, source) {
|
||||
function deepMerge(target, source) {
|
||||
Object.keys(source).forEach(function(key) {
|
||||
if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) {
|
||||
if (!target[key] || typeof target[key] !== 'object') {
|
||||
target[key] = {}; // Ensure target[key] is an object before merging
|
||||
}
|
||||
if (!target[key] || typeof target[key] !== 'object') target[key] = {}; // Ensure target[key] is an object before merging
|
||||
deepMerge(target[key], source[key]); // Recursively merge objects
|
||||
} else {
|
||||
target[key] = source[key]; // Overwrite or add the value
|
||||
}
|
||||
} else target[key] = source[key]; // Overwrite or add the value
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@ checkFFmpeg().then((ffmpegPath) => {
|
||||
logInfo(`${consoleLogTitle} Using ${ffmpegPath === 'ffmpeg' ? 'system-installed FFmpeg' : 'ffmpeg-static'}`);
|
||||
logInfo(`${consoleLogTitle} Starting audio stream on device: \x1b[35m${serverConfig.audio.audioDevice}\x1b[0m`);
|
||||
|
||||
const sampleRate = Number(this?.Server?.SampleRate || serverConfig.audio.sampleRate || 44100) + Number(serverConfig.audio.samplerateOffset || 0);
|
||||
const sampleRate = Number(serverConfig.audio.sampleRate || 44100) + Number(serverConfig.audio.samplerateOffset || 0);
|
||||
|
||||
const channels = Number(this?.Server?.Channels || serverConfig.audio.audioChannels || 2);
|
||||
const channels = Number(serverConfig.audio.audioChannels || 2);
|
||||
|
||||
let ffmpeg = null;
|
||||
let restartTimer = null;
|
||||
@@ -48,7 +48,7 @@ checkFFmpeg().then((ffmpegPath) => {
|
||||
|
||||
...inputArgs,
|
||||
|
||||
"-thread_queue_size", "1024",
|
||||
"-thread_queue_size", "1536",
|
||||
"-ar", String(sampleRate),
|
||||
"-ac", String(channels),
|
||||
|
||||
|
||||
@@ -28,9 +28,7 @@ function parseAudioDevice(options, callback) {
|
||||
const matches = (data.match(regex) || []).map(match => 'hw:' + match.replace(/\s+/g, '').slice(1, -1));
|
||||
|
||||
matches.forEach(match => {
|
||||
if (typeof match === 'string') {
|
||||
audioDevices.push({ name: match });
|
||||
}
|
||||
if (typeof match === 'string') audioDevices.push({ name: match });
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`Error reading file: ${err.message}`);
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
const WebSocket = require('ws');
|
||||
const { serverConfig } = require('../server_config');
|
||||
const { audio_pipe } = require('./index.js');
|
||||
const { PassThrough } = require('stream');
|
||||
|
||||
function createAudioServer() {
|
||||
const audioWss = new WebSocket.Server({ noServer: true });
|
||||
|
||||
audioWss.on('connection', (ws, request) => {
|
||||
const clientIp =
|
||||
request.headers['x-forwarded-for'] ||
|
||||
request.connection.remoteAddress;
|
||||
const clientIp = request.headers['x-forwarded-for'] || request.socket.remoteAddress;
|
||||
|
||||
if (serverConfig.webserver.banlist?.includes(clientIp)) {
|
||||
ws.close(1008, 'Banned IP');
|
||||
@@ -19,12 +16,7 @@ function createAudioServer() {
|
||||
|
||||
audio_pipe.on('data', (chunk) => {
|
||||
audioWss.clients.forEach((client) => {
|
||||
if (client.readyState === WebSocket.OPEN) {
|
||||
client.send(chunk, {
|
||||
binary: true,
|
||||
compress: false
|
||||
});
|
||||
}
|
||||
if (client.readyState === WebSocket.OPEN) client.send(chunk, {binary: true, compress: false });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
84
server/tuner_profiles.js
Normal file
84
server/tuner_profiles.js
Normal 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;
|
||||
@@ -15,12 +15,10 @@ const fileExists = path => new Promise(resolve => fs.access(path, fs.constants.F
|
||||
async function connect() {
|
||||
if (serverConfig.tunnel?.enabled === true) {
|
||||
const librariesDir = path.resolve(__dirname, '../libraries');
|
||||
if (!await fileExists(librariesDir)) {
|
||||
await fs.mkdir(librariesDir);
|
||||
}
|
||||
if (!await fileExists(librariesDir)) await fs.mkdir(librariesDir);
|
||||
const frpcPath = path.resolve(librariesDir, 'frpc' + (os.platform() === 'win32' ? '.exe' : ''));
|
||||
if (!await fileExists(frpcPath)) {
|
||||
logInfo('frpc binary required for tunnel is not available. Downloading now...');
|
||||
logInfo('frpc binary, required for tunnel is not available. Downloading now...');
|
||||
const frpcFileName = `frpc_${os.platform}_${os.arch}` + (os.platform() === 'win32' ? '.exe' : '');
|
||||
|
||||
try {
|
||||
@@ -33,9 +31,7 @@ async function connect() {
|
||||
return;
|
||||
}
|
||||
logInfo('Downloading of frpc is completed.')
|
||||
if (os.platform() === 'linux' || os.platform() === 'darwin') {
|
||||
await fs.chmod(frpcPath, 0o770);
|
||||
}
|
||||
if (os.platform() === 'linux' || os.platform() === 'darwin') await fs.chmod(frpcPath, 0o770);
|
||||
}
|
||||
const cfg = ejs.render(frpcConfigTemplate, {
|
||||
cfg: serverConfig.tunnel,
|
||||
@@ -73,7 +69,6 @@ async function connect() {
|
||||
child.on('close', (code) => {
|
||||
logInfo(`Tunnel process exited with code ${code}`);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@ const { serverConfig } = require('./server_config');
|
||||
const consoleCmd = require('./console');
|
||||
|
||||
let localDb = {};
|
||||
let nextLocalDbUpdate = 0;
|
||||
const localDbUpdateInterval = 7 * 24 * 60 * 60 * 1000; // 7-day database update interval
|
||||
let awaitingTxInfo = true;
|
||||
let lastFetchTime = 0;
|
||||
let piFreqIndex = {}; // Indexing for speedier PI+Freq combinations
|
||||
const fetchInterval = 1000;
|
||||
@@ -71,7 +74,7 @@ if (serverConfig.identification.gpsMode) {
|
||||
// Function to build local TX database from FMDX Maps endpoint.
|
||||
async function buildTxDatabase() {
|
||||
if (Latitude.length > 0 && Longitude.length > 0) {
|
||||
let awaitingTxInfo = true;
|
||||
awaitingTxInfo = true;
|
||||
while (awaitingTxInfo) {
|
||||
try {
|
||||
consoleCmd.logInfo('Fetching transmitter database...');
|
||||
@@ -85,6 +88,7 @@ async function buildTxDatabase() {
|
||||
localDb = await response.json();
|
||||
buildPiFreqIndex();
|
||||
consoleCmd.logInfo('Transmitter database successfully loaded.');
|
||||
nextLocalDbUpdate = Date.now() + localDbUpdateInterval;
|
||||
awaitingTxInfo = false;
|
||||
} catch (error) {
|
||||
consoleCmd.logError("Failed to fetch transmitter database:", error);
|
||||
@@ -92,9 +96,7 @@ async function buildTxDatabase() {
|
||||
consoleCmd.logInfo('Retrying transmitter database download...');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
consoleCmd.logInfo('Server latitude and longitude must be set before transmitter database can be built');
|
||||
}
|
||||
} else consoleCmd.logInfo('Server latitude and longitude must be set before transmitter database can be built');
|
||||
}
|
||||
|
||||
// Function to build index map of PI+Freq combinations
|
||||
@@ -158,8 +160,7 @@ function getStateBoundingBox(coordinates) {
|
||||
|
||||
// Function to check if a city (lat, lon) falls within the bounding box of a state
|
||||
function isCityInState(lat, lon, boundingBox) {
|
||||
return lat >= boundingBox.minLat && lat <= boundingBox.maxLat &&
|
||||
lon >= boundingBox.minLon && lon <= boundingBox.maxLon;
|
||||
return lat >= boundingBox.minLat && lat <= boundingBox.maxLat && lon >= boundingBox.minLon && lon <= boundingBox.maxLon;
|
||||
}
|
||||
|
||||
// Function to check if a city (lat, lon) is inside any US state and return the state name
|
||||
@@ -227,11 +228,8 @@ function evaluateStation(station, esMode) {
|
||||
let extraWeight = erp > weightedErp && station.distanceKm <= weightDistance ? 0.3 : 0;
|
||||
let score = 0;
|
||||
// If ERP is 1W, use a simpler formula to avoid zero-scoring.
|
||||
if (erp === 0.001) {
|
||||
score = erp / station.distanceKm;
|
||||
} else {
|
||||
score = ((10 * (Math.log10(erp * 1000))) / weightDistance) + extraWeight;
|
||||
}
|
||||
if (erp === 0.001) score = erp / station.distanceKm;
|
||||
else score = ((10 * (Math.log10(erp * 1000))) / weightDistance) + extraWeight;
|
||||
return score;
|
||||
}
|
||||
|
||||
@@ -240,6 +238,10 @@ async function fetchTx(freq, piCode, rdsPs) {
|
||||
let match = null;
|
||||
let multiMatches = [];
|
||||
const now = Date.now();
|
||||
if (now > nextLocalDbUpdate && !awaitingTxInfo) {
|
||||
consoleCmd.logInfo('Time to update transmitter database.');
|
||||
buildTxDatabase();
|
||||
}
|
||||
freq = parseFloat(freq);
|
||||
|
||||
if (
|
||||
@@ -265,6 +267,15 @@ async function fetchTx(freq, piCode, rdsPs) {
|
||||
stations: [station]
|
||||
}));
|
||||
|
||||
if (filteredLocations.length > 1) {
|
||||
const extraFilteredLocations = filteredLocations.map(locData => ({
|
||||
...locData,
|
||||
stations: locData.stations.filter(station => (station.ps.toLowerCase() === rdsPs.replace(/ /g, '_').toLowerCase()))
|
||||
})).filter(locData => locData.stations.length > 0);
|
||||
|
||||
if (extraFilteredLocations.length > 0) filteredLocations = extraFilteredLocations;
|
||||
}
|
||||
|
||||
// Only check PS if we have more than one match.
|
||||
if (filteredLocations.length > 1) {
|
||||
filteredLocations = filteredLocations.map(locData => ({
|
||||
@@ -287,12 +298,8 @@ async function fetchTx(freq, piCode, rdsPs) {
|
||||
loc => loc.distanceKm < 700 && loc.erp >= 10
|
||||
);
|
||||
let esMode = false;
|
||||
if (!tropoPriority) {
|
||||
esMode = checkEs();
|
||||
}
|
||||
for (let loc of filteredLocations) {
|
||||
loc.score = evaluateStation(loc, esMode);
|
||||
}
|
||||
if (!tropoPriority) esMode = checkEs();
|
||||
for (let loc of filteredLocations) loc.score = evaluateStation(loc, esMode);
|
||||
// Sort by score in descending order
|
||||
filteredLocations.sort((a, b) => b.score - a.score);
|
||||
match = filteredLocations[0];
|
||||
@@ -306,11 +313,9 @@ async function fetchTx(freq, piCode, rdsPs) {
|
||||
}
|
||||
|
||||
if (match) {
|
||||
if (match.itu === 'USA') {
|
||||
if (match.itu == 'USA') { // Also known as Dumbfuckinstan. they should not go to hell, but hell+ (it is NOT better)
|
||||
const state = getStateForCoordinates(match.lat, match.lon);
|
||||
if (state) {
|
||||
match.state = state; // Add state to matchingCity
|
||||
}
|
||||
if (state) match.state = state; // Add state to matchingCity
|
||||
}
|
||||
const result = {
|
||||
station: match.detectedByPireg
|
||||
@@ -343,9 +348,7 @@ function checkEs() {
|
||||
const now = Date.now();
|
||||
const url = "https://fmdx.org/includes/tools/get_muf.php";
|
||||
|
||||
if (esSwitchCache.lastCheck && now - esSwitchCache.lastCheck < esFetchInterval) {
|
||||
return esSwitchCache.esSwitch;
|
||||
}
|
||||
if (esSwitchCache.lastCheck && now - esSwitchCache.lastCheck < esFetchInterval) return esSwitchCache.esSwitch;
|
||||
|
||||
if (Latitude > 20) {
|
||||
esSwitchCache.lastCheck = now;
|
||||
@@ -372,15 +375,12 @@ function haversine(lat1, lon1, lat2, lon2) {
|
||||
const R = 6371;
|
||||
const dLat = deg2rad(lat2 - lat1);
|
||||
const dLon = deg2rad(lon2 - lon1);
|
||||
const a =
|
||||
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||||
Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
||||
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
||||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
const distance = R * c;
|
||||
|
||||
const y = Math.sin(dLon) * Math.cos(deg2rad(lat2));
|
||||
const x = Math.cos(deg2rad(lat1)) * Math.sin(deg2rad(lat2)) -
|
||||
Math.sin(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.cos(dLon);
|
||||
const x = Math.cos(deg2rad(lat1)) * Math.sin(deg2rad(lat2)) - Math.sin(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.cos(dLon);
|
||||
const azimuth = Math.atan2(y, x);
|
||||
const azimuthDegrees = (azimuth * 180 / Math.PI + 360) % 360;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
%>
|
||||
|
||||
|
||||
@@ -293,8 +293,8 @@ pre {
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.flex-container.contains-dropdown {
|
||||
z-index: 999;
|
||||
.contains-dropdown {
|
||||
z-index: 990;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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'));
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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()}]`;
|
||||
@@ -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>
|
||||
<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: [
|
||||
{ value: 'tef', label: 'TEF668x / TEA685x' },
|
||||
{ value: 'xdr', label: 'XDR (F1HD / S10HDiP)' },
|
||||
{ value: 'sdr', label: 'SDR (RTL-SDR / AirSpy)' },
|
||||
{ value: 'other', label: 'Other' }
|
||||
]
|
||||
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> </p>
|
||||
<p class="text-gray">Choose your desired <strong>serial port</strong><br> </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' },
|
||||
|
||||
@@ -47,14 +47,12 @@
|
||||
|
||||
<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">
|
||||
<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: [
|
||||
{ value: 'tef', label: 'TEF668x / TEA685x' },
|
||||
{ value: 'xdr', label: 'XDR (F1HD / S10HDiP)' },
|
||||
{ value: 'sdr', label: 'SDR (RTL-SDR / AirSpy)' },
|
||||
{ value: 'other', label: 'Other' }
|
||||
]
|
||||
options: tunerProfiles.map(profile => ({
|
||||
value: profile.id,
|
||||
label: profile.label
|
||||
}))
|
||||
}) %><br>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
@@ -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}`
|
||||
|
||||
Reference in New Issue
Block a user