diff --git a/package-lock.json b/package-lock.json index 81cf1d5..4efddd8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -337,190 +337,939 @@ "node": ">=6.9.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/darwin-arm64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ - "arm64" + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@gfx/zopfli": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@gfx/zopfli/-/zopfli-1.0.15.tgz", + "integrity": "sha512-7mBgpi7UD82fsff5ThQKet0uBTl4BYerQuc+/qA1ELTwWEiIedRTcD3JgiUu9wwZ2kytW8JOb165rSdAt8PfcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/pluginutils": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", + "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "cpu": [ + "loong64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } + "linux" + ] }, - "node_modules/@gfx/zopfli": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/@gfx/zopfli/-/zopfli-1.0.15.tgz", - "integrity": "sha512-7mBgpi7UD82fsff5ThQKet0uBTl4BYerQuc+/qA1ELTwWEiIedRTcD3JgiUu9wwZ2kytW8JOb165rSdAt8PfcQ==", + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "cpu": [ + "loong64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "base64-js": "^1.3.0" - }, - "engines": { - "node": ">= 8" - } + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "cpu": [ + "ppc64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "cpu": [ + "ppc64" + ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "cpu": [ + "riscv64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "engines": { - "node": ">=6.0.0" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "cpu": [ + "riscv64" + ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "cpu": [ + "s390x" + ], "dev": true, - "license": "MIT" + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "cpu": [ + "x64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "cpu": [ + "x64" + ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "engines": { - "node": ">= 8" - } + "optional": true, + "os": [ + "openbsd" + ] }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } + "optional": true, + "os": [ + "openharmony" + ] }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@rollup/pluginutils": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", - "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@rollup/pluginutils/node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", + "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@rollup/rollup-darwin-arm64": { + "node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", - "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", + "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", "cpu": [ - "arm64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "darwin" + "win32" ] }, "node_modules/@sveltejs/acorn-typescript": { @@ -3928,21 +4677,6 @@ "dev": true, "license": "ISC" }, - "node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, "node_modules/zimmerframe": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", diff --git a/src/App.tsx b/src/App.tsx index 4efc38b..062541a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -144,28 +144,29 @@ export function App() { ? await getDeviceInfoFast() : await getDeviceInfo() const info = parseESP800(raw) - const wsComm = info['webcommunication']?.trim() ?? '' - const parts = wsComm.split(':') - const asyncMode = parts[0]?.trim() === 'Async' - const wsPort = parts[1]?.trim() ?? '80' - const wsIp = parts[2]?.trim() ?? httpHost.split(':')[0] + const asyncMode = info['WebCommunication']?.trim() === 'Asynchronous' + const wsPort = info['WebSocketPort']?.trim() || '80' + const wsIp = info['WebSocketIP']?.trim() || httpHost.split(':')[0] const wsHost = asyncMode ? `${httpHost}/ws` : wsPort === '80' ? wsIp : `${wsIp}:${wsPort}` cachedWsHost.current = wsHost cachedHostFailures.current = 0 // Only refresh ESP info on a fresh probe. - const axes = parseInt(info['axis'] ?? '3', 10) + const axes = (info['Axisletters'] ?? '').length setEspInfo({ - version: info['FW version']?.trim() ?? '', - hostname: info['hostname']?.trim() ?? httpHost, - authentication: info['authentication']?.trim() === 'yes', + version: info['FWVersion']?.trim() ?? '', + hostname: info['HostName']?.trim() ?? httpHost, + authentication: info['Authentication']?.trim() === 'Enabled', asyncMode, wsPort: parseInt(wsPort, 10), wsIp, - axes: isNaN(axes) ? 3 : axes, - primarySd: info['primary sd']?.trim() ?? '/sd/', - secondarySd: info['secondary sd']?.trim() ?? '/ext/', + axes: axes || 3, + // No JSON equivalent for these (the legacy plain-text response's + // "primary sd"/"secondary sd" fields) -- same defaults the old + // parse already fell back to whenever they were absent/unparseable. + primarySd: '/sd/', + secondarySd: '/ext/', }) return { wsHost, info } } diff --git a/src/lib/http.ts b/src/lib/http.ts index af05017..1ee9e02 100644 --- a/src/lib/http.ts +++ b/src/lib/http.ts @@ -70,7 +70,7 @@ export const sendSilent = (cmd: string) => get('/command_silent', { plain: cmd }) export const getDeviceInfoFast = () => - get('/command', { plain: '[ESP800]' }, 4000) + get('/command', { plain: '[ESP800]json=yes' }, 4000) export function listFiles(path: string, fs: 'sd' | 'local' = 'sd'): Promise { return serialize(async () => { @@ -214,7 +214,7 @@ export async function saveFileContent( await uploadFile(path, file, fs) } -export const getDeviceInfo = () => sendCommand('[ESP800]') +export const getDeviceInfo = () => sendCommand('[ESP800]json=yes') export function uploadFirmware( file: File, diff --git a/src/lib/parser.ts b/src/lib/parser.ts index a3cca36..ea1f4fc 100644 --- a/src/lib/parser.ts +++ b/src/lib/parser.ts @@ -63,14 +63,27 @@ function parsePos(str: string): Position { return { x: x ?? 0, y: y ?? 0, z: z ?? 0, a, b, c } } +// [ESP800]json=yes wraps the same fields the legacy "FW version:... # +// FW target:... # ..." plain-text response carried in {cmd,status,data} +// instead -- see WifiConfig.cpp's showFwInfoJSON()/wasm/FwInfo.cpp. Field +// names change shape too (e.g. "webcommunication:Sync:81:127.0.0.1" +// becomes separate WebCommunication/WebSocketPort/WebSocketIP fields, and +// "authentication:yes" becomes "Authentication":"Enabled") -- see +// App.tsx's resolveWsHost(), the only real consumer, for how those are +// read now. export function parseESP800(raw: string): Record { + let parsed: unknown + try { + parsed = JSON.parse(raw) + } catch { + return {} + } + const data = (parsed as { data?: Record })?.data + if (!data) return {} const result: Record = {} - raw.split('#').forEach(part => { - const idx = part.indexOf(':') - if (idx > -1) { - result[part.slice(0, idx).trim()] = part.slice(idx + 1).trim() - } - }) + for (const [key, value] of Object.entries(data)) { + result[key] = String(value) + } return result } diff --git a/src/main.tsx b/src/main.tsx index 9bf2aa5..83c9018 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -5,12 +5,19 @@ import { App } from './App' import './store/terminal' import './lib/jogWatchdog' import { disconnect as disconnectWs } from './lib/ws' +import { installWasmBridgeIfActive } from './wasmBridge' if (import.meta.env.VITE_DEMO_MODE) { const { installDemoMode } = await import('./demo') installDemoMode() } +// Not gated by import.meta.env, unlike installDemoMode() above -- this must +// survive in every build mode (including the real `build:esp32` artifact), +// since it's how a stock build detects it's running inside the FluidNC WASM +// demo at runtime. See wasmBridge/index.ts. +installWasmBridgeIfActive() + window.addEventListener('pagehide', () => { disconnectWs() }) // Suppress expected connection errors diff --git a/src/wasmBridge/WasmBridgeWebSocket.ts b/src/wasmBridge/WasmBridgeWebSocket.ts new file mode 100644 index 0000000..f460557 --- /dev/null +++ b/src/wasmBridge/WasmBridgeWebSocket.ts @@ -0,0 +1,109 @@ +// A WebSocket-shaped class backed by the WASM demo's postMessage bridge +// instead of a real network socket -- installed as `window.WebSocket` (see +// index.ts) so src/lib/ws.ts works completely unmodified, the same way it +// talks to a real FluidNC WebSocket. Modeled directly on src/demo/ +// wsSimulator.ts's FakeWebSocket (same public surface, since that's what +// ws.ts's `new WebSocket(url, 'arduino')` call needs), but instead of +// simulating a machine in JS, it relays to a real compiled FluidNC instance +// via shimTransport's postMessage channel. +// +// On real hardware, /command (src/lib/http.ts) and this WebSocket are +// genuinely independent connections, each answered by its own Channel +// object, so their response streams never cross. Here they both share +// the one physical ShimChannel, so demo/index.html serializes every send +// -- both /command's 'fluidnc-shim-command' RPC and this class's own +// raw sends below -- through one central queue (see its deliverShimLine() +// /maybeStartNextShimSend()), so only one thing is ever unacknowledged on +// the shim at a time and nothing can misattribute another's ok/error +// line. (An earlier version only queued /command calls against each +// other, on the theory that a raw send here couldn't be affected either +// way -- that turned out to be wrong: FluidNC's output for one call can +// arrive split across more than one output event, leaving a gap where a +// second queued /command could start and steal this class's own ok/error +// line. Send() below still posts straight to the shim -- the ordering +// guarantee now lives entirely in demo/index.html's queue instead.) +import { sendToShim, addShimLineListener } from './shimTransport' + +export class WasmBridgeWebSocket extends EventTarget { + static readonly CONNECTING = 0 + static readonly OPEN = 1 + static readonly CLOSING = 2 + static readonly CLOSED = 3 + + readonly CONNECTING = 0 + readonly OPEN = 1 + readonly CLOSING = 2 + readonly CLOSED = 3 + + readyState: number = 0 + binaryType: BinaryType = 'arraybuffer' + readonly url: string + readonly protocol: string + readonly bufferedAmount = 0 + readonly extensions = '' + + onopen: ((e: Event) => void) | null = null + onclose: ((e: CloseEvent) => void) | null = null + onerror: ((e: Event) => void) | null = null + onmessage: ((e: MessageEvent) => void) | null = null + + private unsubscribe: (() => void) | null = null + + constructor(url: string, protocols?: string | string[]) { + super() + this.url = url + this.protocol = Array.isArray(protocols) ? (protocols[0] ?? '') : (protocols ?? '') + // Deferred rather than synchronous so callers that set onopen/onmessage + // right after `new WebSocket(...)` (as ws.ts does) don't miss the open + // event -- matches a real WebSocket's inherently async connect. + setTimeout(() => this._open(), 0) + } + + private _open() { + this.readyState = 1 + // isJson is irrelevant here: whether a line came from a [JSON:...] + // reassembly or not, ws.ts wants the same thing real hardware's + // WSChannel would have sent it -- plain unwrapped text. + this.unsubscribe = addShimLineListener((line) => { + const ev = new MessageEvent('message', { data: `${line}\n` }) + this.onmessage?.(ev) + this.dispatchEvent(ev) + }) + const ev = new Event('open') + this.onopen?.(ev) + this.dispatchEvent(ev) + } + + send(data: string | ArrayBuffer | ArrayBufferView | Blob): void { + if (this.readyState !== 1) return + + if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) { + const buf = + data instanceof ArrayBuffer + ? new Uint8Array(data) + : new Uint8Array((data as ArrayBufferView).buffer, (data as ArrayBufferView).byteOffset, (data as ArrayBufferView).byteLength) + // Realtime bytes ('?', ctrl-X, ...) -- ws.ts sends these as a single + // byte, same as it would write to a real socket. sendToShim() takes a + // string (emscripten's cwrap marshals it as UTF-8), and every + // realtime byte FluidNC defines is single-byte-UTF8-safe. + sendToShim(String.fromCharCode(...buf)) + return + } + + if (typeof data === 'string') { + sendToShim(data) + } + } + + close(code?: number, reason?: string) { + if (this.readyState === 3) return + this.readyState = 3 + if (this.unsubscribe) { + this.unsubscribe() + this.unsubscribe = null + } + const ev = new CloseEvent('close', { wasClean: true, code: code ?? 1000, reason: reason ?? '' }) + this.onclose?.(ev) + this.dispatchEvent(ev) + } +} diff --git a/src/wasmBridge/commandBridge.ts b/src/wasmBridge/commandBridge.ts new file mode 100644 index 0000000..67de51d --- /dev/null +++ b/src/wasmBridge/commandBridge.ts @@ -0,0 +1,18 @@ +// Request/response command sends for the wasm bridge's HTTP-shaped surface +// (src/lib/http.ts's sendCommand/sendSilent/getDeviceInfo(Fast), which real +// hardware answers over its /command endpoint -- see WebCommands.cpp / +// WebUIServer.cpp). That handler isn't compiled into the wasm build at all +// (WebUI/ is excluded -- see platformio.ini's [env:wasm] build_src_filter), +// so every command here is forwarded through the same shim channel +// WasmBridgeWebSocket reads from, using shimTransport's sendShimCommand() +// (demo/index.html collects the response and queues concurrent sends +// centrally, so this doesn't need to serialize them itself anymore). +// [ESP800] used to be spoofed locally here (its real handler lives in +// WebUI/WifiConfig.cpp, also excluded, and needs WiFi/WebUI_Server APIs +// that don't exist in this build) -- wasm/FwInfo.cpp now provides a real, +// wasm-specific handler instead, so it's just another command. +import { sendShimCommand } from './shimTransport' + +export function sendBridgeCommand(cmd: string): Promise { + return sendShimCommand(cmd) +} diff --git a/src/wasmBridge/httpBridge.ts b/src/wasmBridge/httpBridge.ts new file mode 100644 index 0000000..f675f9e --- /dev/null +++ b/src/wasmBridge/httpBridge.ts @@ -0,0 +1,251 @@ +// fetch()/XMLHttpRequest interception for the wasm bridge, matching +// src/lib/http.ts's real endpoints (/command, /command_silent, /upload, +// /files, and direct /sd, /localfs, /ext downloads). Modeled directly on +// src/demo/httpSimulator.ts (same URL matching), but backs file operations +// with the real wasm filesystem via shimTransport's fsRequest() instead of +// an in-memory JS simulation, and commands via commandBridge.ts instead of +// canned responses. +import { fsRequest } from './shimTransport' +import { sendBridgeCommand } from './commandBridge' + +type Root = 'native_sd' | 'native_localfs' + +function rootForPath(p: string): Root { + return p === '/upload' ? 'native_sd' : 'native_localfs' +} + +function joinPath(dir: string, name: string): string { + return (dir.endsWith('/') ? dir : `${dir}/`) + name +} + +function ok(body: string, type = 'text/plain'): Response { + return new Response(body, { status: 200, headers: { 'Content-Type': type } }) +} + +// Manual parsing instead of `new URL(href, location.href)`: resolving a +// relative reference against a blob: base throws ("Failed to construct +// 'URL': Invalid URL") -- the same relative-resolution quirk that broke +// Router.tsx's `location.href = ...` navigation elsewhere in this project, +// now also hitting URL construction. It's compounded by a real FigUI bug: +// src/App.tsx's attemptConnect() calls `setBase(\`http://${window.location +// .host}\`)`, and window.location.host is empty inside this blob: iframe, +// so requests end up as "http:///command?..." -- which the URL parser +// reads as host="command", path="/", not what was intended. Since every +// request we care about targets one of a small set of known endpoints, +// recovering the real path+query by finding that endpoint name in the raw +// string sidesteps both problems: it needs no base at all, and it isn't +// fooled by the mis-parsed-host case either. +const KNOWN_PATH_RE = /\/(command_silent|command|upload|files|localfs\/|sd(?:\/|(?=\?)|$))/ + +function extractRequestUrl(href: string): { pathname: string; searchParams: URLSearchParams } | null { + const m = KNOWN_PATH_RE.exec(href) + if (!m) return null + const rest = href.slice(m.index) + const q = rest.indexOf('?') + const pathname = q >= 0 ? rest.slice(0, q) : rest + const searchParams = new URLSearchParams(q >= 0 ? rest.slice(q + 1) : '') + return { pathname, searchParams } +} + +function errorResponse(message: string, status = 500): Response { + return new Response(message, { status }) +} + +// fluidnc_fs_list() (see wasm/wasm_fs_bridge.cpp) emits {"name","size" +// (JSON number, -1 for dirs),"datetime"}; FigUI's http.ts::listFiles() +// expects size as a *string*, checking `f.size === '-1'` for the directory +// sentinel -- matching real FluidNC's FileCommands.cpp wire convention. +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function reshapeListing(raw: any, path: string): unknown { + const files = (raw.files ?? []).map((f: { name: string; size: number; datetime?: string }) => ({ + name: f.name, + shortname: f.name, + size: f.size < 0 ? '-1' : String(f.size), + isDir: f.size < 0, + datetime: f.datetime ?? '', + })) + return { + files, + path: raw.path ?? path, + total: raw.total, + used: raw.used, + occupation: raw.occupation, + status: raw.status ?? 'Ok', + } +} + +async function handleFsAction(root: Root, params: URLSearchParams): Promise { + const action = params.get('action') ?? 'list' + const dir = params.get('path') ?? '/' + const filename = params.get('filename') ?? '' + + try { + switch (action) { + case 'list': + break + case 'delete': + await fsRequest('delete', { root, path: joinPath(dir, filename) }) + break + case 'deletedir': + await fsRequest('deletedir', { root, path: joinPath(dir, filename) }) + break + case 'createdir': + await fsRequest('mkdir', { root, path: joinPath(dir, filename) }) + break + // 'rename' isn't implemented -- no fluidnc_fs_rename() bridge + // function exists yet (same gap as WebUI-mm's wasmBridgeFileTransport + // .ts). Falls through to the default below. + default: + return errorResponse(`Unsupported file operation in wasm demo: ${action}`, 400) + } + const listing = await fsRequest('list', { root, path: dir }) + return ok(JSON.stringify(reshapeListing(listing, dir)), 'application/json') + } catch (e) { + return errorResponse(String(e)) + } +} + +async function handleUpload(root: Root, params: URLSearchParams, body: FormData): Promise { + const dir = params.get('path') ?? '/' + const writes: Promise[] = [] + body.forEach((val, key) => { + if (key === 'myfile[]' && val instanceof File) { + writes.push(val.text().then((content) => fsRequest('write', { root, path: joinPath(dir, val.name), content }))) + } + }) + await Promise.all(writes) + const listing = await fsRequest('list', { root, path: dir }) + return ok(JSON.stringify(reshapeListing(listing, dir)), 'application/json') +} + +async function handleFileDownload(pathname: string): Promise { + const isSd = pathname === '/sd' || pathname.startsWith('/sd/') + const root: Root = isSd ? 'native_sd' : 'native_localfs' + const path = isSd ? pathname.slice(3) || '/' : pathname + try { + const content = await fsRequest('read', { root, path }) + return ok(content as string) + } catch { + return new Response('Not found', { status: 404 }) + } +} + +export function installFetchInterceptor(): void { + const orig = window.fetch.bind(window) + + window.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise => { + const href = typeof input === 'string' ? input : input instanceof URL ? input.href : (input as Request).url + const extracted = extractRequestUrl(href) + if (!extracted) return orig(input, init) + const { pathname: p, searchParams } = extracted + const m = (init?.method ?? 'GET').toUpperCase() + + if (p === '/command' || p === '/command_silent') { + const plain = searchParams.get('plain') ?? '' + try { + return ok(await sendBridgeCommand(plain)) + } catch (e) { + return errorResponse(String(e)) + } + } + + if (p === '/upload' || p === '/files') { + const root = rootForPath(p) + if (m === 'POST' && init?.body instanceof FormData) { + return handleUpload(root, searchParams, init.body) + } + return handleFsAction(root, searchParams) + } + + // /ext isn't backed by anything in the wasm build (no second SD-like + // mount exists there), so it's left unintercepted -- falls through to + // the original fetch, which will fail the same way an unhandled path + // would on real hardware with nothing mounted there. + if (p === '/sd' || p.startsWith('/sd/') || p.startsWith('/localfs/')) { + return handleFileDownload(p) + } + + return orig(input, init) + } +} + +// XHR intercept for uploadFile() (progress-tracked uploads) -- see +// src/lib/http.ts. uploadFirmware() posts to /updatefw, which isn't +// intercepted: there's no real flashing to simulate in a wasm demo, so it's +// left to fail like any other unhandled path, same as demo mode leaves it. +export function installXhrInterceptor(): void { + const Orig = window.XMLHttpRequest + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(window as any).XMLHttpRequest = function InterceptedXHR() { + const real = new Orig() + let intercept = false + let interceptRoot: Root = 'native_sd' + let storedOnload: ((e: ProgressEvent) => void) | null = null + const fakeUpload = { onprogress: null as ((e: ProgressEvent) => void) | null } + + return new Proxy(real, { + get(target, prop) { + if (intercept && prop === 'status') return 200 + if (intercept && prop === 'readyState') return 4 + if (intercept && prop === 'upload') return fakeUpload + + if (prop === 'open') + return (method: string, url: string | URL, ...rest: unknown[]) => { + // See extractRequestUrl()'s comment above -- same blob:-base + // and empty-window.location.host issues apply here. + const path = extractRequestUrl(String(url))?.pathname ?? '' + intercept = path === '/upload' || path === '/files' + interceptRoot = path === '/upload' ? 'native_sd' : 'native_localfs' + if (!intercept) real.open.call(real, method, url, ...(rest as [boolean, string?, string?])) + } + + if (prop === 'send') + return (body?: unknown) => { + if (intercept) { + const fd = body instanceof FormData ? body : null + const writes: Promise[] = [] + if (fd) { + const dir = (fd.get('path') as string) ?? '/' + fd.forEach((val, key) => { + if (key === 'myfile[]' && val instanceof File) { + writes.push( + val.text().then((content) => fsRequest('write', { root: interceptRoot, path: joinPath(dir, val.name), content })) + ) + } + }) + } + Promise.all(writes) + .catch(() => {}) + .then(() => storedOnload?.(new ProgressEvent('load', { loaded: 100, total: 100 }))) + } else { + real.send.call(real, body as XMLHttpRequestBodyInit | Document | null | undefined) + } + } + + if (prop === 'setRequestHeader') + return (name: string, value: string) => { + if (!intercept) real.setRequestHeader.call(real, name, value) + } + + if (prop === 'abort') return () => real.abort.call(real) + + const val = Reflect.get(target, prop, target) + return typeof val === 'function' ? val.bind(target) : val + }, + + set(target, prop, value) { + if (intercept && prop === 'onload') { + storedOnload = value + return true + } + try { + Reflect.set(target, prop, value, target) + } catch { + /* read-only accessor */ + } + return true + }, + }) + } +} diff --git a/src/wasmBridge/index.ts b/src/wasmBridge/index.ts new file mode 100644 index 0000000..5f58b37 --- /dev/null +++ b/src/wasmBridge/index.ts @@ -0,0 +1,23 @@ +import { isWasmBridgeActive } from './shimTransport' +import { WasmBridgeWebSocket } from './WasmBridgeWebSocket' +import { installFetchInterceptor, installXhrInterceptor } from './httpBridge' + +// Mirrors src/demo/index.ts's installDemoMode() (replace window.WebSocket, +// intercept fetch/XHR), but gated on a runtime marker (see +// shimTransport.ts's isWasmBridgeActive()) instead of the build-time +// VITE_DEMO_MODE flag -- so a stock production build (e.g. `npm run +// build:esp32`, the real firmware-deployment artifact) works unmodified +// when loaded inside the FluidNC WASM demo's iframe, without needing a +// special build mode of its own. Unlike demo mode, which fully simulates a +// machine in JS, this talks to a real compiled FluidNC instance running in +// WebAssembly, via postMessage -- see FluidNC/wasm/README.md. +// +// Called unconditionally from main.tsx (not behind an import.meta.env +// check) so it survives tree-shaking in every build mode; it's a no-op +// unless the runtime marker is present. +export function installWasmBridgeIfActive(): void { + if (!isWasmBridgeActive()) return + ;(window as unknown as { WebSocket: unknown }).WebSocket = WasmBridgeWebSocket + installFetchInterceptor() + installXhrInterceptor() +} diff --git a/src/wasmBridge/shimTransport.ts b/src/wasmBridge/shimTransport.ts new file mode 100644 index 0000000..aaaab3f --- /dev/null +++ b/src/wasmBridge/shimTransport.ts @@ -0,0 +1,134 @@ +// Low-level postMessage client for the FluidNC WASM demo's bridge (see +// FluidNC/wasm/README.md and demo/index.html's self.fluidncOnShimOutput / +// 'fluidnc-shim-send' listener). This is the same protocol WebUI-mm's own +// wasmBridgeTransport.ts speaks -- the demo page doesn't know or care which +// WebUI is loaded in its iframe, so nothing on the FluidNC/demo side needed +// to change for FigUI to use it too. +// +// Two message kinds share this one postMessage channel: +// - 'fluidnc-shim-line'/'fluidnc-shim-send': ShimChannel's Grbl-line +// protocol. demo/index.html buffers raw shim output into whole lines and +// reassembles [JSON:...]-encapsulated chunks (see FluidNC's JSONencoder +// -- a JSON payload sent out over a serial-shaped channel is wrapped +// that way so a payload line can never be mistaken for the ok/error +// line that terminates a command) itself before ever posting a line +// here, so this module just relays whole, already-unwrapped lines to +// listeners -- matching what FigUI's own code already expects to see +// from real hardware's WebSocket (WSChannel doesn't use that wrapping -- +// see WebCommands.cpp/ShimChannel.cpp). +// - 'fluidnc-fs-request'/'fluidnc-fs-response': a request/response RPC for +// file operations, answered directly against the WASM instance's MEMFS +// (list/delete/deletedir/mkdir/read/write) -- see demo/index.html's +// handleFsRequest(). +// - 'fluidnc-shim-command'/'fluidnc-shim-command-response': send a +// command, get back its collected response once the ok/error terminator +// arrives -- see demo/index.html's deliverShimLine()/ +// maybeStartNextShimCommand(), which also queues these so two in flight +// at once can't have their response lines cross-attributed (this used +// to be commandBridge.ts's own job, via a hand-rolled line collector and +// a Promise chain to serialize sends -- both gone now that +// demo/index.html does it centrally for every WebUI bridge). + +type LineListener = (line: string, isJson: boolean) => void + +interface FsResponse { + type: 'fluidnc-fs-response' + id: number + ok: boolean + result?: unknown +} + +interface ShimCommandResponse { + type: 'fluidnc-shim-command-response' + id: number + ok: boolean + response: string + ackLine: string +} + +const lineListeners: LineListener[] = [] +const pendingFsRequests = new Map void; reject: (error: Error) => void }>() +const pendingShimCommands = new Map void; reject: (error: Error) => void }>() +let nextRequestId = 1 +let nextCommandId = 1 + +// Set by a marker