diff --git a/.env b/.env index 3afb5f60..29f0d4cc 100644 --- a/.env +++ b/.env @@ -3,3 +3,4 @@ OPENSHOCK_FW_CDN_DOMAIN=firmware.openshock.org OPENSHOCK_FW_VERSION=0.0.0-unknown OPENSHOCK_FW_HOSTNAME=OpenShock OPENSHOCK_FW_AP_PREFIX=OpenShock- +OPENSHOCK_URI_BUFFER_SIZE=256 diff --git a/.github/scripts/package-lock.json b/.github/scripts/package-lock.json index 22ca303b..074ab3c7 100644 --- a/.github/scripts/package-lock.json +++ b/.github/scripts/package-lock.json @@ -11,8 +11,8 @@ "dependencies": { "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", - "ini": "^4.1.1", - "semver": "^7.5.4" + "ini": "^4.1.2", + "semver": "^7.6.0" } }, "node_modules/@actions/core": { @@ -181,9 +181,9 @@ "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" }, "node_modules/ini": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", + "integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -230,9 +230,9 @@ } }, "node_modules/undici": { - "version": "5.28.3", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.3.tgz", - "integrity": "sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==", + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", "dependencies": { "@fastify/busboy": "^2.0.0" }, diff --git a/.github/scripts/package.json b/.github/scripts/package.json index 65b9030e..6d654add 100644 --- a/.github/scripts/package.json +++ b/.github/scripts/package.json @@ -11,7 +11,7 @@ "dependencies": { "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", - "ini": "^4.1.1", - "semver": "^7.5.4" + "ini": "^4.1.2", + "semver": "^7.6.0" } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dbe9997..83677e5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,47 @@ +# Version 1.2.0 Release Notes + +This release adds a new shocker protocol, more bugfixes, configurability, and performance improvements. + +## Highlight + +- Added support for **998DR** Petrainer RF protocol. + +## Major Updates + +- Add command to get/set api domain. +- Add command to get/set/clear override for Live Control Gateway (LCG) domain. + +## Minor Updates + +- Change transmission end command to last for 300 ms. +- Increase WDT timeout during OTA updates to prevent watchdog resets. +- Remove non thread-safe RF sequence caching. +- Update flatbuffers to 23.5.26. +- Start utilizing StringView more to reduce memory and CPU usage. +- Small code cleanup and refactoring. + +# Version 1.2.0-rc.1 Release Notes + +This is the first release candidate for version 1.2.0. + +## Highlight + +- Added support for **998DR** Petrainer RF protocol. + +## Major Updates + +- Add command to get/set api domain. +- Add command to get/set/clear override for Live Control Gateway (LCG) domain. + +## Minor Updates + +- Change transmission end command to last for 300 ms. +- Increase WDT timeout during OTA updates to prevent watchdog resets. +- Remove non thread-safe RF sequence caching. +- Update flatbuffers to 23.5.26. +- Start utilizing StringView more to reduce memory and CPU usage. +- Small code cleanup and refactoring. + # Version 1.1.2 Release Notes - Add support for OpenShock Core V2 Hardware diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 38673b3b..8e4560b8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,33 +11,33 @@ "@floating-ui/dom": "1.6.3" }, "devDependencies": { - "@playwright/test": "1.42.1", - "@skeletonlabs/skeleton": "2.9.0", - "@skeletonlabs/tw-plugin": "0.3.1", + "@playwright/test": "1.43.1", + "@skeletonlabs/skeleton": "2.9.1", + "@skeletonlabs/tw-plugin": "0.4.0", "@sveltejs/adapter-static": "^3.0.1", - "@sveltejs/kit": "2.5.2", - "@sveltejs/vite-plugin-svelte": "^3.0.2", + "@sveltejs/kit": "2.5.6", + "@sveltejs/vite-plugin-svelte": "^3.1.0", "@tailwindcss/forms": "0.5.7", - "@tailwindcss/typography": "0.5.10", - "@types/node": "20.11.24", - "@typescript-eslint/eslint-plugin": "7.1.0", - "@typescript-eslint/parser": "7.1.0", - "autoprefixer": "10.4.18", - "eslint": "8.57.0", + "@tailwindcss/typography": "0.5.12", + "@types/node": "20.12.7", + "@typescript-eslint/eslint-plugin": "7.6.0", + "@typescript-eslint/parser": "7.6.0", + "autoprefixer": "10.4.19", + "eslint": "^8.5.7", "eslint-config-prettier": "9.1.0", - "eslint-plugin-svelte": "2.35.1", - "flatbuffers": "23.5.26", - "postcss": "8.4.35", + "eslint-plugin-svelte": "2.37.0", + "flatbuffers": "24.3.25", + "postcss": "8.4.38", "prettier": "3.2.5", - "prettier-plugin-svelte": "3.2.2", - "svelte": "4.2.12", - "svelte-check": "3.6.6", - "tailwindcss": "3.4.1", + "prettier-plugin-svelte": "3.2.3", + "svelte": "4.2.14", + "svelte-check": "3.6.9", + "tailwindcss": "3.4.3", "tslib": "2.6.2", - "typescript": "5.3.3", - "vite": "^5.1.4", - "vite-plugin-tailwind-purgecss": "^0.2.0", - "vitest": "1.3.1" + "typescript": "5.4.5", + "vite": "^5.2.8", + "vite-plugin-tailwind-purgecss": "^0.3.0", + "vitest": "1.5.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -74,10 +74,26 @@ "node": ">=6.0.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/android-arm": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.9.tgz", - "integrity": "sha512-jkYjjq7SdsWuNI6b5quymW0oC83NN5FdRPuCbs9HZ02mfVdAP8B8eeqLSYU3gb6OJEaY5CQabtTFbqBf26H3GA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", "cpu": [ "arm" ], @@ -91,9 +107,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.9.tgz", - "integrity": "sha512-q4cR+6ZD0938R19MyEW3jEsMzbb/1rulLXiNAJQADD/XYp7pT+rOS5JGxvpRW8dFDEfjW4wLgC/3FXIw4zYglQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", "cpu": [ "arm64" ], @@ -107,9 +123,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.9.tgz", - "integrity": "sha512-KOqoPntWAH6ZxDwx1D6mRntIgZh9KodzgNOy5Ebt9ghzffOk9X2c1sPwtM9P+0eXbefnDhqYfkh5PLP5ULtWFA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", "cpu": [ "x64" ], @@ -123,9 +139,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.9.tgz", - "integrity": "sha512-KBJ9S0AFyLVx2E5D8W0vExqRW01WqRtczUZ8NRu+Pi+87opZn5tL4Y0xT0mA4FtHctd0ZgwNoN639fUUGlNIWw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", "cpu": [ "arm64" ], @@ -139,9 +155,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.9.tgz", - "integrity": "sha512-vE0VotmNTQaTdX0Q9dOHmMTao6ObjyPm58CHZr1UK7qpNleQyxlFlNCaHsHx6Uqv86VgPmR4o2wdNq3dP1qyDQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", "cpu": [ "x64" ], @@ -155,9 +171,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.9.tgz", - "integrity": "sha512-uFQyd/o1IjiEk3rUHSwUKkqZwqdvuD8GevWF065eqgYfexcVkxh+IJgwTaGZVu59XczZGcN/YMh9uF1fWD8j1g==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", "cpu": [ "arm64" ], @@ -171,9 +187,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.9.tgz", - "integrity": "sha512-WMLgWAtkdTbTu1AWacY7uoj/YtHthgqrqhf1OaEWnZb7PQgpt8eaA/F3LkV0E6K/Lc0cUr/uaVP/49iE4M4asA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", "cpu": [ "x64" ], @@ -187,9 +203,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.9.tgz", - "integrity": "sha512-C/ChPohUYoyUaqn1h17m/6yt6OB14hbXvT8EgM1ZWaiiTYz7nWZR0SYmMnB5BzQA4GXl3BgBO1l8MYqL/He3qw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", "cpu": [ "arm" ], @@ -203,9 +219,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.9.tgz", - "integrity": "sha512-PiPblfe1BjK7WDAKR1Cr9O7VVPqVNpwFcPWgfn4xu0eMemzRp442hXyzF/fSwgrufI66FpHOEJk0yYdPInsmyQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", "cpu": [ "arm64" ], @@ -219,9 +235,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.9.tgz", - "integrity": "sha512-f37i/0zE0MjDxijkPSQw1CO/7C27Eojqb+r3BbHVxMLkj8GCa78TrBZzvPyA/FNLUMzP3eyHCVkAopkKVja+6Q==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", "cpu": [ "ia32" ], @@ -235,9 +251,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.9.tgz", - "integrity": "sha512-t6mN147pUIf3t6wUt3FeumoOTPfmv9Cc6DQlsVBpB7eCpLOqQDyWBP1ymXn1lDw4fNUSb/gBcKAmvTP49oIkaA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", "cpu": [ "loong64" ], @@ -251,9 +267,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.9.tgz", - "integrity": "sha512-jg9fujJTNTQBuDXdmAg1eeJUL4Jds7BklOTkkH80ZgQIoCTdQrDaHYgbFZyeTq8zbY+axgptncko3v9p5hLZtw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", "cpu": [ "mips64el" ], @@ -267,9 +283,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.9.tgz", - "integrity": "sha512-tkV0xUX0pUUgY4ha7z5BbDS85uI7ABw3V1d0RNTii7E9lbmV8Z37Pup2tsLV46SQWzjOeyDi1Q7Wx2+QM8WaCQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", "cpu": [ "ppc64" ], @@ -283,9 +299,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.9.tgz", - "integrity": "sha512-DfLp8dj91cufgPZDXr9p3FoR++m3ZJ6uIXsXrIvJdOjXVREtXuQCjfMfvmc3LScAVmLjcfloyVtpn43D56JFHg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", "cpu": [ "riscv64" ], @@ -299,9 +315,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.9.tgz", - "integrity": "sha512-zHbglfEdC88KMgCWpOl/zc6dDYJvWGLiUtmPRsr1OgCViu3z5GncvNVdf+6/56O2Ca8jUU+t1BW261V6kp8qdw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", "cpu": [ "s390x" ], @@ -315,9 +331,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.9.tgz", - "integrity": "sha512-JUjpystGFFmNrEHQnIVG8hKwvA2DN5o7RqiO1CVX8EN/F/gkCjkUMgVn6hzScpwnJtl2mPR6I9XV1oW8k9O+0A==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", "cpu": [ "x64" ], @@ -331,9 +347,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.9.tgz", - "integrity": "sha512-GThgZPAwOBOsheA2RUlW5UeroRfESwMq/guy8uEe3wJlAOjpOXuSevLRd70NZ37ZrpO6RHGHgEHvPg1h3S1Jug==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", "cpu": [ "x64" ], @@ -347,9 +363,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.9.tgz", - "integrity": "sha512-Ki6PlzppaFVbLnD8PtlVQfsYw4S9n3eQl87cqgeIw+O3sRr9IghpfSKY62mggdt1yCSZ8QWvTZ9jo9fjDSg9uw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", "cpu": [ "x64" ], @@ -363,9 +379,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.9.tgz", - "integrity": "sha512-MLHj7k9hWh4y1ddkBpvRj2b9NCBhfgBt3VpWbHQnXRedVun/hC7sIyTGDGTfsGuXo4ebik2+3ShjcPbhtFwWDw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", "cpu": [ "x64" ], @@ -379,9 +395,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.9.tgz", - "integrity": "sha512-GQoa6OrQ8G08guMFgeXPH7yE/8Dt0IfOGWJSfSH4uafwdC7rWwrfE6P9N8AtPGIjUzdo2+7bN8Xo3qC578olhg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", "cpu": [ "arm64" ], @@ -395,9 +411,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.9.tgz", - "integrity": "sha512-UOozV7Ntykvr5tSOlGCrqU3NBr3d8JqPes0QWN2WOXfvkWVGRajC+Ym0/Wj88fUgecUCLDdJPDF0Nna2UK3Qtg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", "cpu": [ "ia32" ], @@ -411,9 +427,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.9.tgz", - "integrity": "sha512-oxoQgglOP7RH6iasDrhY+R/3cHrfwIDvRlT4CGChflq6twk8iENeVvMJjmvBb94Ik1Z+93iGO27err7w6l54GQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", "cpu": [ "x64" ], @@ -483,11 +499,11 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", - "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.3.tgz", + "integrity": "sha512-O0WKDOo0yhJuugCx6trZQj5jVJ9yR0ystG2JaNAemYUWce+pmM6WUEFIibnWyEJKdrDxhm75NoSRME35FNaM/Q==", "dependencies": { - "@floating-ui/utils": "^0.2.1" + "@floating-ui/utils": "^0.2.0" } }, "node_modules/@floating-ui/dom": { @@ -532,11 +548,55 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -632,13 +692,23 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@playwright/test": { - "version": "1.42.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.42.1.tgz", - "integrity": "sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==", + "version": "1.43.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.43.1.tgz", + "integrity": "sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==", "dev": true, "dependencies": { - "playwright": "1.42.1" + "playwright": "1.43.1" }, "bin": { "playwright": "cli.js" @@ -654,9 +724,9 @@ "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.0.tgz", - "integrity": "sha512-+1ge/xmaJpm1KVBuIH38Z94zj9fBD+hp+/5WLaHgyY8XLq1ibxk/zj6dTXaqM2cAbYKq8jYlhHd6k05If1W5xA==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.1.tgz", + "integrity": "sha512-fH8/o8nSUek8ceQnT7K4EQbSiV7jgkHq81m9lWZFIXjJ7lJzpWXbQFpT/Zh6OZYnpFykvzC3fbEvEAFZu03dPA==", "cpu": [ "arm" ], @@ -667,9 +737,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.0.tgz", - "integrity": "sha512-im6hUEyQ7ZfoZdNvtwgEJvBWZYauC9KVKq1w58LG2Zfz6zMd8gRrbN+xCVoqA2hv/v6fm9lp5LFGJ3za8EQH3A==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.1.tgz", + "integrity": "sha512-Y/9OHLjzkunF+KGEoJr3heiD5X9OLa8sbT1lm0NYeKyaM3oMhhQFvPB0bNZYJwlq93j8Z6wSxh9+cyKQaxS7PQ==", "cpu": [ "arm64" ], @@ -680,9 +750,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.0.tgz", - "integrity": "sha512-u7aTMskN6Dmg1lCT0QJ+tINRt+ntUrvVkhbPfFz4bCwRZvjItx2nJtwJnJRlKMMaQCHRjrNqHRDYvE4mBm3DlQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.1.tgz", + "integrity": "sha512-+kecg3FY84WadgcuSVm6llrABOdQAEbNdnpi5X3UwWiFVhZIZvKgGrF7kmLguvxHNQy+UuRV66cLVl3S+Rkt+Q==", "cpu": [ "arm64" ], @@ -693,9 +763,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.0.tgz", - "integrity": "sha512-8FvEl3w2ExmpcOmX5RJD0yqXcVSOqAJJUJ29Lca29Ik+3zPS1yFimr2fr5JSZ4Z5gt8/d7WqycpgkX9nocijSw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.1.tgz", + "integrity": "sha512-2pYRzEjVqq2TB/UNv47BV/8vQiXkFGVmPFwJb+1E0IFFZbIX8/jo1olxqqMbo6xCXf8kabANhp5bzCij2tFLUA==", "cpu": [ "x64" ], @@ -706,9 +776,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.0.tgz", - "integrity": "sha512-lHoKYaRwd4gge+IpqJHCY+8Vc3hhdJfU6ukFnnrJasEBUvVlydP8PuwndbWfGkdgSvZhHfSEw6urrlBj0TSSfg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.1.tgz", + "integrity": "sha512-mS6wQ6Do6/wmrF9aTFVpIJ3/IDXhg1EZcQFYHZLHqw6AzMBjTHWnCG35HxSqUNphh0EHqSM6wRTT8HsL1C0x5g==", "cpu": [ "arm" ], @@ -719,9 +789,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.0.tgz", - "integrity": "sha512-JbEPfhndYeWHfOSeh4DOFvNXrj7ls9S/2omijVsao+LBPTPayT1uKcK3dHW3MwDJ7KO11t9m2cVTqXnTKpeaiw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.1.tgz", + "integrity": "sha512-p9rGKYkHdFMzhckOTFubfxgyIO1vw//7IIjBBRVzyZebWlzRLeNhqxuSaZ7kCEKVkm/kuC9fVRW9HkC/zNRG2w==", "cpu": [ "arm64" ], @@ -732,9 +802,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.0.tgz", - "integrity": "sha512-ahqcSXLlcV2XUBM3/f/C6cRoh7NxYA/W7Yzuv4bDU1YscTFw7ay4LmD7l6OS8EMhTNvcrWGkEettL1Bhjf+B+w==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.1.tgz", + "integrity": "sha512-nDY6Yz5xS/Y4M2i9JLQd3Rofh5OR8Bn8qe3Mv/qCVpHFlwtZSBYSPaU4mrGazWkXrdQ98GB//H0BirGR/SKFSw==", "cpu": [ "arm64" ], @@ -744,10 +814,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.1.tgz", + "integrity": "sha512-im7HE4VBL+aDswvcmfx88Mp1soqL9OBsdDBU8NqDEYtkri0qV0THhQsvZtZeNNlLeCUQ16PZyv7cqutjDF35qw==", + "cpu": [ + "ppc64le" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.0.tgz", - "integrity": "sha512-uwvOYNtLw8gVtrExKhdFsYHA/kotURUmZYlinH2VcQxNCQJeJXnkmWgw2hI9Xgzhgu7J9QvWiq9TtTVwWMDa+w==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.1.tgz", + "integrity": "sha512-RWdiHuAxWmzPJgaHJdpvUUlDz8sdQz4P2uv367T2JocdDa98iRw2UjIJ4QxSyt077mXZT2X6pKfT2iYtVEvOFw==", "cpu": [ "riscv64" ], @@ -757,10 +840,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.1.tgz", + "integrity": "sha512-VMgaGQ5zRX6ZqV/fas65/sUGc9cPmsntq2FiGmayW9KMNfWVG/j0BAqImvU4KTeOOgYSf1F+k6at1UfNONuNjA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.0.tgz", - "integrity": "sha512-m6pkSwcZZD2LCFHZX/zW2aLIISyzWLU3hrLLzQKMI12+OLEzgruTovAxY5sCZJkipklaZqPy/2bEEBNjp+Y7xg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.1.tgz", + "integrity": "sha512-9Q7DGjZN+hTdJomaQ3Iub4m6VPu1r94bmK2z3UeWP3dGUecRC54tmVu9vKHTm1bOt3ASoYtEz6JSRLFzrysKlA==", "cpu": [ "x64" ], @@ -771,9 +867,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.0.tgz", - "integrity": "sha512-VFAC1RDRSbU3iOF98X42KaVicAfKf0m0OvIu8dbnqhTe26Kh6Ym9JrDulz7Hbk7/9zGc41JkV02g+p3BivOdAg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.1.tgz", + "integrity": "sha512-JNEG/Ti55413SsreTguSx0LOVKX902OfXIKVg+TCXO6Gjans/k9O6ww9q3oLGjNDaTLxM+IHFMeXy/0RXL5R/g==", "cpu": [ "x64" ], @@ -784,9 +880,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.0.tgz", - "integrity": "sha512-9jPgMvTKXARz4inw6jezMLA2ihDBvgIU9Ml01hjdVpOcMKyxFBJrn83KVQINnbeqDv0+HdO1c09hgZ8N0s820Q==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.1.tgz", + "integrity": "sha512-ryS22I9y0mumlLNwDFYZRDFLwWh3aKaC72CWjFcFvxK0U6v/mOkM5Up1bTbCRAhv3kEIwW2ajROegCIQViUCeA==", "cpu": [ "arm64" ], @@ -797,9 +893,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.0.tgz", - "integrity": "sha512-WE4pT2kTXQN2bAv40Uog0AsV7/s9nT9HBWXAou8+++MBCnY51QS02KYtm6dQxxosKi1VIz/wZIrTQO5UP2EW+Q==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.1.tgz", + "integrity": "sha512-TdloItiGk+T0mTxKx7Hp279xy30LspMso+GzQvV2maYePMAWdmrzqSNZhUpPj3CGw12aGj57I026PgLCTu8CGg==", "cpu": [ "ia32" ], @@ -810,9 +906,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.0.tgz", - "integrity": "sha512-aPP5Q5AqNGuT0tnuEkK/g4mnt3ZhheiXrDIiSVIHN9mcN21OyXDVbEMqmXPE7e2OplNLDkcvV+ZoGJa2ZImFgw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.1.tgz", + "integrity": "sha512-wQGI+LY/Py20zdUPq+XCem7JcPOyzIJBm3dli+56DJsQOHbnXZFEwgmnC6el1TPAfC8lBT3m+z69RmLykNUbew==", "cpu": [ "x64" ], @@ -829,9 +925,9 @@ "dev": true }, "node_modules/@skeletonlabs/skeleton": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@skeletonlabs/skeleton/-/skeleton-2.9.0.tgz", - "integrity": "sha512-s6l29M0PU+0he8+ifmfA6aA6Hvua58QDgcNlHrm9FFFdlj2D89BkT67f49LihNODOrTia7KewswkbdtYlPe8Ow==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@skeletonlabs/skeleton/-/skeleton-2.9.1.tgz", + "integrity": "sha512-cWDYfhZ8kIahPxvoT9xmwb3utIgtDpVQddNoQHnssxZZbqefl5z3cvWbuvvqEjewDxwKq4Zw4MtPcaT9nxojmQ==", "dev": true, "dependencies": { "esm-env": "1.0.0" @@ -841,9 +937,9 @@ } }, "node_modules/@skeletonlabs/tw-plugin": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@skeletonlabs/tw-plugin/-/tw-plugin-0.3.1.tgz", - "integrity": "sha512-DjjeOHN3HhFQf6gYPT2MUZMkIdw1jeB9mbuKC8etQxUlOR4XitfC7hssRWFJ8RJsvrrN0myCBbdWkVG1JVA96g==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@skeletonlabs/tw-plugin/-/tw-plugin-0.4.0.tgz", + "integrity": "sha512-v6Y4deBq9ByRx3kTRGgekhhYkWEYgNNNu8UXOwJngCStB7w8SwmbNFSeHkluxMbgCgMnJyp220EMpw9nj/rEsQ==", "dev": true, "peerDependencies": { "tailwindcss": ">=3.0.0" @@ -859,9 +955,9 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.2.tgz", - "integrity": "sha512-1Pm2lsBYURQsjnLyZa+jw75eVD4gYHxGRwPyFe4DAmB3FjTVR8vRNWGeuDLGFcKMh/B1ij6FTUrc9GrerogCng==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.6.tgz", + "integrity": "sha512-AYb02Jm5MfNqJHc8zrj7ScQAFAKmTUCkpkfoi8EVaZZDdnjkvI7L2GtnTDhpiXSAZRVitZX4qm59sMS1FgL+lQ==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -891,17 +987,17 @@ } }, "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.0.2.tgz", - "integrity": "sha512-MpmF/cju2HqUls50WyTHQBZUV3ovV/Uk8k66AN2gwHogNAG8wnW8xtZDhzNBsFJJuvmq1qnzA5kE7YfMJNFv2Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.1.0.tgz", + "integrity": "sha512-sY6ncCvg+O3njnzbZexcVtUqOBE3iYmQPJ9y+yXSkOwG576QI/xJrBnQSRXFLGwJNBa0T78JEKg5cIR0WOAuUw==", "dev": true, "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^2.0.0", "debug": "^4.3.4", "deepmerge": "^4.3.1", "kleur": "^4.1.5", - "magic-string": "^0.30.5", - "svelte-hmr": "^0.15.3", + "magic-string": "^0.30.9", + "svelte-hmr": "^0.16.0", "vitefu": "^0.2.5" }, "engines": { @@ -942,9 +1038,9 @@ } }, "node_modules/@tailwindcss/typography": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.10.tgz", - "integrity": "sha512-Pe8BuPJQJd3FfRnm6H0ulKIGoMEQS+Vq01R6M5aCrFB/ccR/shT+0kXLjouGC1gFLm9hopTFN+DMP0pfwRWzPw==", + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.12.tgz", + "integrity": "sha512-CNwpBpconcP7ppxmuq3qvaCxiRWnbhANpY/ruH4L5qs2GCiVDJXde/pjj2HWPV1+Q4G9+V/etrwUYopdcjAlyg==", "dev": true, "dependencies": { "lodash.castarray": "^4.4.0", @@ -975,9 +1071,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.24", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.24.tgz", - "integrity": "sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==", + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -996,25 +1092,25 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.1.0.tgz", - "integrity": "sha512-j6vT/kCulhG5wBmGtstKeiVr1rdXE4nk+DT1k6trYkwlrvW9eOF5ZbgKnd/YR6PcM4uTEXa0h6Fcvf6X7Dxl0w==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.6.0.tgz", + "integrity": "sha512-gKmTNwZnblUdnTIJu3e9kmeRRzV2j1a/LUO27KNNAnIC5zjy1aSvXSRp4rVNlmAoHlQ7HzX42NbKpcSr4jF80A==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.1.0", - "@typescript-eslint/type-utils": "7.1.0", - "@typescript-eslint/utils": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/type-utils": "7.6.0", + "@typescript-eslint/utils": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1030,20 +1126,19 @@ } } }, - "node_modules/@typescript-eslint/parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.1.0.tgz", - "integrity": "sha512-V1EknKUubZ1gWFjiOZhDSNToOjs63/9O0puCgGS8aDOgpZY326fzFu15QAUjwaXzRZjf/qdsdBrckYdv9YxB8w==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.6.0.tgz", + "integrity": "sha512-NxAfqAPNLG6LTmy7uZgpK8KcuiS2NZD/HlThPXQRGwz6u7MDBWRVliEEl1Gj6U7++kVJTpehkhZzCJLMK66Scw==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.1.0", - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/typescript-estree": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0", - "debug": "^4.3.4" + "@typescript-eslint/typescript-estree": "7.6.0", + "@typescript-eslint/utils": "7.6.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1058,36 +1153,45 @@ } } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.1.0.tgz", - "integrity": "sha512-6TmN4OJiohHfoOdGZ3huuLhpiUgOGTpgXNUPJgeZOZR3DnIpdSgtt83RS35OYNNXxM4TScVlpVKC9jyQSETR1A==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.6.0.tgz", + "integrity": "sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0" + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.15", + "@types/semver": "^7.5.8", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "semver": "^7.6.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.1.0.tgz", - "integrity": "sha512-UZIhv8G+5b5skkcuhgvxYWHjk7FW7/JP5lPASMEUoliAPwIH/rxoUSQPia2cuOj9AmDZmwUl1usKm85t5VUMew==", + "node_modules/@typescript-eslint/parser": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.6.0.tgz", + "integrity": "sha512-usPMPHcwX3ZoPWnBnhhorc14NJw9J4HpSXQX4urF2TPKG0au0XhJoZyX62fmvdHONUkmyUe74Hzm1//XA+BoYg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.1.0", - "@typescript-eslint/utils": "7.1.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", + "debug": "^4.3.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1102,13 +1206,30 @@ } } }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.6.0.tgz", + "integrity": "sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/types": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.1.0.tgz", - "integrity": "sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz", + "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==", "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1116,22 +1237,22 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.1.0.tgz", - "integrity": "sha512-k7MyrbD6E463CBbSpcOnwa8oXRdHzH1WiVzOipK3L5KSML92ZKgUBrTlehdi7PEIMT8k0bQixHUGXggPAlKnOQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.6.0.tgz", + "integrity": "sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1153,9 +1274,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -1167,42 +1288,17 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@typescript-eslint/utils": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.1.0.tgz", - "integrity": "sha512-WUFba6PZC5OCGEmbweGpnNJytJiLG7ZvDBJJoUcX4qZYf1mGZ97mO2Mps6O2efxJcJdRNpqweCistDbZMwIVHw==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.1.0", - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/typescript-estree": "7.1.0", - "semver": "^7.5.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - } - }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.1.0.tgz", - "integrity": "sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz", + "integrity": "sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.6.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1216,13 +1312,13 @@ "dev": true }, "node_modules/@vitest/expect": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.3.1.tgz", - "integrity": "sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.0.tgz", + "integrity": "sha512-0pzuCI6KYi2SIC3LQezmxujU9RK/vwC1U9R0rLuGlNGcOuDWxqWKu6nUdFsX9tH1WU0SXtAxToOsEjeUn1s3hA==", "dev": true, "dependencies": { - "@vitest/spy": "1.3.1", - "@vitest/utils": "1.3.1", + "@vitest/spy": "1.5.0", + "@vitest/utils": "1.5.0", "chai": "^4.3.10" }, "funding": { @@ -1230,12 +1326,12 @@ } }, "node_modules/@vitest/runner": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.3.1.tgz", - "integrity": "sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.0.tgz", + "integrity": "sha512-7HWwdxXP5yDoe7DTpbif9l6ZmDwCzcSIK38kTSIt6CFEpMjX4EpCgT6wUmS0xTXqMI6E/ONmfgRKmaujpabjZQ==", "dev": true, "dependencies": { - "@vitest/utils": "1.3.1", + "@vitest/utils": "1.5.0", "p-limit": "^5.0.0", "pathe": "^1.1.1" }, @@ -1271,9 +1367,9 @@ } }, "node_modules/@vitest/snapshot": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.3.1.tgz", - "integrity": "sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.0.tgz", + "integrity": "sha512-qpv3fSEuNrhAO3FpH6YYRdaECnnRjg9VxbhdtPwPRnzSfHVXnNzzrpX4cJxqiwgRMo7uRMWDFBlsBq4Cr+rO3A==", "dev": true, "dependencies": { "magic-string": "^0.30.5", @@ -1285,9 +1381,9 @@ } }, "node_modules/@vitest/spy": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.3.1.tgz", - "integrity": "sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.0.tgz", + "integrity": "sha512-vu6vi6ew5N5MMHJjD5PoakMRKYdmIrNJmyfkhRpQt5d9Ewhw9nZ5Aqynbi3N61bvk9UvZ5UysMT6ayIrZ8GA9w==", "dev": true, "dependencies": { "tinyspy": "^2.2.0" @@ -1297,9 +1393,9 @@ } }, "node_modules/@vitest/utils": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.3.1.tgz", - "integrity": "sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.0.tgz", + "integrity": "sha512-BDU0GNL8MWkRkSRdNFvCUCAVOeHaUlVJ9Tx0TYBZyXaaOTmGtUFObzchCivIBrIwKzvZA7A9sCejVhXM2aY98A==", "dev": true, "dependencies": { "diff-sequences": "^29.6.3", @@ -1312,9 +1408,9 @@ } }, "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1440,9 +1536,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.18", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz", - "integrity": "sha512-1DKbDfsr6KUElM6wg+0zRNkB/Q7WcKYAaK+pzXn+Xqmszm/5Xa9coeNdtP88Vi+dPzZnMjhge8GIV49ZQkDa+g==", + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", + "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", "dev": true, "funding": [ { @@ -1460,7 +1556,7 @@ ], "dependencies": { "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001591", + "caniuse-lite": "^1.0.30001599", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", @@ -1591,9 +1687,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001593", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001593.tgz", - "integrity": "sha512-UWM1zlo3cZfkpBysd7AS+z+v007q9G1+fLTUU42rQnY6t2axoogPW/xol6T7juU5EUoOhML4WgBIdG+9yYqAjQ==", + "version": "1.0.30001608", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001608.tgz", + "integrity": "sha512-cjUJTQkk9fQlJR2s4HMuPMvTiRggl0rAVMtthQuyOlDWuqHXqN8azLq+pi8B2TjwKJ32diHjUqRIKeFX4z1FoA==", "dev": true, "funding": [ { @@ -1902,12 +1998,63 @@ "node": ">=6.0.0" } }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/electron-to-chromium": { - "version": "1.4.690", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.690.tgz", - "integrity": "sha512-+2OAGjUx68xElQhydpcbqH50hE8Vs2K6TkAeLhICYfndb67CVH0UsZaijmRUE3rHlIxU1u0jxwhgVe6fK3YANA==", + "version": "1.4.734", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.734.tgz", + "integrity": "sha512-pYfGUc+ll8AOzLbLC0lfgwkvCZIV+sKGuFFsSNuF3K3ujrmem8jIjg/t6DNq0J7biTSS1hCt/Hts0nmA3ZyprQ==", "dev": true }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es6-promise": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", @@ -1915,9 +2062,9 @@ "dev": true }, "node_modules/esbuild": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.9.tgz", - "integrity": "sha512-U9CHtKSy+EpPsEBa+/A2gMs/h3ylBC0H0KSqIg7tpztHerLi6nrrcoUJAkNCEPumx8yJ+Byic4BVwHgRbN0TBg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", "dev": true, "hasInstallScript": true, "bin": { @@ -1927,28 +2074,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.19.9", - "@esbuild/android-arm64": "0.19.9", - "@esbuild/android-x64": "0.19.9", - "@esbuild/darwin-arm64": "0.19.9", - "@esbuild/darwin-x64": "0.19.9", - "@esbuild/freebsd-arm64": "0.19.9", - "@esbuild/freebsd-x64": "0.19.9", - "@esbuild/linux-arm": "0.19.9", - "@esbuild/linux-arm64": "0.19.9", - "@esbuild/linux-ia32": "0.19.9", - "@esbuild/linux-loong64": "0.19.9", - "@esbuild/linux-mips64el": "0.19.9", - "@esbuild/linux-ppc64": "0.19.9", - "@esbuild/linux-riscv64": "0.19.9", - "@esbuild/linux-s390x": "0.19.9", - "@esbuild/linux-x64": "0.19.9", - "@esbuild/netbsd-x64": "0.19.9", - "@esbuild/openbsd-x64": "0.19.9", - "@esbuild/sunos-x64": "0.19.9", - "@esbuild/win32-arm64": "0.19.9", - "@esbuild/win32-ia32": "0.19.9", - "@esbuild/win32-x64": "0.19.9" + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" } }, "node_modules/escalade": { @@ -2028,10 +2176,13 @@ } }, "node_modules/eslint-compat-utils": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.1.2.tgz", - "integrity": "sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.0.tgz", + "integrity": "sha512-dc6Y8tzEcSYZMHa+CMPLi/hyo1FzNeonbhJL7Ol0ccuKQkwopJcJBA9YL/xmMTLU1eKigXo9vj9nALElWYSowg==", "dev": true, + "dependencies": { + "semver": "^7.5.4" + }, "engines": { "node": ">=12" }, @@ -2052,23 +2203,23 @@ } }, "node_modules/eslint-plugin-svelte": { - "version": "2.35.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.35.1.tgz", - "integrity": "sha512-IF8TpLnROSGy98Z3NrsKXWDSCbNY2ReHDcrYTuXZMbfX7VmESISR78TWgO9zdg4Dht1X8coub5jKwHzP0ExRug==", + "version": "2.37.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.37.0.tgz", + "integrity": "sha512-H/2Gz7agYHEMEEzRuLYuCmAIdjuBnbhFG9hOK0yCdSBvvJGJMkjo+lR6j67OIvLOavgp4L7zA5LnDKi8WqdPhQ==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@jridgewell/sourcemap-codec": "^1.4.14", - "debug": "^4.3.1", - "eslint-compat-utils": "^0.1.2", + "@eslint-community/eslint-utils": "^4.4.0", + "@jridgewell/sourcemap-codec": "^1.4.15", + "debug": "^4.3.4", + "eslint-compat-utils": "^0.5.0", "esutils": "^2.0.3", - "known-css-properties": "^0.29.0", - "postcss": "^8.4.5", + "known-css-properties": "^0.30.0", + "postcss": "^8.4.38", "postcss-load-config": "^3.1.4", "postcss-safe-parser": "^6.0.0", - "postcss-selector-parser": "^6.0.11", - "semver": "^7.5.3", - "svelte-eslint-parser": ">=0.33.0 <1.0.0" + "postcss-selector-parser": "^6.0.16", + "semver": "^7.6.0", + "svelte-eslint-parser": ">=0.34.0 <1.0.0" }, "engines": { "node": "^14.17.0 || >=16.0.0" @@ -2077,8 +2228,8 @@ "url": "https://github.com/sponsors/ota-meshi" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0-0", - "svelte": "^3.37.0 || ^4.0.0" + "eslint": "^7.0.0 || ^8.0.0-0 || ^9.0.0-0", + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.95" }, "peerDependenciesMeta": { "svelte": { @@ -2087,9 +2238,9 @@ } }, "node_modules/eslint-plugin-svelte/node_modules/postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", + "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -2334,17 +2485,33 @@ } }, "node_modules/flatbuffers": { - "version": "23.5.26", - "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-23.5.26.tgz", - "integrity": "sha512-vE+SI9vrJDwi1oETtTIFldC/o9GsVKRM+s6EL0nQgxXlYV1Vc4Tk30hj4xGICftInKQKj1F3up2n8UbIVobISQ==", + "version": "24.3.25", + "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-24.3.25.tgz", + "integrity": "sha512-3HDgPbgiwWMI9zVB7VYBHaMrbOO7Gm0v+yD2FV/sCKj+9NDeVL7BOBYUuhWAQGKWOzBo8S9WdMvV0eixO233XQ==", "dev": true }, "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -2530,9 +2697,9 @@ } }, "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, "engines": { "node": ">= 4" @@ -2622,6 +2789,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -2679,6 +2855,24 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jiti": { "version": "1.21.0", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", @@ -2689,9 +2883,9 @@ } }, "node_modules/js-tokens": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.3.tgz", - "integrity": "sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz", + "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", "dev": true }, "node_modules/js-yaml": { @@ -2749,9 +2943,9 @@ } }, "node_modules/known-css-properties": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.29.0.tgz", - "integrity": "sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==", + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.30.0.tgz", + "integrity": "sha512-VSWXYUnsPu9+WYKkfmJyLKtIvaRJi1kXUqVmBACORXZQxT5oZDsoZ2vQP+bQFDnWtpI/4eq3MLoRMjI2fnLzTQ==", "dev": true }, "node_modules/levn": { @@ -2859,9 +3053,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "version": "0.30.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.9.tgz", + "integrity": "sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -2955,6 +3149,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -3190,6 +3393,31 @@ "node": ">=6" } }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dev": true, + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3223,6 +3451,31 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -3306,12 +3559,12 @@ } }, "node_modules/playwright": { - "version": "1.42.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.1.tgz", - "integrity": "sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg==", + "version": "1.43.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.43.1.tgz", + "integrity": "sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==", "dev": true, "dependencies": { - "playwright-core": "1.42.1" + "playwright-core": "1.43.1" }, "bin": { "playwright": "cli.js" @@ -3324,9 +3577,9 @@ } }, "node_modules/playwright-core": { - "version": "1.42.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.1.tgz", - "integrity": "sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==", + "version": "1.43.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.43.1.tgz", + "integrity": "sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -3336,9 +3589,9 @@ } }, "node_modules/postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "dev": true, "funding": [ { @@ -3357,7 +3610,7 @@ "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -3546,9 +3799,9 @@ } }, "node_modules/prettier-plugin-svelte": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.2.2.tgz", - "integrity": "sha512-ZzzE/wMuf48/1+Lf2Ffko0uDa6pyCfgHV6+uAhtg2U0AAXGrhCSW88vEJNAkAxW5qyrFY1y1zZ4J8TgHrjW++Q==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.2.3.tgz", + "integrity": "sha512-wJq8RunyFlWco6U0WJV5wNCM7zpBFakS76UBSbmzMGpncpK98NZABaE+s7n8/APDCEVNHXC5Mpq+MLebQtsRlg==", "dev": true, "peerDependencies": { "prettier": "^3.0.0", @@ -3591,13 +3844,13 @@ } }, "node_modules/purgecss": { - "version": "6.0.0-alpha.0", - "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-6.0.0-alpha.0.tgz", - "integrity": "sha512-UC7d7uIyZsky+srEsSXny9BkbTcVn3ZtBCNX3rW3DsqJKhvUXFRpufA4ktcHzWF0+JLZgmsqjUm/8R82x9bHpw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-6.0.0.tgz", + "integrity": "sha512-s3EBxg5RSWmpqd0KGzNqPiaBbWDz1/As+2MzoYVGMqgDqRTLBhJW6sywfTBek7OwNfoS/6pS0xdtvChNhFj2cw==", "dev": true, "dependencies": { - "commander": "^10.0.0", - "glob": "^8.0.3", + "commander": "^12.0.0", + "glob": "^10.3.10", "postcss": "^8.4.4", "postcss-selector-parser": "^6.0.7" }, @@ -3605,6 +3858,16 @@ "purgecss": "bin/purgecss.js" } }, + "node_modules/purgecss-from-html": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/purgecss-from-html/-/purgecss-from-html-6.0.0.tgz", + "integrity": "sha512-GkgAUzgyC4kwcVY5+QOI2eqQghV1Lq7q2uIODAPIueiBn3mHpJOh9boSMjfUQg0/YU/ZEWq7SzjwetuqxTvD4g==", + "dev": true, + "dependencies": { + "parse5": "^7.1.2", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + } + }, "node_modules/purgecss/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -3615,43 +3878,49 @@ } }, "node_modules/purgecss/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", + "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==", "dev": true, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/purgecss/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.6", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=12" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/purgecss/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/queue-microtask": { @@ -3753,10 +4022,13 @@ } }, "node_modules/rollup": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.0.tgz", - "integrity": "sha512-bUHW/9N21z64gw8s6tP4c88P382Bq/L5uZDowHlHx6s/QWpjJXivIAbEw6LZthgSvlEizZBfLC4OAvWe7aoF7A==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.1.tgz", + "integrity": "sha512-4LnHSdd3QK2pa1J6dFbfm1HN0D7vSK/ZuZTsdyUAlA6Rr1yTouUTL13HaDOGJVgby461AhrNGBS7sCGXXtT+SA==", "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, "bin": { "rollup": "dist/bin/rollup" }, @@ -3765,19 +4037,21 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.0", - "@rollup/rollup-android-arm64": "4.9.0", - "@rollup/rollup-darwin-arm64": "4.9.0", - "@rollup/rollup-darwin-x64": "4.9.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.0", - "@rollup/rollup-linux-arm64-gnu": "4.9.0", - "@rollup/rollup-linux-arm64-musl": "4.9.0", - "@rollup/rollup-linux-riscv64-gnu": "4.9.0", - "@rollup/rollup-linux-x64-gnu": "4.9.0", - "@rollup/rollup-linux-x64-musl": "4.9.0", - "@rollup/rollup-win32-arm64-msvc": "4.9.0", - "@rollup/rollup-win32-ia32-msvc": "4.9.0", - "@rollup/rollup-win32-x64-msvc": "4.9.0", + "@rollup/rollup-android-arm-eabi": "4.14.1", + "@rollup/rollup-android-arm64": "4.14.1", + "@rollup/rollup-darwin-arm64": "4.14.1", + "@rollup/rollup-darwin-x64": "4.14.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.14.1", + "@rollup/rollup-linux-arm64-gnu": "4.14.1", + "@rollup/rollup-linux-arm64-musl": "4.14.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.14.1", + "@rollup/rollup-linux-riscv64-gnu": "4.14.1", + "@rollup/rollup-linux-s390x-gnu": "4.14.1", + "@rollup/rollup-linux-x64-gnu": "4.14.1", + "@rollup/rollup-linux-x64-musl": "4.14.1", + "@rollup/rollup-win32-arm64-msvc": "4.14.1", + "@rollup/rollup-win32-ia32-msvc": "4.14.1", + "@rollup/rollup-win32-x64-msvc": "4.14.1", "fsevents": "~2.3.2" } }, @@ -3841,9 +4115,9 @@ } }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -3939,9 +4213,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true, "engines": { "node": ">=0.10.0" @@ -3959,6 +4233,71 @@ "integrity": "sha512-aFZ19IgVmhdB2uX599ve2kE6BIE3YMnQ6Gp6BURhW/oIzpXGKr878TQfAQZn1+i0Flcc/UKUy1gOlcfaUBCryg==", "dev": true }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -3971,6 +4310,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-final-newline": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", @@ -4008,12 +4360,12 @@ } }, "node_modules/strip-literal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.0.0.tgz", - "integrity": "sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz", + "integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==", "dev": true, "dependencies": { - "js-tokens": "^8.0.2" + "js-tokens": "^9.0.0" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -4086,9 +4438,9 @@ } }, "node_modules/svelte": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.12.tgz", - "integrity": "sha512-d8+wsh5TfPwqVzbm4/HCXC783/KPHV60NvwitJnyTA5lWn1elhXMNWhXGCJ7PwPa8qFUnyJNIyuIRt2mT0WMug==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.14.tgz", + "integrity": "sha512-ry3+YlWqZpHxLy45MW4MZIxNdvB+Wl7p2nnstWKbOAewaJyNJuOtivSbRChcfIej6wFBjWqyKmf/NgK1uW2JAA==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.1", @@ -4111,9 +4463,9 @@ } }, "node_modules/svelte-check": { - "version": "3.6.6", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.6.6.tgz", - "integrity": "sha512-b9q9rOHOMYF3U8XllK7LmXTq1LeWQ98waGfEJzrFutViadkNl1tgdEtxIQ8yuPx+VQ4l7YrknYol+0lfZocaZw==", + "version": "3.6.9", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.6.9.tgz", + "integrity": "sha512-hDQrk3L0osX07djQyMiXocKysTLfusqi8AriNcCiQxhQR49/LonYolcUGMtZ0fbUR8HTR198Prrgf52WWU9wEg==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.17", @@ -4133,16 +4485,16 @@ } }, "node_modules/svelte-eslint-parser": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.33.1.tgz", - "integrity": "sha512-vo7xPGTlKBGdLH8T5L64FipvTrqv3OQRx9d2z5X05KKZDlF4rQk8KViZO4flKERY+5BiVdOh7zZ7JGJWo5P0uA==", + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.34.1.tgz", + "integrity": "sha512-9+uLA1pqI9AZioKVGJzYYmlOZWxfoCXSbAM9iaNm7H01XlYlzRTtJfZgl9o3StQGN41PfGJIbkKkfk3e/pHFfA==", "dev": true, "dependencies": { - "eslint-scope": "^7.0.0", - "eslint-visitor-keys": "^3.0.0", - "espree": "^9.0.0", - "postcss": "^8.4.29", - "postcss-scss": "^4.0.8" + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "postcss": "^8.4.38", + "postcss-scss": "^4.0.9" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -4151,7 +4503,7 @@ "url": "https://github.com/sponsors/ota-meshi" }, "peerDependencies": { - "svelte": "^3.37.0 || ^4.0.0" + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.94" }, "peerDependenciesMeta": { "svelte": { @@ -4160,9 +4512,9 @@ } }, "node_modules/svelte-hmr": { - "version": "0.15.3", - "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.3.tgz", - "integrity": "sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==", + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.16.0.tgz", + "integrity": "sha512-Gyc7cOS3VJzLlfj7wKS0ZnzDVdv3Pn2IuVeJPk9m2skfhcu5bq3wtIZyQGggr7/Iim5rH5cncyQft/kRLupcnA==", "dev": true, "engines": { "node": "^12.20 || ^14.13.1 || >= 16" @@ -4235,9 +4587,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", - "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", + "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==", "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -4248,7 +4600,7 @@ "fast-glob": "^3.3.0", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.19.1", + "jiti": "^1.21.0", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", @@ -4381,9 +4733,9 @@ "dev": true }, "node_modules/tinypool": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.2.tgz", - "integrity": "sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==", + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.3.tgz", + "integrity": "sha512-Ud7uepAklqRH1bvwy22ynrliC7Dljz7Tm8M/0RBUW+YRa4YHhZ6e4PpgE+fu1zr/WqB1kbeuVrdfeuyIBpy4tw==", "dev": true, "engines": { "node": ">=14.0.0" @@ -4420,12 +4772,12 @@ } }, "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "dev": true, "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" @@ -4477,9 +4829,9 @@ } }, "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -4547,14 +4899,14 @@ "dev": true }, "node_modules/vite": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.4.tgz", - "integrity": "sha512-n+MPqzq+d9nMVTKyewqw6kSt+R3CkvF9QAKY8obiQn8g1fwTscKxyfaYnC632HtBXAQGc1Yjomphwn1dtwGAHg==", + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz", + "integrity": "sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==", "dev": true, "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.35", - "rollup": "^4.2.0" + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" }, "bin": { "vite": "bin/vite.js" @@ -4602,9 +4954,9 @@ } }, "node_modules/vite-node": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.3.1.tgz", - "integrity": "sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.0.tgz", + "integrity": "sha512-tV8h6gMj6vPzVCa7l+VGq9lwoJjW8Y79vst8QZZGiuRAfijU+EEWuc0kFpmndQrWhMMhet1jdSF+40KSZUqIIw==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -4624,18 +4976,35 @@ } }, "node_modules/vite-plugin-tailwind-purgecss": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/vite-plugin-tailwind-purgecss/-/vite-plugin-tailwind-purgecss-0.2.0.tgz", - "integrity": "sha512-6Q+SaalUd0t3BOIIiCQPlbZQuYARVgjoC78X+fLbQJqIEy/9fC58aQgHMgi+CmYfVfZmJToA8YiLueSGEo2mng==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/vite-plugin-tailwind-purgecss/-/vite-plugin-tailwind-purgecss-0.3.0.tgz", + "integrity": "sha512-hm0yOZ2Z+Xn0T70lWa10IbgBgORGFj5da5NLUglvHSiZHSeJrQKcG1WAZjCBSBTrLPTffF+vYDbBLgVpbo2q0A==", "dev": true, "dependencies": { + "chalk": "^5.3.0", + "css-tree": "^2.3.1", "estree-walker": "^3.0.3", - "purgecss": "6.0.0-alpha.0" + "fast-glob": "^3.3.2", + "purgecss": "^6.0.0", + "purgecss-from-html": "^6.0.0" }, "peerDependencies": { + "tailwindcss": "^3.3.0", "vite": "^4.1.1 || ^5.0.0" } }, + "node_modules/vite-plugin-tailwind-purgecss/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/vite/node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -4665,16 +5034,16 @@ } }, "node_modules/vitest": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.3.1.tgz", - "integrity": "sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.0.tgz", + "integrity": "sha512-d8UKgR0m2kjdxDWX6911uwxout6GHS0XaGH1cksSIVVG8kRlE7G7aBw7myKQCvDI5dT4j7ZMa+l706BIORMDLw==", "dev": true, "dependencies": { - "@vitest/expect": "1.3.1", - "@vitest/runner": "1.3.1", - "@vitest/snapshot": "1.3.1", - "@vitest/spy": "1.3.1", - "@vitest/utils": "1.3.1", + "@vitest/expect": "1.5.0", + "@vitest/runner": "1.5.0", + "@vitest/snapshot": "1.5.0", + "@vitest/spy": "1.5.0", + "@vitest/utils": "1.5.0", "acorn-walk": "^8.3.2", "chai": "^4.3.10", "debug": "^4.3.4", @@ -4686,9 +5055,9 @@ "std-env": "^3.5.0", "strip-literal": "^2.0.0", "tinybench": "^2.5.1", - "tinypool": "^0.8.2", + "tinypool": "^0.8.3", "vite": "^5.0.0", - "vite-node": "1.3.1", + "vite-node": "1.5.0", "why-is-node-running": "^2.2.2" }, "bin": { @@ -4703,8 +5072,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.3.1", - "@vitest/ui": "1.3.1", + "@vitest/browser": "1.5.0", + "@vitest/ui": "1.5.0", "happy-dom": "*", "jsdom": "*" }, @@ -4760,6 +5129,100 @@ "node": ">=8" } }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 24c8f074..dc7d4e66 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,33 +15,33 @@ "test:unit": "vitest" }, "devDependencies": { - "@playwright/test": "1.42.1", - "@skeletonlabs/skeleton": "2.9.0", - "@skeletonlabs/tw-plugin": "0.3.1", + "@playwright/test": "1.43.1", + "@skeletonlabs/skeleton": "2.9.1", + "@skeletonlabs/tw-plugin": "0.4.0", "@sveltejs/adapter-static": "^3.0.1", - "@sveltejs/kit": "2.5.2", - "@sveltejs/vite-plugin-svelte": "^3.0.2", + "@sveltejs/kit": "2.5.6", + "@sveltejs/vite-plugin-svelte": "^3.1.0", "@tailwindcss/forms": "0.5.7", - "@tailwindcss/typography": "0.5.10", - "@types/node": "20.11.24", - "@typescript-eslint/eslint-plugin": "7.1.0", - "@typescript-eslint/parser": "7.1.0", - "autoprefixer": "10.4.18", - "eslint": "8.57.0", + "@tailwindcss/typography": "0.5.12", + "@types/node": "20.12.7", + "@typescript-eslint/eslint-plugin": "7.6.0", + "@typescript-eslint/parser": "7.6.0", + "autoprefixer": "10.4.19", + "eslint": "^8.5.7", "eslint-config-prettier": "9.1.0", - "eslint-plugin-svelte": "2.35.1", - "flatbuffers": "23.5.26", - "postcss": "8.4.35", + "eslint-plugin-svelte": "2.37.0", + "flatbuffers": "24.3.25", + "postcss": "8.4.38", "prettier": "3.2.5", - "prettier-plugin-svelte": "3.2.2", - "svelte": "4.2.12", - "svelte-check": "3.6.6", - "tailwindcss": "3.4.1", + "prettier-plugin-svelte": "3.2.3", + "svelte": "4.2.14", + "svelte-check": "3.6.9", + "tailwindcss": "3.4.3", "tslib": "2.6.2", - "typescript": "5.3.3", - "vite": "^5.1.4", - "vite-plugin-tailwind-purgecss": "^0.2.0", - "vitest": "1.3.1" + "typescript": "5.4.5", + "vite": "^5.2.8", + "vite-plugin-tailwind-purgecss": "^0.3.0", + "vitest": "1.5.0" }, "type": "module", "dependencies": { diff --git a/frontend/src/lib/_fbs/open-shock/serialization/configuration/backend-config.ts b/frontend/src/lib/_fbs/open-shock/serialization/configuration/backend-config.ts index 54d691c1..edb4a223 100644 --- a/frontend/src/lib/_fbs/open-shock/serialization/configuration/backend-config.ts +++ b/frontend/src/lib/_fbs/open-shock/serialization/configuration/backend-config.ts @@ -42,8 +42,18 @@ authToken(optionalEncoding?:any):string|Uint8Array|null { return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; } +/** + * Override the Live-Control-Gateway (LCG) URL + */ +lcgOverride():string|null +lcgOverride(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +lcgOverride(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 8); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + static startBackendConfig(builder:flatbuffers.Builder) { - builder.startObject(2); + builder.startObject(3); } static addDomain(builder:flatbuffers.Builder, domainOffset:flatbuffers.Offset) { @@ -54,15 +64,20 @@ static addAuthToken(builder:flatbuffers.Builder, authTokenOffset:flatbuffers.Off builder.addFieldOffset(1, authTokenOffset, 0); } +static addLcgOverride(builder:flatbuffers.Builder, lcgOverrideOffset:flatbuffers.Offset) { + builder.addFieldOffset(2, lcgOverrideOffset, 0); +} + static endBackendConfig(builder:flatbuffers.Builder):flatbuffers.Offset { const offset = builder.endObject(); return offset; } -static createBackendConfig(builder:flatbuffers.Builder, domainOffset:flatbuffers.Offset, authTokenOffset:flatbuffers.Offset):flatbuffers.Offset { +static createBackendConfig(builder:flatbuffers.Builder, domainOffset:flatbuffers.Offset, authTokenOffset:flatbuffers.Offset, lcgOverrideOffset:flatbuffers.Offset):flatbuffers.Offset { BackendConfig.startBackendConfig(builder); BackendConfig.addDomain(builder, domainOffset); BackendConfig.addAuthToken(builder, authTokenOffset); + BackendConfig.addLcgOverride(builder, lcgOverrideOffset); return BackendConfig.endBackendConfig(builder); } } diff --git a/frontend/src/lib/_fbs/open-shock/serialization/types/shocker-model-type.ts b/frontend/src/lib/_fbs/open-shock/serialization/types/shocker-model-type.ts index e41c5b9d..3ec76ac9 100644 --- a/frontend/src/lib/_fbs/open-shock/serialization/types/shocker-model-type.ts +++ b/frontend/src/lib/_fbs/open-shock/serialization/types/shocker-model-type.ts @@ -4,5 +4,6 @@ export enum ShockerModelType { CaiXianlin = 0, - Petrainer = 1 + Petrainer = 1, + Petrainer998DR = 2 } diff --git a/include/Common.h b/include/Common.h index 6ef50aad..9925f948 100644 --- a/include/Common.h +++ b/include/Common.h @@ -20,7 +20,6 @@ #error "OPENSHOCK_FW_VERSION must be defined" #endif -#define OPENSHOCK_API_URL(path) "https://" OPENSHOCK_API_DOMAIN path #define OPENSHOCK_FW_CDN_URL(path) "https://" OPENSHOCK_FW_CDN_DOMAIN path #define OPENSHOCK_GPIO_INVALID 0 diff --git a/include/GatewayClient.h b/include/GatewayClient.h index 64c799f5..56860ccb 100644 --- a/include/GatewayClient.h +++ b/include/GatewayClient.h @@ -32,6 +32,7 @@ namespace OpenShock { bool loop(); private: + void _setState(State state); void _sendKeepAlive(); void _sendBootStatus(); void _handleEvent(WStype_t type, std::uint8_t* payload, std::size_t length); diff --git a/include/GatewayConnectionManager.h b/include/GatewayConnectionManager.h index 8bd1a9bd..ce596402 100644 --- a/include/GatewayConnectionManager.h +++ b/include/GatewayConnectionManager.h @@ -12,7 +12,7 @@ namespace OpenShock::GatewayConnectionManager { bool IsConnected(); bool IsLinked(); - AccountLinkResultCode Link(const char* linkCode); + AccountLinkResultCode Link(StringView linkCode); void UnLink(); bool SendMessageTXT(StringView data); diff --git a/include/SemVer.h b/include/SemVer.h index dad22c14..f1578d8c 100644 --- a/include/SemVer.h +++ b/include/SemVer.h @@ -17,7 +17,7 @@ namespace OpenShock { : major(major), minor(minor), patch(patch), prerelease(), build() {} SemVer(std::uint16_t major, std::uint16_t minor, std::uint16_t patch, StringView prerelease, StringView build) - : major(major), minor(minor), patch(patch), prerelease(prerelease.data(), prerelease.length()), build(build.data(), build.length()) + : major(major), minor(minor), patch(patch), prerelease(prerelease.toString()), build(build.toString()) {} bool operator==(const SemVer& other) const { diff --git a/include/ShockerModelType.h b/include/ShockerModelType.h index aa2e7f61..edb9cd36 100644 --- a/include/ShockerModelType.h +++ b/include/ShockerModelType.h @@ -24,6 +24,16 @@ namespace OpenShock { return true; } + if (strcasecmp(str, "petrainer998dr") == 0) { + out = ShockerModelType::Petrainer998DR; + return true; + } + + if (allowTypo && strcasecmp(str, "pettrainer998dr") == 0) { + out = ShockerModelType::Petrainer998DR; + return true; + } + return false; } } // namespace OpenShock diff --git a/include/StringView.h b/include/StringView.h index 959afe06..72111eca 100644 --- a/include/StringView.h +++ b/include/StringView.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include @@ -25,10 +27,13 @@ namespace OpenShock { constexpr StringView(const char* const ptr, std::size_t len) : _ptrBeg(ptr), _ptrEnd(ptr + len) { } constexpr StringView(const char* const ptrBeg, const char* const ptrEnd) : _ptrBeg(ptrBeg), _ptrEnd(ptrEnd) { } constexpr StringView(const StringView& str) : _ptrBeg(str._ptrBeg), _ptrEnd(str._ptrEnd) { } - StringView(const String& str) : _ptrBeg(str.c_str()), _ptrEnd(str.c_str() + str.length()) { } - StringView(const std::string& str) : _ptrBeg(str.c_str()), _ptrEnd(str.c_str() + str.size()) { } + inline StringView(const String& str) : _ptrBeg(str.c_str()), _ptrEnd(str.c_str() + str.length()) { } + inline StringView(const std::string& str) : _ptrBeg(str.c_str()), _ptrEnd(str.c_str() + str.size()) { } + inline StringView(const flatbuffers::String& str) : _ptrBeg(str.c_str()), _ptrEnd(str.c_str() + str.size()) { } constexpr bool isNull() const { return _ptrBeg == nullptr || _ptrEnd == nullptr; } + constexpr bool isEmpty() const { return _ptrBeg >= _ptrEnd; } + constexpr bool isNullOrEmpty() const { return isNull() || isEmpty(); } constexpr const char* data() const { return _ptrBeg; } @@ -42,14 +47,14 @@ namespace OpenShock { constexpr char back() const { return *(_ptrEnd - 1); } constexpr std::size_t size() const { - if (isNull()) return 0; + if (isNullOrEmpty()) { + return 0; + } return _ptrEnd - _ptrBeg; } constexpr std::size_t length() const { return size(); } - constexpr bool isNullOrEmpty() const { return size() == 0; } - constexpr StringView substr(std::size_t pos, std::size_t count = StringView::npos) const { if (isNullOrEmpty()) { return *this; @@ -69,6 +74,10 @@ namespace OpenShock { } constexpr std::size_t find(char needle, std::size_t pos = 0) const { + if (isNullOrEmpty() || pos >= size()) { + return StringView::npos; + } + std::size_t _size = this->size(); for (std::size_t i = pos; i < _size; ++i) { @@ -79,8 +88,8 @@ namespace OpenShock { return StringView::npos; } - std::size_t find(StringView needle, std::size_t pos = 0) const { - if (isNullOrEmpty() || pos + needle.size() >= size()) { + inline std::size_t find(StringView needle, std::size_t pos = 0) const { + if (isNullOrEmpty() || needle.isNullOrEmpty() || pos + needle.size() >= size()) { return StringView::npos; } @@ -92,7 +101,11 @@ namespace OpenShock { return ptr - _ptrBeg; } - std::size_t rfind(char needle, std::size_t pos = StringView::npos) const { + inline std::size_t rfind(char needle, std::size_t pos = StringView::npos) const { + if (isNullOrEmpty() || pos >= size()) { + return StringView::npos; + } + std::size_t _size = this->size(); if (pos == StringView::npos) { @@ -109,8 +122,8 @@ namespace OpenShock { return StringView::npos; } - std::size_t rfind(StringView needle, std::size_t pos = StringView::npos) const { - if (isNullOrEmpty()) { + inline std::size_t rfind(StringView needle, std::size_t pos = StringView::npos) const { + if (isNullOrEmpty() || needle.isNullOrEmpty() || pos + needle.size() >= size()) { return StringView::npos; } @@ -128,7 +141,7 @@ namespace OpenShock { return ptr - _ptrBeg; } - StringView beforeDelimiter(char delimiter) const { + inline StringView beforeDelimiter(char delimiter) const { std::size_t pos = find(delimiter); if (pos != StringView::npos) { return substr(0, pos); @@ -136,7 +149,7 @@ namespace OpenShock { return *this; } - StringView beforeDelimiter(StringView delimiter) const { + inline StringView beforeDelimiter(StringView delimiter) const { std::size_t pos = find(delimiter); if (pos != StringView::npos) { return substr(0, pos); @@ -145,7 +158,7 @@ namespace OpenShock { return *this; } - StringView afterDelimiter(char delimiter) const { + inline StringView afterDelimiter(char delimiter) const { std::size_t pos = find(delimiter); if (pos != StringView::npos) { return substr(pos + 1); @@ -153,7 +166,7 @@ namespace OpenShock { return *this; } - StringView afterDelimiter(StringView delimiter) const { + inline StringView afterDelimiter(StringView delimiter) const { std::size_t pos = find(delimiter); if (pos != StringView::npos) { return substr(pos + delimiter.size()); @@ -162,11 +175,16 @@ namespace OpenShock { return *this; } - std::vector split(char delimiter) const { - std::vector result; + inline std::vector split(char delimiter, std::size_t maxSplits = StringView::npos) const { + if (isNullOrEmpty()) { + return {}; + } - std::size_t pos = 0; - while (pos < size()) { + std::vector result = {}; + + std::size_t pos = 0; + std::size_t splits = 0; + while (pos < size() && splits < maxSplits) { std::size_t nextPos = find(delimiter, pos); if (nextPos == StringView::npos) { nextPos = size(); @@ -174,12 +192,21 @@ namespace OpenShock { result.push_back(substr(pos, nextPos - pos)); pos = nextPos + 1; + ++splits; + } + + if (pos < size()) { + result.push_back(substr(pos)); } return result; } - std::vector split(StringView delimiter) const { - std::vector result; + inline std::vector split(StringView delimiter) const { + if (isNullOrEmpty() || delimiter.isNullOrEmpty()) { + return {}; + } + + std::vector result = {}; std::size_t pos = 0; while (pos < size()) { @@ -194,8 +221,12 @@ namespace OpenShock { return result; } - std::vector split(std::function predicate) const { - std::vector result; + inline std::vector split(std::function predicate) const { + if (isNullOrEmpty()) { + return {}; + } + + std::vector result = {}; const char* start = nullptr; for (const char* ptr = _ptrBeg; ptr < _ptrEnd; ++ptr) { @@ -216,10 +247,10 @@ namespace OpenShock { return result; } - std::vector splitLines() const { + inline std::vector splitLines() const { return split([](char c) { return c == '\r' || c == '\n'; }); } - std::vector splitWhitespace() const { return split(isspace); } + inline std::vector splitWhitespace() const { return split(isspace); } constexpr bool startsWith(char needle) const { if (isNull()) { @@ -230,29 +261,29 @@ namespace OpenShock { } constexpr bool startsWith(StringView needle) const { if (isNull()) { - return false; + return needle.isNullOrEmpty(); } return _strEquals(_ptrBeg, _ptrBeg + needle.size(), needle._ptrBeg, needle._ptrEnd); } constexpr bool endsWith(char needle) const { - if (isNull()) { + if (isNullOrEmpty()) { return false; } return _ptrEnd[-1] == needle; } constexpr bool endsWith(StringView needle) const { - if (isNull()) { - return false; + if (isNullOrEmpty()) { + return needle.isNullOrEmpty(); } return _strEquals(_ptrEnd - needle.size(), _ptrEnd, needle._ptrBeg, needle._ptrEnd); } constexpr StringView& trimLeft() { - if (isNull()) { + if (isNullOrEmpty()) { return *this; } @@ -264,7 +295,7 @@ namespace OpenShock { } constexpr StringView& trimRight() { - if (isNull()) { + if (isNullOrEmpty()) { return *this; } @@ -286,16 +317,16 @@ namespace OpenShock { _ptrEnd = nullptr; } - String toArduinoString() const { - if (isNull()) { + inline String toArduinoString() const { + if (isNullOrEmpty()) { return String(); } return String(_ptrBeg, size()); } - std::string toString() const { - if (isNull()) { + inline std::string toString() const { + if (isNullOrEmpty()) { return std::string(); } @@ -309,13 +340,9 @@ namespace OpenShock { explicit operator std::string() const { return toString(); } /// Returns a reference to the character at the specified index, Going out of bounds is undefined behavior - constexpr char const& operator[](int index) const { - return _ptrBeg[index]; - } + constexpr char const& operator[](int index) const { return _ptrBeg[index]; } /// Returns a const reference to the character at the specified index, Going out of bounds is undefined behavior - constexpr char const& operator[](std::size_t index) const { - return _ptrBeg[index]; - } + constexpr char const& operator[](std::size_t index) const { return _ptrBeg[index]; } constexpr bool operator==(const StringView& other) { if (this == &other) return true; @@ -325,15 +352,17 @@ namespace OpenShock { constexpr bool operator!=(const StringView& other) { return !(*this == other); } constexpr bool operator==(const char* const other) { return *this == StringView(other); } constexpr bool operator!=(const char* const other) { return !(*this == other); } + inline bool operator==(const std::string& other) { return *this == StringView(other); } + inline bool operator!=(const std::string& other) { return !(*this == other); } - bool operator<(const StringView& other) const { + inline bool operator<(const StringView& other) const { if (this == &other) return false; return std::lexicographical_compare(_ptrBeg, _ptrEnd, other._ptrBeg, other._ptrEnd); } - bool operator<=(const StringView& other) const { return *this < other || *this == other; } - bool operator>(const StringView& other) const { return !(*this <= other); } - bool operator>=(const StringView& other) const { return !(*this < other); } + inline bool operator<=(const StringView& other) const { return *this < other || *this == other; } + inline bool operator>(const StringView& other) const { return !(*this <= other); } + inline bool operator>=(const StringView& other) const { return !(*this < other); } constexpr StringView& operator=(const char* const ptr) { _ptrBeg = ptr; @@ -345,7 +374,7 @@ namespace OpenShock { _ptrEnd = str._ptrEnd; return *this; } - StringView& operator=(const std::string& str) { + inline StringView& operator=(const std::string& str) { _ptrBeg = str.c_str(); _ptrEnd = str.c_str() + str.size(); return *this; @@ -356,9 +385,15 @@ namespace OpenShock { if (aStart == bStart && aEnd == bEnd) { return true; } - if (aStart == nullptr || aEnd == nullptr || bStart == nullptr || bEnd == nullptr) { + if (aStart == nullptr || bStart == nullptr) { return false; } + if (aEnd == nullptr) { + aEnd = _getStringEnd(aStart); + } + if (bEnd == nullptr) { + bEnd = _getStringEnd(bStart); + } std::size_t aLen = aEnd - aStart; std::size_t bLen = bEnd - bStart; @@ -392,3 +427,43 @@ namespace OpenShock { const char* _ptrEnd; }; } // namespace OpenShock + +inline OpenShock::StringView operator"" _sv(const char* str, std::size_t len) { + return OpenShock::StringView(str, len); +} + +namespace std { + template<> + struct hash { + std::size_t operator()(OpenShock::StringView str) const { + std::size_t hash = 7; + + for (int i = 0; i < str.size(); ++i) { + hash = hash * 31 + str[i]; + } + + return hash; + } + }; + + struct hash_ci { + std::size_t operator()(OpenShock::StringView str) const { + std::size_t hash = 7; + + for (int i = 0; i < str.size(); ++i) { + hash = hash * 31 + tolower(str[i]); + } + + return hash; + } + }; + + template<> + struct less { + bool operator()(OpenShock::StringView a, OpenShock::StringView b) const { return a < b; } + }; + + struct equals_ci { + bool operator()(OpenShock::StringView a, OpenShock::StringView b) const { return strncasecmp(a.data(), b.data(), std::max(a.size(), b.size())) == 0; } + }; +} // namespace std diff --git a/include/config/BackendConfig.h b/include/config/BackendConfig.h index f3dc2827..4f2b3b48 100644 --- a/include/config/BackendConfig.h +++ b/include/config/BackendConfig.h @@ -1,16 +1,18 @@ #pragma once #include "config/ConfigBase.h" +#include "StringView.h" #include namespace OpenShock::Config { struct BackendConfig : public ConfigBase { BackendConfig(); - BackendConfig(const std::string& domain, const std::string& authToken); + BackendConfig(StringView domain, StringView authToken, StringView lcgOverride); std::string domain; std::string authToken; + std::string lcgOverride; void ToDefault() override; diff --git a/include/config/Config.h b/include/config/Config.h index c5b52431..5c0033eb 100644 --- a/include/config/Config.h +++ b/include/config/Config.h @@ -7,9 +7,9 @@ #include "config/SerialInputConfig.h" #include "config/WiFiConfig.h" #include "config/WiFiCredentials.h" +#include "StringView.h" #include -#include #include namespace OpenShock::Config { @@ -17,7 +17,7 @@ namespace OpenShock::Config { /* GetAsJSON and SaveFromJSON are used for Reading/Writing the config file in its human-readable form. */ std::string GetAsJSON(bool withSensitiveData); - bool SaveFromJSON(const std::string& json); + bool SaveFromJSON(StringView json); /* GetAsFlatBuffer and SaveFromFlatBuffer are used for Reading/Writing the config file in its binary form. */ flatbuffers::Offset GetAsFlatBuffer(flatbuffers::FlatBufferBuilder& builder, bool withSensitiveData); @@ -57,7 +57,7 @@ namespace OpenShock::Config { bool AnyWiFiCredentials(std::function predicate); - std::uint8_t AddWiFiCredentials(const std::string& ssid, const std::string& password); + std::uint8_t AddWiFiCredentials(StringView ssid, StringView password); bool TryGetWiFiCredentialsByID(std::uint8_t id, WiFiCredentials& out); bool TryGetWiFiCredentialsBySSID(const char* ssid, WiFiCredentials& out); std::uint8_t GetWiFiCredentialsIDbySSID(const char* ssid); @@ -69,8 +69,14 @@ namespace OpenShock::Config { bool GetOtaUpdateStep(OtaUpdateStep& out); bool SetOtaUpdateStep(OtaUpdateStep updateStep); + bool GetBackendDomain(std::string& out); + bool SetBackendDomain(StringView domain); bool HasBackendAuthToken(); bool GetBackendAuthToken(std::string& out); - bool SetBackendAuthToken(const std::string& token); + bool SetBackendAuthToken(StringView token); bool ClearBackendAuthToken(); + bool HasBackendLCGOverride(); + bool GetBackendLCGOverride(std::string& out); + bool SetBackendLCGOverride(StringView lcgOverride); + bool ClearBackendLCGOverride(); } // namespace OpenShock::Config diff --git a/include/config/WiFiConfig.h b/include/config/WiFiConfig.h index 1f657d78..e29dfafc 100644 --- a/include/config/WiFiConfig.h +++ b/include/config/WiFiConfig.h @@ -2,6 +2,7 @@ #include "config/ConfigBase.h" #include "config/WiFiCredentials.h" +#include "StringView.h" #include #include @@ -9,7 +10,7 @@ namespace OpenShock::Config { struct WiFiConfig : public ConfigBase { WiFiConfig(); - WiFiConfig(const std::string& accessPointSSID, const std::string& hostname, const std::vector& credentialsList); + WiFiConfig(StringView accessPointSSID, StringView hostname, const std::vector& credentialsList); std::string accessPointSSID; std::string hostname; diff --git a/include/config/WiFiCredentials.h b/include/config/WiFiCredentials.h index 10a9d03a..44c94660 100644 --- a/include/config/WiFiCredentials.h +++ b/include/config/WiFiCredentials.h @@ -1,13 +1,14 @@ #pragma once #include "config/ConfigBase.h" +#include "StringView.h" #include namespace OpenShock::Config { struct WiFiCredentials : public ConfigBase { WiFiCredentials(); - WiFiCredentials(std::uint8_t id, const std::string& ssid, const std::string& password); + WiFiCredentials(std::uint8_t id, StringView ssid, StringView password); std::uint8_t id; std::string ssid; diff --git a/include/http/HTTPRequestManager.h b/include/http/HTTPRequestManager.h index 5ac1700e..4cf5e26e 100644 --- a/include/http/HTTPRequestManager.h +++ b/include/http/HTTPRequestManager.h @@ -10,6 +10,7 @@ namespace OpenShock::HTTP { enum class RequestResult : std::uint8_t { + InternalError, // Internal error InvalidURL, // Invalid URL RequestFailed, // Failed to start request TimedOut, // Request timed out diff --git a/include/http/JsonAPI.h b/include/http/JsonAPI.h index d58ec35d..934c50a0 100644 --- a/include/http/JsonAPI.h +++ b/include/http/JsonAPI.h @@ -12,10 +12,10 @@ namespace OpenShock::HTTP::JsonAPI { /// @brief Gets the device info for the given device token. Valid response codes: 200, 401 /// @param deviceToken /// @return - HTTP::Response GetDeviceInfo(const String& deviceToken); + HTTP::Response GetDeviceInfo(StringView deviceToken); /// @brief Requests a Live Control Gateway to connect to. Valid response codes: 200, 401 /// @param deviceToken /// @return - HTTP::Response AssignLcg(const String& deviceToken); + HTTP::Response AssignLcg(StringView deviceToken); } // namespace OpenShock::HTTP::JsonAPI diff --git a/include/radio/rmt/MainEncoder.h b/include/radio/rmt/MainEncoder.h index 1464a087..c2681795 100644 --- a/include/radio/rmt/MainEncoder.h +++ b/include/radio/rmt/MainEncoder.h @@ -11,5 +11,7 @@ namespace OpenShock::Rmt { std::vector GetSequence(ShockerModelType model, std::uint16_t shockerId, OpenShock::ShockerCommandType type, std::uint8_t intensity); - std::shared_ptr> GetZeroSequence(ShockerModelType model, std::uint16_t shockerId); + inline std::vector GetZeroSequence(ShockerModelType model, std::uint16_t shockerId) { + return GetSequence(model, shockerId, ShockerCommandType::Vibrate, 0); + } } diff --git a/include/radio/rmt/Petrainer998DREncoder.h b/include/radio/rmt/Petrainer998DREncoder.h new file mode 100644 index 00000000..b55cd271 --- /dev/null +++ b/include/radio/rmt/Petrainer998DREncoder.h @@ -0,0 +1,12 @@ +#pragma once + +#include "ShockerCommandType.h" + +#include + +#include +#include + +namespace OpenShock::Rmt::Petrainer998DREncoder { + std::vector GetSequence(std::uint16_t shockerId, OpenShock::ShockerCommandType type, std::uint8_t intensity); +} diff --git a/include/serialization/JsonAPI.h b/include/serialization/JsonAPI.h index 04d48282..a59bf81f 100644 --- a/include/serialization/JsonAPI.h +++ b/include/serialization/JsonAPI.h @@ -9,6 +9,18 @@ #include namespace OpenShock::Serialization::JsonAPI { + struct LcgInstanceDetailsResponse { + std::string name; + std::string version; + std::string currentTime; + std::string countryCode; + std::string fqdn; + }; + struct BackendVersionResponse { + std::string version; + std::string commit; + std::string currentTime; + }; struct AccountLinkResponse { std::string authToken; }; @@ -27,6 +39,8 @@ namespace OpenShock::Serialization::JsonAPI { std::string country; }; + bool ParseLcgInstanceDetailsJsonResponse(int code, const cJSON* root, LcgInstanceDetailsResponse& out); + bool ParseBackendVersionJsonResponse(int code, const cJSON* root, BackendVersionResponse& out); bool ParseAccountLinkJsonResponse(int code, const cJSON* root, AccountLinkResponse& out); bool ParseDeviceInfoJsonResponse(int code, const cJSON* root, DeviceInfoResponse& out); bool ParseAssignLcgJsonResponse(int code, const cJSON* root, AssignLcgResponse& out); diff --git a/include/serialization/_fbs/ConfigFile_generated.h b/include/serialization/_fbs/ConfigFile_generated.h index 434539c5..93a6307b 100644 --- a/include/serialization/_fbs/ConfigFile_generated.h +++ b/include/serialization/_fbs/ConfigFile_generated.h @@ -8,9 +8,9 @@ // Ensure the included flatbuffers.h is the same version as when this file was // generated, otherwise it may not be compatible. -static_assert(FLATBUFFERS_VERSION_MAJOR == 23 && - FLATBUFFERS_VERSION_MINOR == 5 && - FLATBUFFERS_VERSION_REVISION == 26, +static_assert(FLATBUFFERS_VERSION_MAJOR == 24 && + FLATBUFFERS_VERSION_MINOR == 3 && + FLATBUFFERS_VERSION_REVISION == 25, "Non-compatible flatbuffers version included"); namespace OpenShock { @@ -419,7 +419,8 @@ struct BackendConfig FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { } enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { VT_DOMAIN = 4, - VT_AUTH_TOKEN = 6 + VT_AUTH_TOKEN = 6, + VT_LCG_OVERRIDE = 8 }; /// Domain name of the backend server, e.g. "api.shocklink.net" const ::flatbuffers::String *domain() const { @@ -429,12 +430,18 @@ struct BackendConfig FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { const ::flatbuffers::String *auth_token() const { return GetPointer(VT_AUTH_TOKEN); } + /// Override the Live-Control-Gateway (LCG) URL + const ::flatbuffers::String *lcg_override() const { + return GetPointer(VT_LCG_OVERRIDE); + } bool Verify(::flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_DOMAIN) && verifier.VerifyString(domain()) && VerifyOffset(verifier, VT_AUTH_TOKEN) && verifier.VerifyString(auth_token()) && + VerifyOffset(verifier, VT_LCG_OVERRIDE) && + verifier.VerifyString(lcg_override()) && verifier.EndTable(); } }; @@ -449,6 +456,9 @@ struct BackendConfigBuilder { void add_auth_token(::flatbuffers::Offset<::flatbuffers::String> auth_token) { fbb_.AddOffset(BackendConfig::VT_AUTH_TOKEN, auth_token); } + void add_lcg_override(::flatbuffers::Offset<::flatbuffers::String> lcg_override) { + fbb_.AddOffset(BackendConfig::VT_LCG_OVERRIDE, lcg_override); + } explicit BackendConfigBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); @@ -463,8 +473,10 @@ struct BackendConfigBuilder { inline ::flatbuffers::Offset CreateBackendConfig( ::flatbuffers::FlatBufferBuilder &_fbb, ::flatbuffers::Offset<::flatbuffers::String> domain = 0, - ::flatbuffers::Offset<::flatbuffers::String> auth_token = 0) { + ::flatbuffers::Offset<::flatbuffers::String> auth_token = 0, + ::flatbuffers::Offset<::flatbuffers::String> lcg_override = 0) { BackendConfigBuilder builder_(_fbb); + builder_.add_lcg_override(lcg_override); builder_.add_auth_token(auth_token); builder_.add_domain(domain); return builder_.Finish(); @@ -478,13 +490,16 @@ struct BackendConfig::Traits { inline ::flatbuffers::Offset CreateBackendConfigDirect( ::flatbuffers::FlatBufferBuilder &_fbb, const char *domain = nullptr, - const char *auth_token = nullptr) { + const char *auth_token = nullptr, + const char *lcg_override = nullptr) { auto domain__ = domain ? _fbb.CreateString(domain) : 0; auto auth_token__ = auth_token ? _fbb.CreateString(auth_token) : 0; + auto lcg_override__ = lcg_override ? _fbb.CreateString(lcg_override) : 0; return OpenShock::Serialization::Configuration::CreateBackendConfig( _fbb, domain__, - auth_token__); + auth_token__, + lcg_override__); } struct SerialInputConfig FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { diff --git a/include/serialization/_fbs/DeviceToGatewayMessage_generated.h b/include/serialization/_fbs/DeviceToGatewayMessage_generated.h index a853d7f2..0dddb692 100644 --- a/include/serialization/_fbs/DeviceToGatewayMessage_generated.h +++ b/include/serialization/_fbs/DeviceToGatewayMessage_generated.h @@ -8,9 +8,9 @@ // Ensure the included flatbuffers.h is the same version as when this file was // generated, otherwise it may not be compatible. -static_assert(FLATBUFFERS_VERSION_MAJOR == 23 && - FLATBUFFERS_VERSION_MINOR == 5 && - FLATBUFFERS_VERSION_REVISION == 26, +static_assert(FLATBUFFERS_VERSION_MAJOR == 24 && + FLATBUFFERS_VERSION_MINOR == 3 && + FLATBUFFERS_VERSION_REVISION == 25, "Non-compatible flatbuffers version included"); #include "FirmwareBootType_generated.h" diff --git a/include/serialization/_fbs/DeviceToLocalMessage_generated.h b/include/serialization/_fbs/DeviceToLocalMessage_generated.h index 58e04b48..2a86a2a0 100644 --- a/include/serialization/_fbs/DeviceToLocalMessage_generated.h +++ b/include/serialization/_fbs/DeviceToLocalMessage_generated.h @@ -8,9 +8,9 @@ // Ensure the included flatbuffers.h is the same version as when this file was // generated, otherwise it may not be compatible. -static_assert(FLATBUFFERS_VERSION_MAJOR == 23 && - FLATBUFFERS_VERSION_MINOR == 5 && - FLATBUFFERS_VERSION_REVISION == 26, +static_assert(FLATBUFFERS_VERSION_MAJOR == 24 && + FLATBUFFERS_VERSION_MINOR == 3 && + FLATBUFFERS_VERSION_REVISION == 25, "Non-compatible flatbuffers version included"); #include "ConfigFile_generated.h" diff --git a/include/serialization/_fbs/FirmwareBootType_generated.h b/include/serialization/_fbs/FirmwareBootType_generated.h index 331c57a0..af54adce 100644 --- a/include/serialization/_fbs/FirmwareBootType_generated.h +++ b/include/serialization/_fbs/FirmwareBootType_generated.h @@ -8,9 +8,9 @@ // Ensure the included flatbuffers.h is the same version as when this file was // generated, otherwise it may not be compatible. -static_assert(FLATBUFFERS_VERSION_MAJOR == 23 && - FLATBUFFERS_VERSION_MINOR == 5 && - FLATBUFFERS_VERSION_REVISION == 26, +static_assert(FLATBUFFERS_VERSION_MAJOR == 24 && + FLATBUFFERS_VERSION_MINOR == 3 && + FLATBUFFERS_VERSION_REVISION == 25, "Non-compatible flatbuffers version included"); namespace OpenShock { diff --git a/include/serialization/_fbs/GatewayToDeviceMessage_generated.h b/include/serialization/_fbs/GatewayToDeviceMessage_generated.h index 2f79f58a..5b00a6f8 100644 --- a/include/serialization/_fbs/GatewayToDeviceMessage_generated.h +++ b/include/serialization/_fbs/GatewayToDeviceMessage_generated.h @@ -8,9 +8,9 @@ // Ensure the included flatbuffers.h is the same version as when this file was // generated, otherwise it may not be compatible. -static_assert(FLATBUFFERS_VERSION_MAJOR == 23 && - FLATBUFFERS_VERSION_MINOR == 5 && - FLATBUFFERS_VERSION_REVISION == 26, +static_assert(FLATBUFFERS_VERSION_MAJOR == 24 && + FLATBUFFERS_VERSION_MINOR == 3 && + FLATBUFFERS_VERSION_REVISION == 25, "Non-compatible flatbuffers version included"); #include "SemVer_generated.h" diff --git a/include/serialization/_fbs/LocalToDeviceMessage_generated.h b/include/serialization/_fbs/LocalToDeviceMessage_generated.h index 24afa8d6..26ef89b6 100644 --- a/include/serialization/_fbs/LocalToDeviceMessage_generated.h +++ b/include/serialization/_fbs/LocalToDeviceMessage_generated.h @@ -8,9 +8,9 @@ // Ensure the included flatbuffers.h is the same version as when this file was // generated, otherwise it may not be compatible. -static_assert(FLATBUFFERS_VERSION_MAJOR == 23 && - FLATBUFFERS_VERSION_MINOR == 5 && - FLATBUFFERS_VERSION_REVISION == 26, +static_assert(FLATBUFFERS_VERSION_MAJOR == 24 && + FLATBUFFERS_VERSION_MINOR == 3 && + FLATBUFFERS_VERSION_REVISION == 25, "Non-compatible flatbuffers version included"); namespace OpenShock { diff --git a/include/serialization/_fbs/SemVer_generated.h b/include/serialization/_fbs/SemVer_generated.h index f26346fb..6043d6a2 100644 --- a/include/serialization/_fbs/SemVer_generated.h +++ b/include/serialization/_fbs/SemVer_generated.h @@ -8,9 +8,9 @@ // Ensure the included flatbuffers.h is the same version as when this file was // generated, otherwise it may not be compatible. -static_assert(FLATBUFFERS_VERSION_MAJOR == 23 && - FLATBUFFERS_VERSION_MINOR == 5 && - FLATBUFFERS_VERSION_REVISION == 26, +static_assert(FLATBUFFERS_VERSION_MAJOR == 24 && + FLATBUFFERS_VERSION_MINOR == 3 && + FLATBUFFERS_VERSION_REVISION == 25, "Non-compatible flatbuffers version included"); namespace OpenShock { diff --git a/include/serialization/_fbs/ShockerCommandType_generated.h b/include/serialization/_fbs/ShockerCommandType_generated.h index 86720796..c84f66ff 100644 --- a/include/serialization/_fbs/ShockerCommandType_generated.h +++ b/include/serialization/_fbs/ShockerCommandType_generated.h @@ -8,9 +8,9 @@ // Ensure the included flatbuffers.h is the same version as when this file was // generated, otherwise it may not be compatible. -static_assert(FLATBUFFERS_VERSION_MAJOR == 23 && - FLATBUFFERS_VERSION_MINOR == 5 && - FLATBUFFERS_VERSION_REVISION == 26, +static_assert(FLATBUFFERS_VERSION_MAJOR == 24 && + FLATBUFFERS_VERSION_MINOR == 3 && + FLATBUFFERS_VERSION_REVISION == 25, "Non-compatible flatbuffers version included"); namespace OpenShock { diff --git a/include/serialization/_fbs/ShockerModelType_generated.h b/include/serialization/_fbs/ShockerModelType_generated.h index f55fc512..34cb0be7 100644 --- a/include/serialization/_fbs/ShockerModelType_generated.h +++ b/include/serialization/_fbs/ShockerModelType_generated.h @@ -8,9 +8,9 @@ // Ensure the included flatbuffers.h is the same version as when this file was // generated, otherwise it may not be compatible. -static_assert(FLATBUFFERS_VERSION_MAJOR == 23 && - FLATBUFFERS_VERSION_MINOR == 5 && - FLATBUFFERS_VERSION_REVISION == 26, +static_assert(FLATBUFFERS_VERSION_MAJOR == 24 && + FLATBUFFERS_VERSION_MINOR == 3 && + FLATBUFFERS_VERSION_REVISION == 25, "Non-compatible flatbuffers version included"); namespace OpenShock { @@ -20,29 +20,32 @@ namespace Types { enum class ShockerModelType : uint8_t { CaiXianlin = 0, Petrainer = 1, + Petrainer998DR = 2, MIN = CaiXianlin, - MAX = Petrainer + MAX = Petrainer998DR }; -inline const ShockerModelType (&EnumValuesShockerModelType())[2] { +inline const ShockerModelType (&EnumValuesShockerModelType())[3] { static const ShockerModelType values[] = { ShockerModelType::CaiXianlin, - ShockerModelType::Petrainer + ShockerModelType::Petrainer, + ShockerModelType::Petrainer998DR }; return values; } inline const char * const *EnumNamesShockerModelType() { - static const char * const names[3] = { + static const char * const names[4] = { "CaiXianlin", "Petrainer", + "Petrainer998DR", nullptr }; return names; } inline const char *EnumNameShockerModelType(ShockerModelType e) { - if (::flatbuffers::IsOutRange(e, ShockerModelType::CaiXianlin, ShockerModelType::Petrainer)) return ""; + if (::flatbuffers::IsOutRange(e, ShockerModelType::CaiXianlin, ShockerModelType::Petrainer998DR)) return ""; const size_t index = static_cast(e); return EnumNamesShockerModelType()[index]; } diff --git a/include/serialization/_fbs/WifiAuthMode_generated.h b/include/serialization/_fbs/WifiAuthMode_generated.h index e7e6af29..63124f57 100644 --- a/include/serialization/_fbs/WifiAuthMode_generated.h +++ b/include/serialization/_fbs/WifiAuthMode_generated.h @@ -8,9 +8,9 @@ // Ensure the included flatbuffers.h is the same version as when this file was // generated, otherwise it may not be compatible. -static_assert(FLATBUFFERS_VERSION_MAJOR == 23 && - FLATBUFFERS_VERSION_MINOR == 5 && - FLATBUFFERS_VERSION_REVISION == 26, +static_assert(FLATBUFFERS_VERSION_MAJOR == 24 && + FLATBUFFERS_VERSION_MINOR == 3 && + FLATBUFFERS_VERSION_REVISION == 25, "Non-compatible flatbuffers version included"); namespace OpenShock { diff --git a/include/serialization/_fbs/WifiNetworkEventType_generated.h b/include/serialization/_fbs/WifiNetworkEventType_generated.h index 8ceb27d3..e382e547 100644 --- a/include/serialization/_fbs/WifiNetworkEventType_generated.h +++ b/include/serialization/_fbs/WifiNetworkEventType_generated.h @@ -8,9 +8,9 @@ // Ensure the included flatbuffers.h is the same version as when this file was // generated, otherwise it may not be compatible. -static_assert(FLATBUFFERS_VERSION_MAJOR == 23 && - FLATBUFFERS_VERSION_MINOR == 5 && - FLATBUFFERS_VERSION_REVISION == 26, +static_assert(FLATBUFFERS_VERSION_MAJOR == 24 && + FLATBUFFERS_VERSION_MINOR == 3 && + FLATBUFFERS_VERSION_REVISION == 25, "Non-compatible flatbuffers version included"); namespace OpenShock { diff --git a/include/serialization/_fbs/WifiNetwork_generated.h b/include/serialization/_fbs/WifiNetwork_generated.h index 9cc60cec..f549349e 100644 --- a/include/serialization/_fbs/WifiNetwork_generated.h +++ b/include/serialization/_fbs/WifiNetwork_generated.h @@ -8,9 +8,9 @@ // Ensure the included flatbuffers.h is the same version as when this file was // generated, otherwise it may not be compatible. -static_assert(FLATBUFFERS_VERSION_MAJOR == 23 && - FLATBUFFERS_VERSION_MINOR == 5 && - FLATBUFFERS_VERSION_REVISION == 26, +static_assert(FLATBUFFERS_VERSION_MAJOR == 24 && + FLATBUFFERS_VERSION_MINOR == 3 && + FLATBUFFERS_VERSION_REVISION == 25, "Non-compatible flatbuffers version included"); #include "WifiAuthMode_generated.h" diff --git a/include/serialization/_fbs/WifiScanStatus_generated.h b/include/serialization/_fbs/WifiScanStatus_generated.h index 473682f0..285481dc 100644 --- a/include/serialization/_fbs/WifiScanStatus_generated.h +++ b/include/serialization/_fbs/WifiScanStatus_generated.h @@ -8,9 +8,9 @@ // Ensure the included flatbuffers.h is the same version as when this file was // generated, otherwise it may not be compatible. -static_assert(FLATBUFFERS_VERSION_MAJOR == 23 && - FLATBUFFERS_VERSION_MINOR == 5 && - FLATBUFFERS_VERSION_REVISION == 26, +static_assert(FLATBUFFERS_VERSION_MAJOR == 24 && + FLATBUFFERS_VERSION_MINOR == 3 && + FLATBUFFERS_VERSION_REVISION == 25, "Non-compatible flatbuffers version included"); namespace OpenShock { diff --git a/include/wifi/WiFiManager.h b/include/wifi/WiFiManager.h index efab76d4..f3f2ba49 100644 --- a/include/wifi/WiFiManager.h +++ b/include/wifi/WiFiManager.h @@ -1,6 +1,7 @@ #pragma once #include "wifi/WiFiNetwork.h" +#include "StringView.h" #include #include @@ -15,7 +16,7 @@ namespace OpenShock::WiFiManager { /// @param ssid SSID of the network /// @param password Password of the network /// @return True if the network was saved successfully - bool Save(const char* ssid, const std::string& password); + bool Save(const char* ssid, StringView password); /// @brief Removes a network from the config by it's SSID /// @param ssid SSID of the network diff --git a/schemas/ConfigFile.fbs b/schemas/ConfigFile.fbs index d7e88d8f..5810b956 100644 --- a/schemas/ConfigFile.fbs +++ b/schemas/ConfigFile.fbs @@ -42,6 +42,9 @@ table BackendConfig { /// Authentication token for the backend server auth_token:string; + + /// Override the Live-Control-Gateway (LCG) URL + lcg_override:string; } table SerialInputConfig { diff --git a/schemas/Types/ShockerModelType.fbs b/schemas/Types/ShockerModelType.fbs index 606e3df4..8eb555d7 100644 --- a/schemas/Types/ShockerModelType.fbs +++ b/schemas/Types/ShockerModelType.fbs @@ -2,5 +2,6 @@ namespace OpenShock.Serialization.Types; enum ShockerModelType : uint8 { CaiXianlin = 0, - Petrainer = 1 + Petrainer = 1, + Petrainer998DR = 2 } diff --git a/scripts/flatc.exe b/scripts/flatc.exe index 7dd09fc9..cdbd9bf2 100644 Binary files a/scripts/flatc.exe and b/scripts/flatc.exe differ diff --git a/src/CaptivePortalInstance.cpp b/src/CaptivePortalInstance.cpp index bdb43487..ae7d2780 100644 --- a/src/CaptivePortalInstance.cpp +++ b/src/CaptivePortalInstance.cpp @@ -16,13 +16,13 @@ static const char* TAG = "CaptivePortalInstance"; -constexpr std::uint16_t HTTP_PORT = 80; -constexpr std::uint16_t WEBSOCKET_PORT = 81; -constexpr std::uint16_t DNS_PORT = 53; -constexpr std::uint32_t WEBSOCKET_PING_INTERVAL = 10'000; -constexpr std::uint32_t WEBSOCKET_PING_TIMEOUT = 1000; -constexpr std::uint8_t WEBSOCKET_PING_RETRIES = 3; -constexpr std::uint32_t WEBSOCKET_UPDATE_INTERVAL = 10; // 10ms / 100Hz +const std::uint16_t HTTP_PORT = 80; +const std::uint16_t WEBSOCKET_PORT = 81; +const std::uint16_t DNS_PORT = 53; +const std::uint32_t WEBSOCKET_PING_INTERVAL = 10'000; +const std::uint32_t WEBSOCKET_PING_TIMEOUT = 1000; +const std::uint8_t WEBSOCKET_PING_RETRIES = 3; +const std::uint32_t WEBSOCKET_UPDATE_INTERVAL = 10; // 10ms / 100Hz using namespace OpenShock; diff --git a/src/CompatibilityChecks.cpp b/src/CompatibilityChecks.cpp index 87ae9385..ffb4c970 100644 --- a/src/CompatibilityChecks.cpp +++ b/src/CompatibilityChecks.cpp @@ -1,7 +1,7 @@ #include "Common.h" #include "Chipset.h" -constexpr bool kIsValidOrUndefinedRfTxPin = OpenShock::IsValidOutputPin(OpenShock::Constants::GPIO_RF_TX) || OpenShock::Constants::GPIO_RF_TX == OpenShock::Constants::GPIO_INVALID; +const bool kIsValidOrUndefinedRfTxPin = OpenShock::IsValidOutputPin(OpenShock::Constants::GPIO_RF_TX) || OpenShock::Constants::GPIO_RF_TX == OpenShock::Constants::GPIO_INVALID; static_assert(kIsValidOrUndefinedRfTxPin , "OPENSHOCK_RF_TX_GPIO is not a valid output GPIO, and is not declared as bypassed by board specific definitions, refusing to compile"); #ifdef OPENSHOCK_ESTOP_PIN diff --git a/src/GatewayClient.cpp b/src/GatewayClient.cpp index 5e330371..64b89278 100644 --- a/src/GatewayClient.cpp +++ b/src/GatewayClient.cpp @@ -8,6 +8,7 @@ #include "serialization/WSGateway.h" #include "Time.h" #include "util/CertificateUtils.h" +#include "VisualStateManager.h" const char* const TAG = "GatewayClient"; @@ -36,7 +37,7 @@ void GatewayClient::connect(const char* lcgFqdn) { return; } - m_state = State::Connecting; + _setState(State::Connecting); // // ###### ######## ###### ## ## ######## #### ######## ## ## ######## #### ###### ## ## @@ -59,7 +60,7 @@ void GatewayClient::disconnect() { if (m_state != State::Connected) { return; } - m_state = State::Disconnecting; + _setState(State::Disconnecting); m_webSocket.disconnect(); } @@ -104,6 +105,27 @@ bool GatewayClient::loop() { return true; } +void GatewayClient::_setState(State state) { + if (m_state == state) { + return; + } + + m_state = state; + + switch (m_state) { + case State::Disconnected: + ESP_LOGI(TAG, "Disconnected from API"); + OpenShock::VisualStateManager::SetWebSocketConnected(false); + break; + case State::Connected: + ESP_LOGI(TAG, "Connected to API"); + OpenShock::VisualStateManager::SetWebSocketConnected(true); + break; + default: + break; + } +} + void GatewayClient::_sendKeepAlive() { ESP_LOGV(TAG, "Sending Gateway keep-alive message"); Serialization::Gateway::SerializeKeepAliveMessage([this](const std::uint8_t* data, std::size_t len) { return m_webSocket.sendBIN(data, len); }); @@ -146,12 +168,10 @@ void GatewayClient::_handleEvent(WStype_t type, std::uint8_t* payload, std::size switch (type) { case WStype_DISCONNECTED: - ESP_LOGI(TAG, "Disconnected from API"); - m_state = State::Disconnected; + _setState(State::Disconnected); break; case WStype_CONNECTED: - ESP_LOGI(TAG, "Connected to API"); - m_state = State::Connected; + _setState(State::Connected); _sendKeepAlive(); _sendBootStatus(); break; diff --git a/src/GatewayConnectionManager.cpp b/src/GatewayConnectionManager.cpp index 5a0ee78a..a4249ec5 100644 --- a/src/GatewayConnectionManager.cpp +++ b/src/GatewayConnectionManager.cpp @@ -27,11 +27,11 @@ static const char* const TAG = "GatewayConnectionManager"; static const char* const AUTH_TOKEN_FILE = "/authToken"; -constexpr std::uint8_t FLAG_NONE = 0; -constexpr std::uint8_t FLAG_HAS_IP = 1 << 0; -constexpr std::uint8_t FLAG_LINKED = 1 << 1; +const std::uint8_t FLAG_NONE = 0; +const std::uint8_t FLAG_HAS_IP = 1 << 0; +const std::uint8_t FLAG_LINKED = 1 << 1; -constexpr std::uint8_t LINK_CODE_LENGTH = 6; +const std::uint8_t LINK_CODE_LENGTH = 6; static std::uint8_t s_flags = 0; static std::unique_ptr s_wsClient = nullptr; @@ -75,15 +75,15 @@ bool GatewayConnectionManager::IsLinked() { return (s_flags & FLAG_LINKED) != 0; } -AccountLinkResultCode GatewayConnectionManager::Link(const char* linkCode) { +AccountLinkResultCode GatewayConnectionManager::Link(StringView linkCode) { if ((s_flags & FLAG_HAS_IP) == 0) { return AccountLinkResultCode::NoInternetConnection; } s_wsClient = nullptr; - ESP_LOGD(TAG, "Attempting to link to account using code %s", linkCode); + ESP_LOGD(TAG, "Attempting to link to account using code %.*s", linkCode.length(), linkCode.data()); - if (std::strlen(linkCode) != LINK_CODE_LENGTH) { + if (linkCode.length() != LINK_CODE_LENGTH) { ESP_LOGE(TAG, "Invalid link code length"); return AccountLinkResultCode::InvalidCode; } @@ -108,9 +108,9 @@ AccountLinkResultCode GatewayConnectionManager::Link(const char* linkCode) { return AccountLinkResultCode::InternalError; } - const std::string& authToken = response.data.authToken; + StringView authToken = response.data.authToken; - if (authToken.empty()) { + if (authToken.isNullOrEmpty()) { ESP_LOGE(TAG, "Received empty auth token"); return AccountLinkResultCode::InternalError; } @@ -147,7 +147,7 @@ bool GatewayConnectionManager::SendMessageBIN(const std::uint8_t* data, std::siz return s_wsClient->sendMessageBIN(data, length); } -bool FetchDeviceInfo(const String& authToken) { +bool FetchDeviceInfo(StringView authToken) { // TODO: this function is very slow, should be optimized! if ((s_flags & FLAG_HAS_IP) == 0) { return false; @@ -187,7 +187,7 @@ bool FetchDeviceInfo(const String& authToken) { } static std::int64_t _lastConnectionAttempt = 0; -bool ConnectToLCG() { +bool StartConnectingToLCG() { // TODO: this function is very slow, should be optimized! if (s_wsClient == nullptr) { // If wsClient is already initialized, we are already paired or connected ESP_LOGD(TAG, "wsClient is null"); @@ -207,6 +207,15 @@ bool ConnectToLCG() { _lastConnectionAttempt = msNow; + if (Config::HasBackendLCGOverride()) { + std::string lcgOverride; + Config::GetBackendLCGOverride(lcgOverride); + + ESP_LOGD(TAG, "Connecting to overridden LCG endpoint %s", lcgOverride.c_str()); + s_wsClient->connect(lcgOverride.c_str()); + return true; + } + if (!Config::HasBackendAuthToken()) { ESP_LOGD(TAG, "No auth token, can't connect to LCG"); return false; @@ -218,7 +227,7 @@ bool ConnectToLCG() { return false; } - auto response = HTTP::JsonAPI::AssignLcg(authToken.c_str()); + auto response = HTTP::JsonAPI::AssignLcg(authToken); if (response.result == HTTP::RequestResult::RateLimited) { return false; // Just return false, don't spam the console with errors @@ -273,9 +282,5 @@ void GatewayConnectionManager::Update() { return; } - if (ConnectToLCG()) { - ESP_LOGD(TAG, "Successfully connected to LCG"); - OpenShock::VisualStateManager::SetWebSocketConnected(true); - return; - } + StartConnectingToLCG(); } diff --git a/src/OtaUpdateManager.cpp b/src/OtaUpdateManager.cpp index 94f955ca..b7f86de5 100644 --- a/src/OtaUpdateManager.cpp +++ b/src/OtaUpdateManager.cpp @@ -18,6 +18,7 @@ #include "wifi/WiFiManager.h" #include +#include #include #include @@ -147,7 +148,7 @@ bool _flashAppPartition(const esp_partition_t* partition, StringView remoteUrl, if (!OpenShock::FlashPartitionFromUrl(partition, remoteUrl, remoteHash, onProgress)) { ESP_LOGE(TAG, "Failed to flash app partition"); - _sendFailureMessage("Failed to flash app partition"); + _sendFailureMessage("Failed to flash app partition"_sv); return false; } @@ -158,7 +159,7 @@ bool _flashAppPartition(const esp_partition_t* partition, StringView remoteUrl, // Set app partition bootable. if (esp_ota_set_boot_partition(partition) != ESP_OK) { ESP_LOGE(TAG, "Failed to set app partition bootable"); - _sendFailureMessage("Failed to set app partition bootable"); + _sendFailureMessage("Failed to set app partition bootable"_sv); return false; } @@ -173,7 +174,7 @@ bool _flashFilesystemPartition(const esp_partition_t* parition, StringView remot // Make sure captive portal is stopped, timeout after 5 seconds. if (!CaptivePortal::ForceClose(5000U)) { ESP_LOGE(TAG, "Failed to force close captive portal (timed out)"); - _sendFailureMessage("Failed to force close captive portal (timed out)"); + _sendFailureMessage("Failed to force close captive portal (timed out)"_sv); return false; } @@ -193,7 +194,7 @@ bool _flashFilesystemPartition(const esp_partition_t* parition, StringView remot if (!OpenShock::FlashPartitionFromUrl(parition, remoteUrl, remoteHash, onProgress)) { ESP_LOGE(TAG, "Failed to flash filesystem partition"); - _sendFailureMessage("Failed to flash filesystem partition"); + _sendFailureMessage("Failed to flash filesystem partition"_sv); return false; } @@ -205,7 +206,7 @@ bool _flashFilesystemPartition(const esp_partition_t* parition, StringView remot fs::LittleFSFS test; if (!test.begin(false, "/static", 10, "static0")) { ESP_LOGE(TAG, "Failed to mount filesystem"); - _sendFailureMessage("Failed to mount filesystem"); + _sendFailureMessage("Failed to mount filesystem"_sv); return false; } test.end(); @@ -333,7 +334,7 @@ void _otaUpdateTask(void* arg) { OtaUpdateManager::FirmwareRelease release; if (!OtaUpdateManager::TryGetFirmwareRelease(version, release)) { ESP_LOGE(TAG, "Failed to fetch firmware release"); // TODO: Send error message to server - _sendFailureMessage("Failed to fetch firmware release"); + _sendFailureMessage("Failed to fetch firmware release"_sv); continue; } @@ -349,7 +350,7 @@ void _otaUpdateTask(void* arg) { const esp_partition_t* appPartition = esp_ota_get_next_update_partition(nullptr); if (appPartition == nullptr) { ESP_LOGE(TAG, "Failed to get app update partition"); // TODO: Send error message to server - _sendFailureMessage("Failed to get app update partition"); + _sendFailureMessage("Failed to get app update partition"_sv); continue; } @@ -357,10 +358,14 @@ void _otaUpdateTask(void* arg) { const esp_partition_t* filesystemPartition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, "static0"); if (filesystemPartition == nullptr) { ESP_LOGE(TAG, "Failed to find filesystem partition"); // TODO: Send error message to server - _sendFailureMessage("Failed to find filesystem partition"); + _sendFailureMessage("Failed to find filesystem partition"_sv); continue; } + // Increase task watchdog timeout. + // Prevents panics on some ESP32s when clearing large partitions. + esp_task_wdt_init(15, true); + // Flash app and filesystem partitions. if (!_flashFilesystemPartition(filesystemPartition, release.filesystemBinaryUrl, release.filesystemBinaryHash)) continue; if (!_flashAppPartition(appPartition, release.appBinaryUrl, release.appBinaryHash)) continue; @@ -368,10 +373,13 @@ void _otaUpdateTask(void* arg) { // Set OTA boot type in config. if (!Config::SetOtaUpdateStep(OpenShock::OtaUpdateStep::Updated)) { ESP_LOGE(TAG, "Failed to set OTA update step"); - _sendFailureMessage("Failed to set OTA update step"); + _sendFailureMessage("Failed to set OTA update step"_sv); continue; } + // Set task watchdog timeout back to default. + esp_task_wdt_init(5, true); + // Send reboot message. _sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask::Rebooting, 0.0f); @@ -475,16 +483,16 @@ bool OtaUpdateManager::Init() { } bool OtaUpdateManager::TryGetFirmwareVersion(OtaUpdateChannel channel, OpenShock::SemVer& version) { - const char* channelIndexUrl = nullptr; + StringView channelIndexUrl; switch (channel) { case OtaUpdateChannel::Stable: - channelIndexUrl = OPENSHOCK_FW_CDN_STABLE_URL; + channelIndexUrl = StringView(OPENSHOCK_FW_CDN_STABLE_URL); break; case OtaUpdateChannel::Beta: - channelIndexUrl = OPENSHOCK_FW_CDN_BETA_URL; + channelIndexUrl = StringView(OPENSHOCK_FW_CDN_BETA_URL); break; case OtaUpdateChannel::Develop: - channelIndexUrl = OPENSHOCK_FW_CDN_DEVELOP_URL; + channelIndexUrl = StringView(OPENSHOCK_FW_CDN_DEVELOP_URL); break; default: ESP_LOGE(TAG, "Unknown channel: %u", channel); @@ -522,7 +530,7 @@ bool OtaUpdateManager::TryGetFirmwareBoards(const OpenShock::SemVer& version, st ESP_LOGD(TAG, "Fetching firmware boards from %s", channelIndexUrl.c_str()); - if (!_tryGetStringList(channelIndexUrl.c_str(), boards)) { + if (!_tryGetStringList(channelIndexUrl, boards)) { ESP_LOGE(TAG, "Failed to fetch firmware boards"); return false; } @@ -530,7 +538,7 @@ bool OtaUpdateManager::TryGetFirmwareBoards(const OpenShock::SemVer& version, st return true; } -bool _tryParseIntoHash(const std::string& hash, std::uint8_t (&hashBytes)[32]) { +bool _tryParseIntoHash(StringView hash, std::uint8_t (&hashBytes)[32]) { if (!HexUtils::TryParseHex(hash.data(), hash.size(), hashBytes, 32)) { ESP_LOGE(TAG, "Failed to parse hash: %.*s", hash.size(), hash.data()); return false; @@ -561,7 +569,7 @@ bool OtaUpdateManager::TryGetFirmwareRelease(const OpenShock::SemVer& version, F // Fetch hashes. auto sha256HashesResponse = OpenShock::HTTP::GetString( - sha256HashesUrl.c_str(), + sha256HashesUrl, { {"Accept", "text/plain"} }, @@ -576,7 +584,7 @@ bool OtaUpdateManager::TryGetFirmwareRelease(const OpenShock::SemVer& version, F // Parse hashes. bool foundAppHash = false, foundFilesystemHash = false; - for (auto line : hashesLines) { + for (OpenShock::StringView line : hashesLines) { auto parts = line.splitWhitespace(); if (parts.size() != 2) { ESP_LOGE(TAG, "Invalid hashes entry: %.*s", line.size(), line.data()); @@ -586,7 +594,7 @@ bool OtaUpdateManager::TryGetFirmwareRelease(const OpenShock::SemVer& version, F auto hash = parts[0].trim(); auto file = parts[1].trim(); - if (file.startsWith("./")) { + if (file.startsWith("./"_sv)) { file = file.substr(2); } @@ -601,7 +609,7 @@ bool OtaUpdateManager::TryGetFirmwareRelease(const OpenShock::SemVer& version, F return false; } - if (!_tryParseIntoHash(hash.toString(), release.appBinaryHash)) { + if (!_tryParseIntoHash(hash, release.appBinaryHash)) { return false; } @@ -612,7 +620,7 @@ bool OtaUpdateManager::TryGetFirmwareRelease(const OpenShock::SemVer& version, F return false; } - if (!_tryParseIntoHash(hash.toString(), release.filesystemBinaryHash)) { + if (!_tryParseIntoHash(hash, release.filesystemBinaryHash)) { return false; } diff --git a/src/SemVer.cpp b/src/SemVer.cpp index f90eee57..722093c4 100644 --- a/src/SemVer.cpp +++ b/src/SemVer.cpp @@ -239,12 +239,12 @@ std::string SemVer::toString() const { if (!prerelease.empty()) { str += '-'; - str.append(prerelease.data(), prerelease.length()); + str.append(prerelease.c_str(), prerelease.length()); } if (!build.empty()) { str += '+'; - str.append(build.data(), build.length()); + str.append(build.c_str(), build.length()); } return str; @@ -253,7 +253,7 @@ std::string SemVer::toString() const { bool OpenShock::TryParseSemVer(StringView semverStr, SemVer& semver) { auto parts = semverStr.split('.'); if (parts.size() < 3) { - ESP_LOGE(TAG, "Must have at least 3 parts: %s", semverStr.data()); + ESP_LOGE(TAG, "Must have at least 3 parts: %.*s", semverStr.length(), semverStr.data()); return false; } @@ -272,27 +272,27 @@ bool OpenShock::TryParseSemVer(StringView semverStr, SemVer& semver) { } if (!_tryParseU16(majorStr, semver.major)) { - ESP_LOGE(TAG, "Invalid major version: %s", majorStr.data()); + ESP_LOGE(TAG, "Invalid major version: %.*s", majorStr.length(), majorStr.data()); return false; } if (!_tryParseU16(minorStr, semver.minor)) { - ESP_LOGE(TAG, "Invalid minor version: %s", minorStr.data()); + ESP_LOGE(TAG, "Invalid minor version: %.*s", minorStr.length(), minorStr.data()); return false; } if (!_tryParseU16(patchStr, semver.patch)) { - ESP_LOGE(TAG, "Invalid patch version: %s", patchStr.data()); + ESP_LOGE(TAG, "Invalid patch version: %.*s", patchStr.length(), patchStr.data()); return false; } if (!semver.prerelease.empty() && !_semverIsPrerelease(semver.prerelease)) { - ESP_LOGE(TAG, "Invalid prerelease: %s", semver.prerelease.data()); + ESP_LOGE(TAG, "Invalid prerelease: %s", semver.prerelease.c_str()); return false; } if (!semver.build.empty() && !_semverIsBuild(semver.build)) { - ESP_LOGE(TAG, "Invalid build: %s", semver.build.data()); + ESP_LOGE(TAG, "Invalid build: %s", semver.build.c_str()); return false; } diff --git a/src/VisualStateManager.cpp b/src/VisualStateManager.cpp index a15a740a..7ab132e0 100644 --- a/src/VisualStateManager.cpp +++ b/src/VisualStateManager.cpp @@ -10,15 +10,15 @@ const char* const TAG = "VisualStateManager"; -constexpr std::uint64_t kCriticalErrorFlag = 1 << 0; -constexpr std::uint64_t kEmergencyStoppedFlag = 1 << 1; -constexpr std::uint64_t kEmergencyStopClearedFlag = 1 << 2; -constexpr std::uint64_t kWebSocketConnectedFlag = 1 << 3; -constexpr std::uint64_t kWiFiConnectedFlag = 1 << 4; -constexpr std::uint64_t kWiFiScanningFlag = 1 << 5; +const std::uint64_t kCriticalErrorFlag = 1 << 0; +const std::uint64_t kEmergencyStoppedFlag = 1 << 1; +const std::uint64_t kEmergencyStopClearedFlag = 1 << 2; +const std::uint64_t kWebSocketConnectedFlag = 1 << 3; +const std::uint64_t kWiFiConnectedFlag = 1 << 4; +const std::uint64_t kWiFiScanningFlag = 1 << 5; // Bitmask of when the system is in a "all clear" state. -constexpr std::uint64_t kStatusOKMask = kWebSocketConnectedFlag | kWiFiConnectedFlag; +const std::uint64_t kStatusOKMask = kWebSocketConnectedFlag | kWiFiConnectedFlag; static std::uint64_t s_stateFlags = 0; static std::shared_ptr s_builtInLedManager; @@ -26,34 +26,34 @@ static std::shared_ptr s_RGBLedManager; using namespace OpenShock; -constexpr PinPatternManager::State kCriticalErrorPattern[] = { +const PinPatternManager::State kCriticalErrorPattern[] = { { true, 100}, // LED ON for 0.1 seconds {false, 100} // LED OFF for 0.1 seconds }; -constexpr RGBPatternManager::RGBState kCriticalErrorRGBPattern[] = { +const RGBPatternManager::RGBState kCriticalErrorRGBPattern[] = { {255, 0, 0, 100}, // Red ON for 0.1 seconds { 0, 0, 0, 100} // OFF for 0.1 seconds }; -constexpr PinPatternManager::State kEmergencyStoppedPattern[] = { +const PinPatternManager::State kEmergencyStoppedPattern[] = { { true, 500}, {false, 500} }; -constexpr RGBPatternManager::RGBState kEmergencyStoppedRGBPattern[] = { +const RGBPatternManager::RGBState kEmergencyStoppedRGBPattern[] = { {255, 0, 0, 500}, { 0, 0, 0, 500} }; -constexpr PinPatternManager::State kEmergencyStopClearedPattern[] = { +const PinPatternManager::State kEmergencyStopClearedPattern[] = { { true, 150}, {false, 150} }; -constexpr RGBPatternManager::RGBState kEmergencyStopClearedRGBPattern[] = { +const RGBPatternManager::RGBState kEmergencyStopClearedRGBPattern[] = { {0, 255, 0, 150}, {0, 0, 0, 150} }; -constexpr PinPatternManager::State kWiFiDisconnectedPattern[] = { +const PinPatternManager::State kWiFiDisconnectedPattern[] = { { true, 100}, {false, 100}, { true, 100}, @@ -61,7 +61,7 @@ constexpr PinPatternManager::State kWiFiDisconnectedPattern[] = { { true, 100}, {false, 700} }; -constexpr RGBPatternManager::RGBState kWiFiDisconnectedRGBPattern[] = { +const RGBPatternManager::RGBState kWiFiDisconnectedRGBPattern[] = { {0, 0, 255, 100}, {0, 0, 0, 100}, {0, 0, 255, 100}, @@ -70,20 +70,20 @@ constexpr RGBPatternManager::RGBState kWiFiDisconnectedRGBPattern[] = { {0, 0, 0, 700} }; -constexpr PinPatternManager::State kWiFiConnectedWithoutWSPattern[] = { +const PinPatternManager::State kWiFiConnectedWithoutWSPattern[] = { { true, 100}, {false, 100}, { true, 100}, {false, 700} }; -constexpr RGBPatternManager::RGBState kWiFiConnectedWithoutWSRGBPattern[] = { +const RGBPatternManager::RGBState kWiFiConnectedWithoutWSRGBPattern[] = { {255, 165, 0, 100}, { 0, 0, 0, 100}, {255, 165, 0, 100}, { 0, 0, 0, 700} }; -constexpr PinPatternManager::State kPingNoResponsePattern[] = { +const PinPatternManager::State kPingNoResponsePattern[] = { { true, 100}, {false, 100}, { true, 100}, @@ -93,7 +93,7 @@ constexpr PinPatternManager::State kPingNoResponsePattern[] = { { true, 100}, {false, 700} }; -constexpr RGBPatternManager::RGBState kPingNoResponseRGBPattern[] = { +const RGBPatternManager::RGBState kPingNoResponseRGBPattern[] = { {0, 50, 255, 100}, {0, 0, 0, 100}, {0, 50, 255, 100}, @@ -104,7 +104,7 @@ constexpr RGBPatternManager::RGBState kPingNoResponseRGBPattern[] = { {0, 0, 0, 700} }; -constexpr PinPatternManager::State kWebSocketCantConnectPattern[] = { +const PinPatternManager::State kWebSocketCantConnectPattern[] = { { true, 100}, {false, 100}, { true, 100}, @@ -116,7 +116,7 @@ constexpr PinPatternManager::State kWebSocketCantConnectPattern[] = { { true, 100}, {false, 700} }; -constexpr RGBPatternManager::RGBState kWebSocketCantConnectRGBPattern[] = { +const RGBPatternManager::RGBState kWebSocketCantConnectRGBPattern[] = { {255, 0, 0, 100}, { 0, 0, 0, 100}, {255, 0, 0, 100}, @@ -129,20 +129,20 @@ constexpr RGBPatternManager::RGBState kWebSocketCantConnectRGBPattern[] = { { 0, 0, 0, 700} }; -constexpr PinPatternManager::State kWebSocketConnectedPattern[] = { +const PinPatternManager::State kWebSocketConnectedPattern[] = { { true, 100}, {false, 10'000} }; -constexpr RGBPatternManager::RGBState kWebSocketConnectedRGBPattern[] = { +const RGBPatternManager::RGBState kWebSocketConnectedRGBPattern[] = { {0, 255, 0, 100}, {0, 0, 0, 10'000}, }; -constexpr PinPatternManager::State kSolidOnPattern[] = { +const PinPatternManager::State kSolidOnPattern[] = { {true, 100'000} }; -constexpr PinPatternManager::State kSolidOffPattern[] = { +const PinPatternManager::State kSolidOffPattern[] = { {false, 100'000} }; diff --git a/src/config/BackendConfig.cpp b/src/config/BackendConfig.cpp index af560633..351f213a 100644 --- a/src/config/BackendConfig.cpp +++ b/src/config/BackendConfig.cpp @@ -7,7 +7,9 @@ const char* const TAG = "Config::BackendConfig"; using namespace OpenShock::Config; -BackendConfig::BackendConfig() : domain(OPENSHOCK_API_DOMAIN), authToken() { } +BackendConfig::BackendConfig() : domain(OPENSHOCK_API_DOMAIN), authToken(), lcgOverride() { } + +BackendConfig::BackendConfig(StringView domain, StringView authToken, StringView lcgOverride) : domain(domain.toString()), authToken(authToken.toString()), lcgOverride(lcgOverride.toString()) { } void BackendConfig::ToDefault() { domain = OPENSHOCK_API_DOMAIN; @@ -22,6 +24,7 @@ bool BackendConfig::FromFlatbuffers(const Serialization::Configuration::BackendC Internal::Utils::FromFbsStr(domain, config->domain(), OPENSHOCK_API_DOMAIN); Internal::Utils::FromFbsStr(authToken, config->auth_token(), ""); + Internal::Utils::FromFbsStr(lcgOverride, config->lcg_override(), ""); return true; } @@ -36,7 +39,9 @@ flatbuffers::Offset Back authTokenOffset = 0; } - return Serialization::Configuration::CreateBackendConfig(builder, domainOffset, authTokenOffset); + auto lcgOverrideOffset = builder.CreateString(lcgOverride); + + return Serialization::Configuration::CreateBackendConfig(builder, domainOffset, authTokenOffset, lcgOverrideOffset); } bool BackendConfig::FromJSON(const cJSON* json) { @@ -52,6 +57,7 @@ bool BackendConfig::FromJSON(const cJSON* json) { Internal::Utils::FromJsonStr(domain, json, "domain", OPENSHOCK_API_DOMAIN); Internal::Utils::FromJsonStr(authToken, json, "authToken", ""); + Internal::Utils::FromJsonStr(lcgOverride, json, "lcgOverride", ""); return true; } @@ -60,9 +66,12 @@ cJSON* BackendConfig::ToJSON(bool withSensitiveData) const { cJSON* root = cJSON_CreateObject(); cJSON_AddStringToObject(root, "domain", domain.c_str()); + if (withSensitiveData) { cJSON_AddStringToObject(root, "authToken", authToken.c_str()); } + cJSON_AddStringToObject(root, "lcgOverride", lcgOverride.c_str()); + return root; } diff --git a/src/config/Config.cpp b/src/config/Config.cpp index e46cca17..8aad0307 100644 --- a/src/config/Config.cpp +++ b/src/config/Config.cpp @@ -152,8 +152,8 @@ std::string Config::GetAsJSON(bool withSensitiveData) { return result; } -bool Config::SaveFromJSON(const std::string& json) { - cJSON* root = cJSON_Parse(json.c_str()); +bool Config::SaveFromJSON(StringView json) { + cJSON* root = cJSON_ParseWithLength(json.data(), json.size()); if (root == nullptr) { ESP_LOGE(TAG, "Failed to parse JSON: %s", cJSON_GetErrorPtr()); return false; @@ -445,7 +445,7 @@ bool Config::AnyWiFiCredentials(std::function& credentialsList) : accessPointSSID(accessPointSSID), hostname(hostname), credentialsList(credentialsList) { } +WiFiConfig::WiFiConfig(StringView accessPointSSID, StringView hostname, const std::vector& credentialsList) : accessPointSSID(accessPointSSID.toString()), hostname(hostname.toString()), credentialsList(credentialsList) { } void WiFiConfig::ToDefault() { accessPointSSID = OPENSHOCK_FW_AP_PREFIX; diff --git a/src/config/WiFiCredentials.cpp b/src/config/WiFiCredentials.cpp index 546404ee..9bd5b75a 100644 --- a/src/config/WiFiCredentials.cpp +++ b/src/config/WiFiCredentials.cpp @@ -10,7 +10,7 @@ using namespace OpenShock::Config; WiFiCredentials::WiFiCredentials() : id(0), ssid(), password() { } -WiFiCredentials::WiFiCredentials(std::uint8_t id, const std::string& ssid, const std::string& password) : id(id), ssid(ssid), password(password) { } +WiFiCredentials::WiFiCredentials(std::uint8_t id, StringView ssid, StringView password) : id(id), ssid(ssid.toString()), password(password.toString()) { } void WiFiCredentials::ToDefault() { id = 0; @@ -28,6 +28,11 @@ bool WiFiCredentials::FromFlatbuffers(const Serialization::Configuration::WiFiCr Internal::Utils::FromFbsStr(ssid, config->ssid(), ""); Internal::Utils::FromFbsStr(password, config->password(), ""); + if (ssid.empty()) { + ESP_LOGE(TAG, "ssid is empty"); + return false; + } + return true; } @@ -59,6 +64,11 @@ bool WiFiCredentials::FromJSON(const cJSON* json) { Internal::Utils::FromJsonStr(ssid, json, "ssid", ""); Internal::Utils::FromJsonStr(password, json, "password", ""); + if (ssid.empty()) { + ESP_LOGE(TAG, "ssid is empty"); + return false; + } + return true; } diff --git a/src/event_handlers/websocket/Gateway.cpp b/src/event_handlers/websocket/Gateway.cpp index 2d3849ef..b7274fa6 100644 --- a/src/event_handlers/websocket/Gateway.cpp +++ b/src/event_handlers/websocket/Gateway.cpp @@ -19,7 +19,7 @@ typedef Schemas::GatewayToDeviceMessagePayload PayloadType; using namespace OpenShock; -constexpr std::size_t HANDLER_COUNT = static_cast(PayloadType::MAX) + 1; +const std::size_t HANDLER_COUNT = static_cast(PayloadType::MAX) + 1; #define SET_HANDLER(payload, handler) handlers[static_cast(payload)] = handler diff --git a/src/event_handlers/websocket/Local.cpp b/src/event_handlers/websocket/Local.cpp index 894010ec..8ddde09f 100644 --- a/src/event_handlers/websocket/Local.cpp +++ b/src/event_handlers/websocket/Local.cpp @@ -18,7 +18,7 @@ typedef Schemas::LocalToDeviceMessagePayload PayloadType; using namespace OpenShock; -constexpr std::size_t HANDLER_COUNT = static_cast(PayloadType::MAX) + 1; +const std::size_t HANDLER_COUNT = static_cast(PayloadType::MAX) + 1; #define SET_HANDLER(payload, handler) handlers[static_cast(payload)] = handler diff --git a/src/event_handlers/websocket/local/AccountLinkCommand.cpp b/src/event_handlers/websocket/local/AccountLinkCommand.cpp index c052beee..9bd34ce9 100644 --- a/src/event_handlers/websocket/local/AccountLinkCommand.cpp +++ b/src/event_handlers/websocket/local/AccountLinkCommand.cpp @@ -44,7 +44,7 @@ void _Private::HandleAccountLinkCommand(std::uint8_t socketId, const OpenShock:: return; } - auto result = GatewayConnectionManager::Link(code->data()); + auto result = GatewayConnectionManager::Link(*code); serializeSetRfTxPinResult(socketId, result); } diff --git a/src/http/HTTPRequestManager.cpp b/src/http/HTTPRequestManager.cpp index 855b58ac..faf33801 100644 --- a/src/http/HTTPRequestManager.cpp +++ b/src/http/HTTPRequestManager.cpp @@ -11,8 +11,8 @@ #include #include -constexpr std::size_t HTTP_BUFFER_SIZE = 4096LLU; -constexpr int HTTP_DOWNLOAD_SIZE_LIMIT = 200 * 1024 * 1024; // 200 MB +const std::size_t HTTP_BUFFER_SIZE = 4096LLU; +const int HTTP_DOWNLOAD_SIZE_LIMIT = 200 * 1024 * 1024; // 200 MB const char* const TAG = "HTTPRequestManager"; @@ -107,7 +107,7 @@ StringView _getDomain(StringView url) { } // Remove the protocol, port, and path eg. "https://api.example.com:443/path" -> "api.example.com" - url = url.afterDelimiter("://").beforeDelimiter('/').beforeDelimiter(':'); + url = url.afterDelimiter("://"_sv).beforeDelimiter('/').beforeDelimiter(':'); // Remove all subdomains eg. "api.example.com" -> "example.com" auto domainSep = url.rfind('.'); diff --git a/src/http/JsonAPI.cpp b/src/http/JsonAPI.cpp index c85444d4..fd6e2c83 100644 --- a/src/http/JsonAPI.cpp +++ b/src/http/JsonAPI.cpp @@ -1,12 +1,18 @@ #include "http/JsonAPI.h" #include "Common.h" +#include "config/Config.h" using namespace OpenShock; HTTP::Response HTTP::JsonAPI::LinkAccount(const char* accountLinkCode) { - char uri[256]; - sprintf(uri, OPENSHOCK_API_URL("/1/device/pair/%s"), accountLinkCode); + std::string domain; + if (!Config::GetBackendDomain(domain)) { + return {HTTP::RequestResult::InternalError, 0, {}}; + } + + char uri[OPENSHOCK_URI_BUFFER_SIZE]; + sprintf(uri, "https://%s/1/device/pair/%s", domain.c_str(), accountLinkCode); return HTTP::GetJSON( uri, @@ -18,24 +24,40 @@ HTTP::Response HTTP::JsonAPI::LinkA ); } -HTTP::Response HTTP::JsonAPI::GetDeviceInfo(const String& deviceToken) { +HTTP::Response HTTP::JsonAPI::GetDeviceInfo(StringView deviceToken) { + std::string domain; + if (!Config::GetBackendDomain(domain)) { + return {HTTP::RequestResult::InternalError, 0, {}}; + } + + char uri[OPENSHOCK_URI_BUFFER_SIZE]; + sprintf(uri, "https://%s/1/device/self", domain.c_str()); + return HTTP::GetJSON( - OPENSHOCK_API_URL("/1/device/self"), + uri, { - { "Accept", "application/json"}, - {"DeviceToken", deviceToken} + { "Accept", "application/json"}, + {"DeviceToken", deviceToken.toArduinoString()} }, Serialization::JsonAPI::ParseDeviceInfoJsonResponse, {200, 401} ); } -HTTP::Response HTTP::JsonAPI::AssignLcg(const String& deviceToken) { +HTTP::Response HTTP::JsonAPI::AssignLcg(StringView deviceToken) { + std::string domain; + if (!Config::GetBackendDomain(domain)) { + return {HTTP::RequestResult::InternalError, 0, {}}; + } + + char uri[OPENSHOCK_URI_BUFFER_SIZE]; + sprintf(uri, "https://%s/1/device/assignLCG", domain.c_str()); + return HTTP::GetJSON( - OPENSHOCK_API_URL("/1/device/assignLCG"), + uri, { - { "Accept", "application/json"}, - {"DeviceToken", deviceToken} + { "Accept", "application/json"}, + {"DeviceToken", deviceToken.toArduinoString()} }, Serialization::JsonAPI::ParseAssignLcgJsonResponse, {200, 401} diff --git a/src/radio/RFTransmitter.cpp b/src/radio/RFTransmitter.cpp index 950a98a4..df9848f9 100644 --- a/src/radio/RFTransmitter.cpp +++ b/src/radio/RFTransmitter.cpp @@ -15,13 +15,14 @@ const UBaseType_t RFTRANSMITTER_QUEUE_SIZE = 64; const BaseType_t RFTRANSMITTER_TASK_PRIORITY = 1; const std::uint32_t RFTRANSMITTER_TASK_STACK_SIZE = 4096; // PROFILED: 1.4KB stack usage const float RFTRANSMITTER_TICKRATE_NS = 1000; +const std::int64_t TRANSMIT_END_DURATION = 300; using namespace OpenShock; struct command_t { std::int64_t until; std::vector sequence; - std::shared_ptr> zeroSequence; + std::vector zeroSequence; std::uint16_t shockerId; bool overwrite; }; @@ -178,6 +179,17 @@ void RFTransmitter::TransmitTask(void* arg) { } } + if(OpenShock::EStopManager::IsEStopped()) { + + std::int64_t whenEStoppedTime = EStopManager::WhenEStopped(); + + for (auto it = commands.begin(); it != commands.end(); ++it) { + cmd = *it; + + cmd->until = whenEStoppedTime; + } + } + // Send queued commands for (auto it = commands.begin(); it != commands.end();) { cmd = *it; @@ -187,15 +199,20 @@ void RFTransmitter::TransmitTask(void* arg) { // Remove expired or empty commands, else send the command. // After sending/receiving a command, move to the next one. - if (expired || empty || OpenShock::EStopManager::IsEStopped()) { + if (expired || empty) { // If the command is not empty, send the zero sequence to stop the shocker if (!empty) { - rmtWriteBlocking(rmtHandle, cmd->zeroSequence->data(), cmd->zeroSequence->size()); + rmtWriteBlocking(rmtHandle, cmd->zeroSequence.data(), cmd->zeroSequence.size()); } - // Remove the command and move to the next one - it = commands.erase(it); - delete cmd; + if(cmd->until + TRANSMIT_END_DURATION < OpenShock::millis()) { + // Remove the command and move to the next one + it = commands.erase(it); + delete cmd; + } else { + // Move to the next command + ++it; + } } else { // Send the command rmtWriteBlocking(rmtHandle, cmd->sequence.data(), cmd->sequence.size()); diff --git a/src/radio/rmt/MainEncoder.cpp b/src/radio/rmt/MainEncoder.cpp index 5da09093..80a7a87c 100644 --- a/src/radio/rmt/MainEncoder.cpp +++ b/src/radio/rmt/MainEncoder.cpp @@ -3,6 +3,7 @@ #include "Logging.h" #include "radio/rmt/CaiXianlinEncoder.h" #include "radio/rmt/PetrainerEncoder.h" +#include "radio/rmt/Petrainer998DREncoder.h" #include @@ -14,6 +15,8 @@ std::vector Rmt::GetSequence(ShockerModelType model, std::uint16_t s switch (model) { case ShockerModelType::Petrainer: return Rmt::PetrainerEncoder::GetSequence(shockerId, type, intensity); + case ShockerModelType::Petrainer998DR: + return Rmt::Petrainer998DREncoder::GetSequence(shockerId, type, intensity); case ShockerModelType::CaiXianlin: return Rmt::CaiXianlinEncoder::GetSequence(shockerId, 0, type, intensity); default: @@ -21,17 +24,21 @@ std::vector Rmt::GetSequence(ShockerModelType model, std::uint16_t s return {}; } } +/* std::shared_ptr> Rmt::GetZeroSequence(ShockerModelType model, std::uint16_t shockerId) { static std::unordered_map>> _sequences; auto it = _sequences.find(shockerId); - if (it != _sequences.end()) return it->second; + if (it != _sequences.end()) return it->second; // FIXME: This is not thread-safe, and does not check if model is the same, causing a protocol mismatch std::shared_ptr> sequence; switch (model) { case ShockerModelType::Petrainer: sequence = std::make_shared>(Rmt::PetrainerEncoder::GetSequence(shockerId, ShockerCommandType::Vibrate, 0)); break; + case ShockerModelType::Petrainer998DR: + sequence = std::make_shared>(Rmt::Petrainer998DREncoder::GetSequence(shockerId, ShockerCommandType::Vibrate, 0)); + break; case ShockerModelType::CaiXianlin: sequence = std::make_shared>(Rmt::CaiXianlinEncoder::GetSequence(shockerId, 0, ShockerCommandType::Vibrate, 0)); break; @@ -45,3 +52,4 @@ std::shared_ptr> Rmt::GetZeroSequence(ShockerModelType m return sequence; } +*/ diff --git a/src/radio/rmt/Petrainer998DREncoder.cpp b/src/radio/rmt/Petrainer998DREncoder.cpp new file mode 100644 index 00000000..68b1f0c2 --- /dev/null +++ b/src/radio/rmt/Petrainer998DREncoder.cpp @@ -0,0 +1,58 @@ +#include "radio/rmt/Petrainer998DREncoder.h" + +#include "radio/rmt/internal/Shared.h" + +const rmt_data_t kRmtPreamble = {1500, 1, 750, 0}; +const rmt_data_t kRmtOne = {750, 1, 250, 0}; +const rmt_data_t kRmtZero = {250, 1, 750, 0}; + +using namespace OpenShock; + +std::vector Rmt::Petrainer998DREncoder::GetSequence(std::uint16_t shockerId, ShockerCommandType type, std::uint8_t intensity) { + // Intensity must be between 0 and 100 + intensity = std::min(intensity, static_cast(100)); + + std::uint8_t typeVal = 0; + // typeInvert has the value of typeVal but bits are reversed and inverted + std::uint8_t typeInvert = 0; + switch (type) { + case ShockerCommandType::Shock: + typeVal = 0b0001; + typeInvert = 0b0111; + break; + case ShockerCommandType::Vibrate: + typeVal = 0b0010; + typeInvert = 0b1011; + break; + case ShockerCommandType::Sound: + typeVal = 0b0100; + typeInvert = 0b1101; + break; + // case ShockerCommandType::Light: + // typeVal = 0b1000; + // typeInvert = 0b1110; + // break; + default: + return {}; // Invalid type + } + + // TODO: Channel argument? + // Can be [000] or [111], 3 bits wide + std::uint8_t channel = 0b000; + std::uint8_t channelInvert = 0b111; + + // Payload layout: [channel:3][typeVal:4][shockerID:17][intensity:7][typeInvert:4][channelInvert:3] + std::uint64_t data = (static_cast(channel & 0b111) << 35 | static_cast(typeVal & 0b1111) << 31 | static_cast(shockerId & 0x1FFFF) << 14 | static_cast(intensity & 0x7F) << 7 | static_cast(typeInvert & 0b1111) << 3 | static_cast(channelInvert & 0b111)); + + std::vector pulses; + pulses.reserve(42); + + // Generate the sequence + pulses.push_back(kRmtPreamble); + pulses.push_back(kRmtOne); + Internal::EncodeBits<38>(pulses, data, kRmtOne, kRmtZero); + pulses.push_back(kRmtZero); + pulses.push_back(kRmtZero); + + return pulses; +} diff --git a/src/serial/SerialInputHandler.cpp b/src/serial/SerialInputHandler.cpp index 3b17ccb5..8bae0b88 100644 --- a/src/serial/SerialInputHandler.cpp +++ b/src/serial/SerialInputHandler.cpp @@ -5,8 +5,11 @@ #include "config/Config.h" #include "config/SerialInputConfig.h" #include "FormatHelpers.h" +#include "http/HTTPRequestManager.h" #include "Logging.h" +#include "serialization/JsonAPI.h" #include "serialization/JsonSerial.h" +#include "StringView.h" #include "Time.h" #include "util/Base64Utils.h" #include "wifi/WiFiManager.h" @@ -16,6 +19,8 @@ #include +#include + const char* const TAG = "SerialInputHandler"; #define SERPR_SYS(format, ...) Serial.printf("$SYS$|" format "\n", ##__VA_ARGS__) @@ -25,34 +30,40 @@ const char* const TAG = "SerialInputHandler"; using namespace OpenShock; -constexpr std::int64_t PASTE_INTERVAL_THRESHOLD_MS = 20; -constexpr std::size_t SERIAL_BUFFER_CLEAR_THRESHOLD = 512; +const std::int64_t PASTE_INTERVAL_THRESHOLD_MS = 20; +const std::size_t SERIAL_BUFFER_CLEAR_THRESHOLD = 512; struct SerialCmdHandler { - const char* cmd; + StringView cmd; const char* helpResponse; - void (*commandHandler)(char*, std::size_t); + void (*commandHandler)(StringView); }; static bool s_echoEnabled = true; -static std::unordered_map s_commandHandlers; +static std::unordered_map s_commandHandlers; /// @brief Tries to parse a boolean from a string (case-insensitive) /// @param str Input string /// @param strLen Length of input string /// @param out Output boolean /// @return True if the argument is a boolean, false otherwise -bool _tryParseBool(const char* str, std::size_t strLen, bool& out) { - if (str == nullptr || strLen == 0) { +bool _tryParseBool(StringView str, bool& out) { + if (str.isNullOrEmpty()) { return false; } - if (strcasecmp(str, "true") == 0) { + str = str.trim(); + + if (str.length() > 5) { + return false; + } + + if (strncasecmp(str.data(), "true", str.length()) == 0) { out = true; return true; } - if (strcasecmp(str, "false") == 0) { + if (strncasecmp(str.data(), "false", str.length()) == 0) { out = false; return true; } @@ -60,25 +71,22 @@ bool _tryParseBool(const char* str, std::size_t strLen, bool& out) { return false; } -void _handleVersionCommand(char* arg, std::size_t argLength) { +void _handleVersionCommand(StringView arg) { (void)arg; - (void)argLength; Serial.print("\n"); SerialInputHandler::PrintVersionInfo(); } -void _handleRestartCommand(char* arg, std::size_t argLength) { +void _handleRestartCommand(StringView arg) { (void)arg; - (void)argLength; Serial.println("Restarting ESP..."); ESP.restart(); } -void _handleFactoryResetCommand(char* arg, std::size_t argLength) { +void _handleFactoryResetCommand(StringView arg) { (void)arg; - (void)argLength; Serial.println("Resetting to factory defaults..."); Config::FactoryReset(); @@ -86,8 +94,8 @@ void _handleFactoryResetCommand(char* arg, std::size_t argLength) { ESP.restart(); } -void _handleRfTxPinCommand(char* arg, std::size_t argLength) { - if (arg == nullptr || argLength == 0) { +void _handleRfTxPinCommand(StringView arg) { + if (arg.isNullOrEmpty()) { std::uint8_t txPin; if (!Config::GetRFConfigTxPin(txPin)) { SERPR_ERROR("Failed to get RF TX pin from config"); @@ -99,8 +107,10 @@ void _handleRfTxPinCommand(char* arg, std::size_t argLength) { return; } + auto str = arg.toString(); // Copy the string to null-terminate it (VERY IMPORTANT) + unsigned int pin; - if (sscanf(arg, "%u", &pin) != 1) { + if (sscanf(str.c_str(), "%u", &pin) != 1) { SERPR_ERROR("Invalid argument (not a number)"); return; } @@ -131,8 +141,68 @@ void _handleRfTxPinCommand(char* arg, std::size_t argLength) { } } -void _handleAuthtokenCommand(char* arg, std::size_t argLength) { - if (arg == nullptr || argLength == 0) { +void _handleDomainCommand(StringView arg) { + if (arg.isNullOrEmpty()) { + std::string domain; + if (!Config::GetBackendDomain(domain)) { + SERPR_ERROR("Failed to get domain from config"); + return; + } + + // Get domain + SERPR_RESPONSE("Domain|%s", domain.c_str()); + return; + } + + // Check if the domain is too long + // TODO: Remove magic number + if (arg.length() + 40 >= OPENSHOCK_URI_BUFFER_SIZE) { + SERPR_ERROR("Domain name too long, please try increasing the \"OPENSHOCK_URI_BUFFER_SIZE\" constant in source code"); + return; + } + + char uri[OPENSHOCK_URI_BUFFER_SIZE]; + sprintf(uri, "https://%.*s/1", arg.length(), arg.data()); + + auto resp = HTTP::GetJSON( + uri, + { + {"Accept", "application/json"} + }, + Serialization::JsonAPI::ParseBackendVersionJsonResponse, + {200} + ); + + if (resp.result != HTTP::RequestResult::Success) { + SERPR_ERROR("Tried to connect to \"%.*s\", but failed with status [%d], refusing to save domain to config", arg.length(), arg.data(), resp.code); + return; + } + + ESP_LOGI( + TAG, + "Successfully connected to \"%.*s\", version: %s, commit: %s, current time: %s", + arg.length(), + arg.data(), + resp.data.version.c_str(), + resp.data.commit.c_str(), + resp.data.currentTime.c_str() + ); + + bool result = OpenShock::Config::SetBackendDomain(arg); + + if (!result) { + SERPR_ERROR("Failed to save config"); + return; + } + + SERPR_SUCCESS("Saved config, restarting..."); + + // Restart to use the new domain + ESP.restart(); +} + +void _handleAuthtokenCommand(StringView arg) { + if (arg.isNullOrEmpty()) { std::string authToken; if (!Config::GetBackendAuthToken(authToken)) { SERPR_ERROR("Failed to get auth token from config"); @@ -144,7 +214,7 @@ void _handleAuthtokenCommand(char* arg, std::size_t argLength) { return; } - bool result = OpenShock::Config::SetBackendAuthToken(std::string(arg, argLength)); + bool result = OpenShock::Config::SetBackendAuthToken(arg); if (result) { SERPR_SUCCESS("Saved config"); @@ -153,10 +223,93 @@ void _handleAuthtokenCommand(char* arg, std::size_t argLength) { } } -void _handleNetworksCommand(char* arg, std::size_t argLength) { +void _handleLcgOverrideCommand(StringView arg) { + if (arg.isNullOrEmpty()) { + std::string lcgOverride; + if (!Config::GetBackendLCGOverride(lcgOverride)) { + SERPR_ERROR("Failed to get LCG override from config"); + return; + } + + // Get LCG override + SERPR_RESPONSE("LcgOverride|%s", lcgOverride.c_str()); + return; + } + + if (arg.startsWith("clear")) { + if (arg.size() != 5) { + SERPR_ERROR("Invalid command (clear command should not have any arguments)"); + return; + } + + bool result = OpenShock::Config::SetBackendLCGOverride(std::string()); + if (result) { + SERPR_SUCCESS("Cleared LCG override"); + } else { + SERPR_ERROR("Failed to clear LCG override"); + } + return; + } + + if (arg.startsWith("set ")) { + if (arg.size() <= 4) { + SERPR_ERROR("Invalid command (set command should have an argument)"); + return; + } + + StringView domain = arg.substr(4); + + if (domain.size() + 40 >= OPENSHOCK_URI_BUFFER_SIZE) { + SERPR_ERROR("Domain name too long, please try increasing the \"OPENSHOCK_URI_BUFFER_SIZE\" constant in source code"); + return; + } + + char uri[OPENSHOCK_URI_BUFFER_SIZE]; + sprintf(uri, "https://%.*s/1", static_cast(domain.size()), domain.data()); + + auto resp = HTTP::GetJSON( + uri, + { + {"Accept", "application/json"} + }, + Serialization::JsonAPI::ParseLcgInstanceDetailsJsonResponse, + {200} + ); + + if (resp.result != HTTP::RequestResult::Success) { + SERPR_ERROR("Tried to connect to \"%.*s\", but failed with status [%d], refusing to save domain to config", domain.size(), domain.data(), resp.code); + return; + } + + ESP_LOGI( + TAG, + "Successfully connected to \"%.*s\", name: %s, version: %s, current time: %s, country code: %s, FQDN: %s", + domain.size(), + domain.data(), + resp.data.name.c_str(), + resp.data.version.c_str(), + resp.data.currentTime.c_str(), + resp.data.countryCode.c_str(), + resp.data.fqdn.c_str() + ); + + bool result = OpenShock::Config::SetBackendLCGOverride(domain); + + if (result) { + SERPR_SUCCESS("Saved config"); + } else { + SERPR_ERROR("Failed to save config"); + } + return; + } + + SERPR_ERROR("Invalid subcommand"); +} + +void _handleNetworksCommand(StringView arg) { cJSON* root; - if (arg == nullptr || argLength == 0) { + if (arg.isNullOrEmpty()) { root = cJSON_CreateArray(); if (root == nullptr) { SERPR_ERROR("Failed to create JSON array"); @@ -180,7 +333,7 @@ void _handleNetworksCommand(char* arg, std::size_t argLength) { return; } - root = cJSON_ParseWithLength(arg, argLength); + root = cJSON_ParseWithLength(arg.data(), arg.length()); if (root == nullptr) { SERPR_ERROR("Failed to parse JSON: %s", cJSON_GetErrorPtr()); return; @@ -222,10 +375,10 @@ void _handleNetworksCommand(char* arg, std::size_t argLength) { OpenShock::WiFiManager::RefreshNetworkCredentials(); } -void _handleKeepAliveCommand(char* arg, std::size_t argLength) { +void _handleKeepAliveCommand(StringView arg) { bool keepAliveEnabled; - if (arg == nullptr || argLength == 0) { + if (arg.isNullOrEmpty()) { // Get keep alive status if (!Config::GetRFConfigKeepAliveEnabled(keepAliveEnabled)) { SERPR_ERROR("Failed to get keep-alive status from config"); @@ -236,7 +389,7 @@ void _handleKeepAliveCommand(char* arg, std::size_t argLength) { return; } - if (!_tryParseBool(arg, argLength, keepAliveEnabled)) { + if (!_tryParseBool(arg, keepAliveEnabled)) { SERPR_ERROR("Invalid argument (not a boolean)"); return; } @@ -250,15 +403,15 @@ void _handleKeepAliveCommand(char* arg, std::size_t argLength) { } } -void _handleSerialEchoCommand(char* arg, std::size_t argLength) { - if (arg == nullptr || argLength == 0) { +void _handleSerialEchoCommand(StringView arg) { + if (arg.isNullOrEmpty()) { // Get current serial echo status SERPR_RESPONSE("SerialEcho|%s", s_echoEnabled ? "true" : "false"); return; } bool enabled; - if (!_tryParseBool(arg, argLength, enabled)) { + if (!_tryParseBool(arg, enabled)) { SERPR_ERROR("Invalid argument (not a boolean)"); return; } @@ -273,8 +426,8 @@ void _handleSerialEchoCommand(char* arg, std::size_t argLength) { } } -void _handleValidGpiosCommand(char* arg, std::size_t argLength) { - if (arg != nullptr && argLength > 0) { +void _handleValidGpiosCommand(StringView arg) { + if (!arg.isNullOrEmpty()) { SERPR_ERROR("Invalid argument (too many arguments)"); return; } @@ -298,8 +451,8 @@ void _handleValidGpiosCommand(char* arg, std::size_t argLength) { SERPR_RESPONSE("ValidGPIOs|%s", buffer.c_str()); } -void _handleJsonConfigCommand(char* arg, std::size_t argLength) { - if (arg == nullptr || argLength == 0) { +void _handleJsonConfigCommand(StringView arg) { + if (arg.isNullOrEmpty()) { // Get raw config std::string json = Config::GetAsJSON(true); @@ -307,7 +460,7 @@ void _handleJsonConfigCommand(char* arg, std::size_t argLength) { return; } - if (!Config::SaveFromJSON(std::string(arg, argLength))) { + if (!Config::SaveFromJSON(arg)) { SERPR_ERROR("Failed to save config"); return; } @@ -317,8 +470,8 @@ void _handleJsonConfigCommand(char* arg, std::size_t argLength) { ESP.restart(); } -void _handleRawConfigCommand(char* arg, std::size_t argLength) { - if (arg == nullptr || argLength == 0) { +void _handleRawConfigCommand(StringView arg) { + if (arg.isNullOrEmpty()) { std::vector buffer; // Get raw config @@ -338,7 +491,7 @@ void _handleRawConfigCommand(char* arg, std::size_t argLength) { } std::vector buffer; - if (!OpenShock::Base64Utils::Decode(arg, argLength, buffer)) { + if (!OpenShock::Base64Utils::Decode(arg.data(), arg.length(), buffer)) { SERPR_ERROR("Failed to decode base64"); return; } @@ -353,9 +506,8 @@ void _handleRawConfigCommand(char* arg, std::size_t argLength) { ESP.restart(); } -void _handleDebugInfoCommand(char* arg, std::size_t argLength) { +void _handleDebugInfoCommand(StringView arg) { (void)arg; - (void)argLength; SERPR_RESPONSE("RTOSInfo|Free Heap|%u", xPortGetFreeHeapSize()); SERPR_RESPONSE("RTOSInfo|Min Free Heap|%u", xPortGetMinimumEverFreeHeapSize()); @@ -384,12 +536,12 @@ void _handleDebugInfoCommand(char* arg, std::size_t argLength) { } } -void _handleRFTransmitCommand(char* arg, std::size_t argLength) { - if (arg == nullptr || argLength == 0) { +void _handleRFTransmitCommand(StringView arg) { + if (arg.isNullOrEmpty()) { SERPR_ERROR("No command"); return; } - cJSON* root = cJSON_ParseWithLength(arg, argLength); + cJSON* root = cJSON_ParseWithLength(arg.data(), arg.length()); if (root == nullptr) { SERPR_ERROR("Failed to parse JSON: %s", cJSON_GetErrorPtr()); return; @@ -413,27 +565,13 @@ void _handleRFTransmitCommand(char* arg, std::size_t argLength) { SERPR_SUCCESS("Command sent"); } -void _handleHelpCommand(char* arg, std::size_t argLength) { - if (arg != nullptr && argLength > 0) { - // Convert argument to lowercase - std::transform(arg, arg + argLength, arg, ::tolower); - - // Get help for a specific command - auto it = s_commandHandlers.find(std::string(arg, argLength)); - if (it != s_commandHandlers.end()) { - Serial.print(it->second.helpResponse); - return; - } +void _handleHelpCommand(StringView arg) { + arg = arg.trim(); + if (arg.isNullOrEmpty()) { + SerialInputHandler::PrintWelcomeHeader(); - SERPR_ERROR("Command \"%.*s\" not found", argLength, arg); - - return; - } - - SerialInputHandler::PrintWelcomeHeader(); - - // Raw string literal (1+ to remove the first newline) - Serial.print(1 + R"( + // Raw string literal (1+ to remove the first newline) + Serial.print(1 + R"( help print this menu help print help for a command version print version information @@ -444,6 +582,8 @@ echo set serial echo enabled validgpios list all valid GPIO pins rftxpin get radio transmit pin rftxpin set radio transmit pin +domain get backend domain +domain set backend domain authtoken get auth token authtoken set auth token networks get all saved networks @@ -457,10 +597,21 @@ rawconfig set raw configuration from base64 rftransmit transmit a RF command factoryreset reset device to factory defaults and restart )"); + return; + } + + // Get help for a specific command + auto it = s_commandHandlers.find(arg); + if (it != s_commandHandlers.end()) { + Serial.print(it->second.helpResponse); + return; + } + + SERPR_ERROR("Command \"%.*s\" not found", arg.length(), arg.data()); } static const SerialCmdHandler kVersionCmdHandler = { - "version", + "version"_sv, R"(version Print version information Example: @@ -469,7 +620,7 @@ static const SerialCmdHandler kVersionCmdHandler = { _handleVersionCommand, }; static const SerialCmdHandler kRestartCmdHandler = { - "restart", + "restart"_sv, R"(restart Restart the board Example: @@ -478,7 +629,7 @@ static const SerialCmdHandler kRestartCmdHandler = { _handleRestartCommand, }; static const SerialCmdHandler kSystemInfoCmdHandler = { - "sysinfo", + "sysinfo"_sv, R"(sysinfo Get system information from RTOS, WiFi, etc. Example: @@ -487,7 +638,7 @@ static const SerialCmdHandler kSystemInfoCmdHandler = { _handleDebugInfoCommand, }; static const SerialCmdHandler kSerialEchoCmdHandler = { - "echo", + "echo"_sv, R"(echo Get the serial echo status. If enabled, typed characters are echoed back to the serial port. @@ -502,7 +653,7 @@ echo [] _handleSerialEchoCommand, }; static const SerialCmdHandler kValidGpiosCmdHandler = { - "validgpios", + "validgpios"_sv, R"(validgpios List all valid GPIO pins Example: @@ -511,7 +662,7 @@ static const SerialCmdHandler kValidGpiosCmdHandler = { _handleValidGpiosCommand, }; static const SerialCmdHandler kRfTxPinCmdHandler = { - "rftxpin", + "rftxpin"_sv, R"(rftxpin Get the GPIO pin used for the radio transmitter. @@ -524,8 +675,22 @@ rftxpin [] )", _handleRfTxPinCommand, }; +static const SerialCmdHandler kDomainCmdHandler = { + "domain"_sv, + R"(domain + Get the backend domain. + +domain [] + Set the backend domain. + Arguments: + must be a string. + Example: + domain api.shocklink.net +)", + _handleDomainCommand, +}; static const SerialCmdHandler kAuthTokenCmdHandler = { - "authtoken", + "authtoken"_sv, R"(authtoken Get the backend auth token. @@ -538,8 +703,27 @@ authtoken [] )", _handleAuthtokenCommand, }; +static const SerialCmdHandler kLcgOverrideCmdHandler = { + "lcgoverride", + R"(lcgoverride + Get the domain overridden for LCG endpoint (if any). + +lcgoverride set + Set a domain to override the LCG endpoint. + Arguments: + must be a string. + Example: + lcgoverride set eu1-gateway.shocklink.net + +lcgoverride clear + Clear the overridden LCG endpoint. + Example: + lcgoverride clear +)", + _handleLcgOverrideCommand, +}; static const SerialCmdHandler kNetworksCmdHandler = { - "networks", + "networks"_sv, R"(networks Get all saved networks. @@ -556,7 +740,7 @@ networks [] _handleNetworksCommand, }; static const SerialCmdHandler kKeepAliveCmdHandler = { - "keepalive", + "keepalive"_sv, R"(keepalive Get the shocker keep-alive status. @@ -570,7 +754,7 @@ keepalive [] _handleKeepAliveCommand, }; static const SerialCmdHandler kJsonConfigCmdHandler = { - "jsonconfig", + "jsonconfig"_sv, R"(jsonconfig Get the configuration as JSON Example: @@ -586,7 +770,7 @@ jsonconfig _handleJsonConfigCommand, }; static const SerialCmdHandler kRawConfigCmdHandler = { - "rawconfig", + "rawconfig"_sv, R"(rawconfig Get the raw binary config Example: @@ -602,12 +786,12 @@ rawconfig _handleRawConfigCommand, }; static const SerialCmdHandler kRfTransmitCmdHandler = { - "rftransmit", + "rftransmit"_sv, R"(rftransmit Transmit a RF command Arguments: must be a JSON object with the following fields: - model (string) Model of the shocker ("caixianlin", "petrainer") + model (string) Model of the shocker ("caixianlin", "petrainer", "petrainer998dr") id (number) ID of the shocker (0-65535) type (string) Type of the command ("shock", "vibrate", "sound", "stop") intensity (number) Intensity of the command (0-255) @@ -618,7 +802,7 @@ static const SerialCmdHandler kRfTransmitCmdHandler = { _handleRFTransmitCommand, }; static const SerialCmdHandler kFactoryResetCmdHandler = { - "factoryreset", + "factoryreset"_sv, R"(factoryreset Reset the device to factory defaults and restart Example: @@ -627,7 +811,7 @@ static const SerialCmdHandler kFactoryResetCmdHandler = { _handleFactoryResetCommand, }; static const SerialCmdHandler khelpCmdHandler = { - "help", + "help"_sv, R"(help [] Print help information Arguments: @@ -676,41 +860,24 @@ int findLineStart(const char* buffer, int bufferSize, int lineEnd) { return -1; } -void processSerialLine(char* data, std::size_t length) { - int delimiter = findChar(data, length, ' '); - if (delimiter == 0) { - SERPR_ERROR("Command cannot start with a space"); +void processSerialLine(StringView line) { + line = line.trim(); + if (line.isNullOrEmpty()) { + SERPR_ERROR("No command"); return; } - char* command = data; - std::size_t commandLength = length; - char* arg = nullptr; - std::size_t argLength = 0; - - // If there is a delimiter, split the command and argument - if (delimiter > 0) { - data[delimiter] = '\0'; - commandLength = delimiter; - arg = data + delimiter + 1; - argLength = length - delimiter - 1; - } - - // Convert command to lowercase - std::transform(command, command + commandLength, command, ::tolower); + auto parts = line.split(' ', 1); + StringView command = parts[0]; + StringView arguments = parts.size() > 1 ? parts[1] : StringView(); - // TODO: Clean this up, test this - auto it = s_commandHandlers.find(std::string(command, commandLength)); - if (it != s_commandHandlers.end()) { - it->second.commandHandler(arg, argLength); + auto it = s_commandHandlers.find(command); + if (it == s_commandHandlers.end()) { + SERPR_ERROR("Command \"%.*s\" not found", command.size(), command.data()); return; } - if (commandLength > 0) { - SERPR_ERROR("Command \"%.*s\" not found", commandLength, command); - } else { - SERPR_ERROR("No command"); - } + it->second.commandHandler(arguments); } bool SerialInputHandler::Init() { @@ -728,7 +895,9 @@ bool SerialInputHandler::Init() { RegisterCommandHandler(kSerialEchoCmdHandler); RegisterCommandHandler(kValidGpiosCmdHandler); RegisterCommandHandler(kRfTxPinCmdHandler); + RegisterCommandHandler(kDomainCmdHandler); RegisterCommandHandler(kAuthTokenCmdHandler); + RegisterCommandHandler(kLcgOverrideCmdHandler); RegisterCommandHandler(kNetworksCmdHandler); RegisterCommandHandler(kKeepAliveCmdHandler); RegisterCommandHandler(kJsonConfigCmdHandler); @@ -819,10 +988,11 @@ void SerialInputHandler::Update() { break; } - buffer[lineEnd] = '\0'; - Serial.printf("\r> %s\n", buffer); + StringView line = StringView(buffer, lineEnd).trim(); + + Serial.printf("\r> %.*s\n", line.size(), line.data()); - processSerialLine(buffer, lineEnd); + processSerialLine(line); int nextLine = findLineStart(buffer, bufferSize, lineEnd + 1); if (nextLine < 0) { diff --git a/src/serialization/JsonAPI.cpp b/src/serialization/JsonAPI.cpp index ab2a896a..eb15885f 100644 --- a/src/serialization/JsonAPI.cpp +++ b/src/serialization/JsonAPI.cpp @@ -8,6 +8,95 @@ const char* const TAG = "JsonAPI"; using namespace OpenShock::Serialization; +bool JsonAPI::ParseLcgInstanceDetailsJsonResponse(int code, const cJSON* root, JsonAPI::LcgInstanceDetailsResponse& out) { + (void)code; + + if (cJSON_IsObject(root) == 0) { + ESP_LOGJSONE("not an object", root); + return false; + } + + out = {}; + + const cJSON* name = cJSON_GetObjectItemCaseSensitive(root, "name"); + if (cJSON_IsString(name) == 0) { + ESP_LOGJSONE("value at 'data.name' is not a string", root); + return false; + } + + const cJSON* version = cJSON_GetObjectItemCaseSensitive(root, "version"); + if (cJSON_IsString(version) == 0) { + ESP_LOGJSONE("value at 'data.version' is not a string", root); + return false; + } + + const cJSON* currentTime = cJSON_GetObjectItemCaseSensitive(root, "currentTime"); + if (cJSON_IsString(currentTime) == 0) { + ESP_LOGJSONE("value at 'data.currentTime' is not a string", root); + return false; + } + + const cJSON* countryCode = cJSON_GetObjectItemCaseSensitive(root, "countryCode"); + if (cJSON_IsString(countryCode) == 0) { + ESP_LOGJSONE("value at 'data.countryCode' is not a string", root); + return false; + } + + const cJSON* fqdn = cJSON_GetObjectItemCaseSensitive(root, "fqdn"); + if (cJSON_IsString(fqdn) == 0) { + ESP_LOGJSONE("value at 'data.fqdn' is not a string", root); + return false; + } + + out.name = name->valuestring; + out.version = version->valuestring; + out.currentTime = currentTime->valuestring; + out.countryCode = countryCode->valuestring; + out.fqdn = fqdn->valuestring; + + return true; +} +bool JsonAPI::ParseBackendVersionJsonResponse(int code, const cJSON* root, JsonAPI::BackendVersionResponse& out) { + (void)code; + + if (cJSON_IsObject(root) == 0) { + ESP_LOGJSONE("not an object", root); + return false; + } + + const cJSON* data = cJSON_GetObjectItemCaseSensitive(root, "data"); + if (cJSON_IsObject(data) == 0) { + ESP_LOGJSONE("value at 'data' is not an object", root); + return false; + } + + out = {}; + + const cJSON* version = cJSON_GetObjectItemCaseSensitive(data, "version"); + if (cJSON_IsString(version) == 0) { + ESP_LOGJSONE("value at 'data.version' is not a string", root); + return false; + } + + const cJSON* commit = cJSON_GetObjectItemCaseSensitive(data, "commit"); + if (cJSON_IsString(commit) == 0) { + ESP_LOGJSONE("value at 'data.commit' is not a string", root); + return false; + } + + const cJSON* currentTime = cJSON_GetObjectItemCaseSensitive(data, "currentTime"); + if (cJSON_IsString(currentTime) == 0) { + ESP_LOGJSONE("value at 'data.currentTime' is not a string", root); + return false; + } + + out.version = version->valuestring; + out.commit = commit->valuestring; + out.currentTime = currentTime->valuestring; + + return true; +} + bool JsonAPI::ParseAccountLinkJsonResponse(int code, const cJSON* root, JsonAPI::AccountLinkResponse& out) { (void)code; diff --git a/src/serialization/JsonSerial.cpp b/src/serialization/JsonSerial.cpp index 3155ca15..210feb1f 100644 --- a/src/serialization/JsonSerial.cpp +++ b/src/serialization/JsonSerial.cpp @@ -23,7 +23,7 @@ bool JsonSerial::ParseShockerCommand(const cJSON* root, JsonSerial::ShockerComma } ShockerModelType modelType; if (!ShockerModelTypeFromString(model->valuestring, modelType)) { - ESP_LOGE(TAG, "value at 'model' is not a valid shocker model (caixianlin, petrainer)"); + ESP_LOGE(TAG, "value at 'model' is not a valid shocker model (caixianlin, petrainer, petrainer998dr)"); return false; } diff --git a/src/util/StringUtils.cpp b/src/util/StringUtils.cpp index 3d7603d2..d32c6d55 100644 --- a/src/util/StringUtils.cpp +++ b/src/util/StringUtils.cpp @@ -8,7 +8,7 @@ static const char* TAG = "StringUtils"; bool OpenShock::FormatToString(std::string& out, const char* format, ...) { - constexpr std::size_t STACK_BUFFER_SIZE = 128; + const std::size_t STACK_BUFFER_SIZE = 128; char buffer[STACK_BUFFER_SIZE]; char* bufferPtr = buffer; diff --git a/src/wifi/WiFiManager.cpp b/src/wifi/WiFiManager.cpp index 04e1890e..3e38a6a7 100644 --- a/src/wifi/WiFiManager.cpp +++ b/src/wifi/WiFiManager.cpp @@ -134,7 +134,7 @@ bool _connectImpl(const char* ssid, const char* password, const std::uint8_t (&b } bool _connectHidden(const std::uint8_t (&bssid)[6], const std::string& password) { (void)password; - + ESP_LOGV(TAG, "Connecting to hidden network " BSSID_FMT, BSSID_ARG(bssid)); // TODO: Implement hidden network support @@ -171,7 +171,7 @@ bool _connect(const std::uint8_t (&bssid)[6], const std::string& password) { return _connectImpl(it->ssid, password.c_str(), bssid); } -bool _authenticate(const WiFiNetwork& net, const std::string& password) { +bool _authenticate(const WiFiNetwork& net, StringView password) { std::uint8_t id = Config::AddWiFiCredentials(net.ssid, password); if (id == 0) { Serialization::Local::SerializeErrorMessage("too_many_credentials", CaptivePortal::BroadcastMessageBIN); @@ -180,7 +180,7 @@ bool _authenticate(const WiFiNetwork& net, const std::string& password) { Serialization::Local::SerializeWiFiNetworkEvent(Serialization::Types::WifiNetworkEventType::Saved, net, CaptivePortal::BroadcastMessageBIN); - return _connect(net.ssid, password); + return _connect(net.ssid, password.toString()); } void _evWiFiConnected(arduino_event_t* event) { @@ -342,7 +342,7 @@ bool WiFiManager::Init() { return true; } -bool WiFiManager::Save(const char* ssid, const std::string& password) { +bool WiFiManager::Save(const char* ssid, StringView password) { ESP_LOGV(TAG, "Authenticating to network %s", ssid); auto it = _findNetworkBySSID(ssid); diff --git a/src/wifi/WiFiScanManager.cpp b/src/wifi/WiFiScanManager.cpp index 96ca0efb..b12e5ac4 100644 --- a/src/wifi/WiFiScanManager.cpp +++ b/src/wifi/WiFiScanManager.cpp @@ -8,9 +8,9 @@ const char* const TAG = "WiFiScanManager"; -constexpr const std::uint8_t OPENSHOCK_WIFI_SCAN_MAX_CHANNEL = 13; -constexpr const std::uint32_t OPENSHOCK_WIFI_SCAN_MAX_MS_PER_CHANNEL = 300; // Adjusting this value will affect the scan rate, but may also affect the scan results -constexpr const std::uint32_t OPENSHOCK_WIFI_SCAN_TIMEOUT_MS = 10 * 1000; +const std::uint8_t OPENSHOCK_WIFI_SCAN_MAX_CHANNEL = 13; +const std::uint32_t OPENSHOCK_WIFI_SCAN_MAX_MS_PER_CHANNEL = 300; // Adjusting this value will affect the scan rate, but may also affect the scan results +const std::uint32_t OPENSHOCK_WIFI_SCAN_TIMEOUT_MS = 10 * 1000; enum WiFiScanTaskNotificationFlags { CHANNEL_DONE = 1 << 0,