diff --git a/package-lock.json b/package-lock.json index e5e7cd6..3eb22cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -836,6 +836,16 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@envelop/instrumentation": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@envelop/instrumentation/-/instrumentation-1.0.1.tgz", @@ -1755,6 +1765,506 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz", + "integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz", + "integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz", + "integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz", + "integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz", + "integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz", + "integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz", + "integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz", + "integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz", + "integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz", + "integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz", + "integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz", + "integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz", + "integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz", + "integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz", + "integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz", + "integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz", + "integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz", + "integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz", + "integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz", + "integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz", + "integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz", + "integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz", + "integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz", + "integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz", + "integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz", + "integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@ionic/cli-framework-output": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/@ionic/cli-framework-output/-/cli-framework-output-2.2.8.tgz", @@ -4997,12 +5507,6 @@ "@types/leaflet": "^1.9" } }, - "node_modules/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", - "license": "MIT" - }, "node_modules/@types/ngeohash": { "version": "0.6.8", "resolved": "https://registry.npmjs.org/@types/ngeohash/-/ngeohash-0.6.8.tgz", @@ -6156,20 +6660,6 @@ "postcss": "^8.1.0" } }, - "node_modules/b4a": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.9.0.tgz", - "integrity": "sha512-dpfcF9fDNR6++cthXR67iyhgqWy9CBouAvIWhIntzBG6cvK/cnIPiZQjBwi/ZqjjBEDGfoNDtmB0kTjroOJ3pQ==", - "license": "Apache-2.0", - "peerDependencies": { - "react-native-b4a": "*" - }, - "peerDependenciesMeta": { - "react-native-b4a": { - "optional": true - } - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -6177,86 +6667,6 @@ "dev": true, "license": "MIT" }, - "node_modules/bare-events": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.2.tgz", - "integrity": "sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==", - "license": "Apache-2.0", - "peerDependencies": { - "bare-abort-controller": "*" - }, - "peerDependenciesMeta": { - "bare-abort-controller": { - "optional": true - } - } - }, - "node_modules/bare-fs": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.8.4.tgz", - "integrity": "sha512-MPC2eyrpbk3A873sK4y6rk+Iv4o2enxTjHZ8zH3HO0mjrpm5XjnguY2XmfidIsDV6vnjpbMBKUAJpV0ISD2SsQ==", - "license": "Apache-2.0", - "dependencies": { - "bare-events": "^2.5.4", - "bare-path": "^3.0.0", - "bare-stream": "^2.6.4", - "bare-url": "^2.2.2", - "fast-fifo": "^1.3.2" - }, - "engines": { - "bare": ">=1.28.0" - }, - "peerDependencies": { - "bare-buffer": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - } - } - }, - "node_modules/bare-path": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.2.tgz", - "integrity": "sha512-ZyKbsuuqK6Ag0K8pX6V5Txq6XeJRvY+wXucnFGRjiyVYP9YWDpIQugk/b+enRYrEYBJaqLzghRQpXPMR7341Nw==", - "license": "Apache-2.0" - }, - "node_modules/bare-stream": { - "version": "2.13.4", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.4.tgz", - "integrity": "sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA==", - "license": "Apache-2.0", - "dependencies": { - "b4a": "^1.8.1", - "streamx": "^2.25.0", - "teex": "^1.0.1" - }, - "peerDependencies": { - "bare-abort-controller": "*", - "bare-buffer": "*", - "bare-events": "*" - }, - "peerDependenciesMeta": { - "bare-abort-controller": { - "optional": true - }, - "bare-buffer": { - "optional": true - }, - "bare-events": { - "optional": true - } - } - }, - "node_modules/bare-url": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.5.5.tgz", - "integrity": "sha512-iM9qJCruvpt2rPaLsrumVDru9pogI7PHziHIa9kTNRFt8C8HuSa+kI9kFezK+NvHEFnnz71rKGZ1EAilq/3V/Q==", - "license": "Apache-2.0", - "dependencies": { - "bare-path": "^3.0.0" - } - }, "node_modules/base-x": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz", @@ -6377,41 +6787,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/bn.js": { "version": "4.12.5", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", @@ -7007,19 +7382,6 @@ "node": ">=0.8" } }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -7038,16 +7400,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "license": "MIT", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -7411,30 +7763,6 @@ "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", "license": "MIT" }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -7735,15 +8063,6 @@ "node": ">= 0.8" } }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/entities": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", @@ -8101,15 +8420,6 @@ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "license": "MIT" }, - "node_modules/events-universal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", - "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", - "license": "Apache-2.0", - "dependencies": { - "bare-events": "^2.7.0" - } - }, "node_modules/eventsource": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", @@ -8131,15 +8441,6 @@ "node": ">=18.0.0" } }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "license": "(MIT OR WTFPL)", - "engines": { - "node": ">=6" - } - }, "node_modules/expect-type": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", @@ -8227,12 +8528,6 @@ "node": ">=6.0.0" } }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "license": "MIT" - }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -8497,12 +8792,6 @@ "node": ">= 0.8" } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" - }, "node_modules/fs-extra": { "version": "11.4.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.1.tgz", @@ -8610,12 +8899,6 @@ "node": ">= 0.4" } }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" - }, "node_modules/glob": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", @@ -9044,12 +9327,6 @@ "node": ">= 0.10" } }, - "node_modules/is-arrayish": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", - "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", - "license": "MIT" - }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -9668,18 +9945,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -9714,6 +9979,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -9742,12 +10008,6 @@ "node": ">= 18" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" - }, "node_modules/mlly": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", @@ -9803,12 +10063,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" - }, "node_modules/native-run": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/native-run/-/native-run-2.0.3.tgz", @@ -9880,24 +10134,6 @@ "node": ">=v0.2.0" } }, - "node_modules/node-abi": { - "version": "3.101.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.101.0.tgz", - "integrity": "sha512-bkb/RkuWCE64eWu1PCh3Fu2pGc9mKRs+kOE+FU2bGHRYlQa86/D321DSATV54AsCQMKz3/xSt9sRgraZTpqdhQ==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-addon-api": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", - "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", - "license": "MIT" - }, "node_modules/node-releases": { "version": "2.0.60", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.60.tgz", @@ -10702,67 +10938,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/prebuild-install/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, - "node_modules/prebuild-install/node_modules/tar-fs": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz", - "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==", - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/prebuild-install/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -10876,31 +11051,35 @@ "license": "MIT" }, "node_modules/protobufjs": { - "version": "6.11.6", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.6.tgz", - "integrity": "sha512-k8BHqgPBOtrlougZZqF2uUk5Z7bN8f0wj+3e8M3hvtSv0NBAz4VBy5f6R5Nxq/l+i7mRFTgNZb2trxqTpHNY/A==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.3.tgz", + "integrity": "sha512-+k0vdJKNdW+Vu+dYe8tZA/VvQb6XKNWexC6URwBFXxNnjLJz9nQJCemGyNgRAWD+B7+nGNc9qMPGwcD7s4nzUw==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", + "@protobufjs/inquire": "^1.1.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", + "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", - "long": "^4.0.0" + "long": "^5.3.2" }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" + "engines": { + "node": ">=12.0.0" } }, + "node_modules/protobufjs/node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, "node_modules/proxy-addr": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", @@ -10918,16 +11097,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -11052,36 +11221,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -11333,6 +11472,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -11952,6 +12092,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, "funding": [ { "type": "github", @@ -12013,9 +12154,9 @@ } }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -12088,26 +12229,52 @@ "license": "ISC" }, "node_modules/sharp": { - "version": "0.32.6", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", - "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", - "hasInstallScript": true, + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz", + "integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==", "license": "Apache-2.0", "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.2", - "node-addon-api": "^6.1.0", - "prebuild-install": "^7.1.1", - "semver": "^7.5.4", - "simple-get": "^4.0.1", - "tar-fs": "^3.0.4", - "tunnel-agent": "^0.6.0" + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" }, "engines": { - "node": ">=14.15.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.5", + "@img/sharp-darwin-x64": "0.35.5", + "@img/sharp-freebsd-wasm32": "0.35.5", + "@img/sharp-libvips-darwin-arm64": "1.3.4", + "@img/sharp-libvips-darwin-x64": "1.3.4", + "@img/sharp-libvips-linux-arm": "1.3.4", + "@img/sharp-libvips-linux-arm64": "1.3.4", + "@img/sharp-libvips-linux-ppc64": "1.3.4", + "@img/sharp-libvips-linux-riscv64": "1.3.4", + "@img/sharp-libvips-linux-s390x": "1.3.4", + "@img/sharp-libvips-linux-x64": "1.3.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", + "@img/sharp-libvips-linuxmusl-x64": "1.3.4", + "@img/sharp-linux-arm": "0.35.5", + "@img/sharp-linux-arm64": "0.35.5", + "@img/sharp-linux-ppc64": "0.35.5", + "@img/sharp-linux-riscv64": "0.35.5", + "@img/sharp-linux-s390x": "0.35.5", + "@img/sharp-linux-x64": "0.35.5", + "@img/sharp-linuxmusl-arm64": "0.35.5", + "@img/sharp-linuxmusl-x64": "0.35.5", + "@img/sharp-webcontainers-wasm32": "0.35.5", + "@img/sharp-win32-arm64": "0.35.5", + "@img/sharp-win32-ia32": "0.35.5", + "@img/sharp-win32-x64": "0.35.5" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/shebang-command": { @@ -12216,51 +12383,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, "node_modules/simple-plist": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.3.1.tgz", @@ -12286,15 +12408,6 @@ "node": ">= 5.10.0" } }, - "node_modules/simple-swizzle": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", - "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -12414,21 +12527,11 @@ "node": ">= 0.10.0" } }, - "node_modules/streamx": { - "version": "2.28.1", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.1.tgz", - "integrity": "sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA==", - "license": "MIT", - "dependencies": { - "events-universal": "^1.0.0", - "fast-fifo": "^1.3.2", - "text-decoder": "^1.1.0" - } - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -12733,50 +12836,6 @@ "node": ">=18" } }, - "node_modules/tar-fs": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.3.tgz", - "integrity": "sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ==", - "license": "MIT", - "dependencies": { - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - }, - "optionalDependencies": { - "bare-fs": "^4.0.1", - "bare-path": "^3.0.0" - } - }, - "node_modules/tar-stream": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.2.tgz", - "integrity": "sha512-+8NeqHRjQWH9nYlwo2gamAMImZCVzI4UoEgDpWorBt9OEfppiZn+uSkskzQKPWIZyji/C8fpWO7u69G0DX0tbg==", - "license": "MIT", - "dependencies": { - "b4a": "^1.9.0", - "bare-fs": "^4.8.2", - "fast-fifo": "^1.3.2", - "streamx": "^2.28.1" - } - }, - "node_modules/teex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", - "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", - "license": "MIT", - "dependencies": { - "streamx": "^2.12.5" - } - }, - "node_modules/text-decoder": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", - "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", - "license": "Apache-2.0", - "dependencies": { - "b4a": "^1.6.4" - } - }, "node_modules/text-segmentation": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", @@ -13543,18 +13602,6 @@ "@esbuild/win32-x64": "0.28.2" } }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -13857,6 +13904,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, "license": "MIT" }, "node_modules/utrie": { diff --git a/package.json b/package.json index 90a1a5c..d6145bd 100644 --- a/package.json +++ b/package.json @@ -179,6 +179,8 @@ "web-push": "^3.6.7" }, "overrides": { - "@tanstack/query-core": "$@tanstack/react-query" + "@tanstack/query-core": "$@tanstack/react-query", + "protobufjs": "7.6.3", + "sharp": "0.35.5" } } diff --git a/scripts/cordn-benchmark.ts b/scripts/cordn-benchmark.ts new file mode 100644 index 0000000..e50e743 --- /dev/null +++ b/scripts/cordn-benchmark.ts @@ -0,0 +1,81 @@ +/** + * Live Cordn native benchmark — send/decrypt/state-size on the real coordinator. + * + * npx tsx scripts/cordn-benchmark.ts + * CORDN_BENCH_N=50 npx tsx scripts/cordn-benchmark.ts + * + * Creates a throwaway two-member group, warms up, measures N encrypted sends + * and N decrypting fetches, and reports the persisted snapshot size (the same + * shape IndexedDB stores). No user data or secrets are logged. + */ +import { performance } from 'node:perf_hooks'; +import { CordnSession, MemoryCordnStore, type CordnSnapshot, type CordnStore } from '../src/lib/cordn/native/session'; + +const N = Math.max(5, Math.min(Number(process.env.CORDN_BENCH_N ?? 30), 100)); +const coordinator = { + serverPubkey: '02816a05b1d4e70a7df9c712ef6f7272774269d65bd8a2f262ce0330d0885693', + relays: ['wss://relay.skate-spots.org'], +}; + +class MeasuringStore implements CordnStore { + private inner = new MemoryCordnStore(); + lastBytes = 0; + async load(): Promise { + return this.inner.load(); + } + async save(snapshot: CordnSnapshot): Promise { + this.lastBytes = JSON.stringify(snapshot).length; + await this.inner.save(snapshot); + } + async clear(): Promise { + await this.inner.clear(); + } +} + +const aStore = new MeasuringStore(); +const bStore = new MeasuringStore(); +const a = await CordnSession.open({ coordinator, store: aStore }); +const b = await CordnSession.open({ coordinator, store: bStore }); +await a.generateKeyPackage('a'); +const bk = await b.generateKeyPackage('b'); +await a.publishKeyPackage('a'); +await b.publishKeyPackage('b'); +const gid = `bench-${Date.now()}`; +const group = await a.createGroup({ groupId: gid, name: 'Benchmark' }); +const { welcomeBase64 } = await a.addMember(group.gid, bk.keyPackage); +const bGroup = await b.joinGroup({ welcomeBase64, keyPackage: bk, name: 'Benchmark' }); + +for (let i = 0; i < 3; i += 1) { + await a.send(gid, `warmup ${i}`); + await b.fetch(gid); +} + +const sendStart = performance.now(); +for (let i = 0; i < N; i += 1) await a.send(gid, `bench message ${i}`); +const sendMs = performance.now() - sendStart; + +const fetchStart = performance.now(); +const received = await b.fetch(bGroup.gid); +const fetchMs = performance.now() - fetchStart; + +const sendPer = sendMs / N; +const fetchPer = fetchMs / N; +console.log( + JSON.stringify( + { + messages: N, + sendMs: Math.round(sendMs), + sendPerMessageMs: Math.round(sendPer * 10) / 10, + fetchMs: Math.round(fetchMs), + fetchPerMessageMs: Math.round(fetchPer * 10) / 10, + received: received.length, + snapshotBytesA: aStore.lastBytes, + snapshotBytesB: bStore.lastBytes, + note: 'Memory snapshot shape matches the IndexedDB adapter; network + coordinator included.', + }, + null, + 2, + ), +); +await a.disconnect(); +await b.disconnect(); diff --git a/src/components/crew/CordnNativePanel.tsx b/src/components/crew/CordnNativePanel.tsx index 84f0139..04351ab 100644 --- a/src/components/crew/CordnNativePanel.tsx +++ b/src/components/crew/CordnNativePanel.tsx @@ -1,4 +1,5 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { Link } from 'react-router-dom'; import type { ReactNode } from 'react'; import { AlertTriangle, @@ -13,8 +14,11 @@ import { Plus, QrCode, RefreshCw, + Reply, + ScanLine, Send, Share2, + Trash2, UserPlus, Users, X, @@ -33,6 +37,7 @@ import { } from '@/components/ui/dialog'; import { QRCodeCanvas } from '@/components/ui/qrcode'; import { ImageWithFallback } from '@/components/ImageWithFallback'; +import { QRScannerModal } from '@/components/wallet/QRScannerModal'; import { useTranslation } from '@/contexts/language'; import { useAuthor } from '@/hooks/useAuthor'; import { useCurrentUser } from '@/hooks/useCurrentUser'; @@ -143,8 +148,10 @@ export function CordnNativePanel({ const sendNative = native.send; const checkInviteNative = native.checkInvite; const [draft, setDraft] = useState(''); + const [replyTo, setReplyTo] = useState<{ id: string; content: string } | null>(null); const [copied, setCopied] = useState(false); const [copiedLink, setCopiedLink] = useState(false); + const [copiedDevice, setCopiedDevice] = useState(false); const [notice, setNotice] = useState(null); const [awaiting, setAwaiting] = useState(() => user?.pubkey ? readPendingJoin(user.pubkey, native.groupId) : false, @@ -152,6 +159,10 @@ export function CordnNativePanel({ const [pending, setPending] = useState(null); const [sentIds, setSentIds] = useState>(() => new Set()); const [inviteOpen, setInviteOpen] = useState(false); + const [deviceLinkOpen, setDeviceLinkOpen] = useState(false); + const [deviceLinkCode, setDeviceLinkCode] = useState(null); + const [deviceLinkPending, setDeviceLinkPending] = useState(false); + const [scannerOpen, setScannerOpen] = useState(false); const scrollRef = useRef(null); const fileRef = useRef(null); const checkingRef = useRef(false); @@ -180,6 +191,9 @@ export function CordnNativePanel({ if (native.group && awaiting) { if (user?.pubkey) clearPendingJoin(user.pubkey, native.groupId); setAwaiting(false); + setDeviceLinkOpen(false); + setDeviceLinkCode(null); + setDeviceLinkPending(false); } }, [native.group, awaiting, user?.pubkey, native.groupId]); @@ -259,6 +273,25 @@ export function CordnNativePanel({ }, 3000); }, []); + const withReply = useCallback( + (text: string) => + replyTo + ? `> ${replyTo.content.replace(/\n/g, '\n> ')}\n\n${text}` + : text, + [replyTo], + ); + + const copyMessage = useCallback(async (text: string) => { + if (!text) return; + try { + await navigator.clipboard.writeText(text); + setNotice(t('common.copied')); + window.setTimeout(() => setNotice(null), 1500); + } catch { + // clipboard blocked + } + }, [t]); + const deliver = useCallback( async (text: string, imageUrl?: string) => { setNotice(null); @@ -278,8 +311,16 @@ export function CordnNativePanel({ const body = draft.trim(); if (!body || native.busy) return; setDraft(''); - await deliver(body); - }, [draft, native.busy, deliver]); + const content = withReply(body); + const sent = await native.send(content); + if (sent) { + markSent(sent.id); + setReplyTo(null); + setPending(null); + } else { + setPending({ text: content, state: 'failed' }); + } + }, [draft, native, withReply, markSent]); const retrySend = useCallback(async () => { if (!pending || pending.state !== 'failed') return; @@ -295,12 +336,12 @@ export function CordnNativePanel({ if (!url) throw new Error('upload returned no url'); const caption = draft.trim(); setDraft(''); - await deliver(caption, url); + await deliver(withReply(caption), url); } catch { setNotice(t('cordnUi.uploadFailed')); } }, - [uploadFile, draft, deliver, t], + [uploadFile, draft, deliver, withReply, t], ); const copyRef = useCallback(async () => { @@ -324,6 +365,39 @@ export function CordnNativePanel({ } }, [inviteUrl]); + const openDeviceLink = useCallback(async () => { + setDeviceLinkOpen(true); + setDeviceLinkCode(null); + setDeviceLinkPending(true); + setNotice(null); + const code = await native.createDeviceLink(); + if (!code) { + setDeviceLinkPending(false); + setNotice(t('cordnUi.deviceLinkFailed')); + return; + } + setDeviceLinkCode(code); + if (user?.pubkey) writePendingJoin(user.pubkey, native.groupId); + setAwaiting(true); + }, [native, user?.pubkey, t]); + + const copyDeviceLink = useCallback(async () => { + if (!deviceLinkCode) return; + try { + await navigator.clipboard.writeText(deviceLinkCode); + setCopiedDevice(true); + window.setTimeout(() => setCopiedDevice(false), 1500); + } catch { + // clipboard blocked — the code is visible + } + }, [deviceLinkCode]); + + const addScannedDevice = useCallback(async (raw: string) => { + setNotice(null); + const ok = await native.addDevice(raw); + setNotice(ok ? t('cordnUi.deviceLinked') : t('cordnUi.deviceLinkFailed')); + }, [native, t]); + const checkNow = useCallback(async () => { if (checkingRef.current) return false; checkingRef.current = true; @@ -584,11 +658,24 @@ export function CordnNativePanel({ )} + {awaiting && (

- {native.interactiveHintVisible ? ( + {deviceLinkPending ? ( + <> + {t('cordnUi.waitingDevice')} + + ) : native.interactiveHintVisible ? ( <> {t('cordnUi.waitingApprovalManual')} @@ -622,16 +709,40 @@ export function CordnNativePanel({

{native.group.members.map((member) => ( { + if (!window.confirm(t('cordnUi.removeMemberConfirm', { name: shortNpub(member.pubkey) }))) return; + void native.removeMember(member.leafIndex).then((ok) => { + if (!ok) setNotice(t('cordnUi.removeMemberFailed')); + }); + }} /> ))}
+
+ {!isCommunity && user.pubkey === crew.pubkey && ( + + )} + +
{t('cordnUi.sent')} )} + + + +
+ {replyTo && ( +
+ + + {t('cordnUi.reply')}: {replyTo.content || t('cordnUi.attachImage')} + + +
+ )} +
+ + { + setDeviceLinkOpen(open); + if (!open) setDeviceLinkCode(null); + }} + > + + + + {t('cordnUi.linkDeviceTitle')} + + {t('cordnUi.linkDeviceHint')} + + {deviceLinkCode ? ( + <> +
+ +
+ + {deviceLinkCode} + +
+ + +
+ + ) : ( +
+ {t('common.loading')} +
+ )} +

{t('cordnUi.linkDeviceNote')}

+
+
+ + setScannerOpen(false)} + onResult={(result) => void addScannedDevice(result.value)} + /> ); } @@ -1022,6 +1228,8 @@ function MemberRow({ founderLabel, botPubkey, botName, + canRemove, + onRemove, }: { pubkey: string; role: MemberRole; @@ -1029,6 +1237,8 @@ function MemberRow({ founderLabel: string; botPubkey?: string; botName?: string; + canRemove?: boolean; + onRemove?: () => void; }) { const { t } = useTranslation(); const label = @@ -1040,6 +1250,7 @@ function MemberRow({ return (
+
{label} + {canRemove && onRemove && ( + + )} +
); } diff --git a/src/hooks/useCordnNative.ts b/src/hooks/useCordnNative.ts index 10f33a8..9436bf9 100644 --- a/src/hooks/useCordnNative.ts +++ b/src/hooks/useCordnNative.ts @@ -17,6 +17,7 @@ import type { } from '@/lib/cordn/native'; import { isInteractiveSigner, isPassiveSigner } from '@/lib/signerCapabilities'; import type { CordnRoom } from '@/lib/cordn/room'; +import { decodeDeviceLink, encodeDeviceLink } from '@/lib/cordn/deviceLink'; /** Resolved coordinator endpoint for the native (ContextVM) client. */ export interface NativeCoordinator { @@ -74,6 +75,12 @@ export interface UseCordnNativeResult { loadRequestsQuiet: () => Promise; /** Founder/admin: add a requester (by pubkey) and deliver a welcome. */ approve: (identifier: string) => Promise; + /** Founder/admin: remove a member leaf (device) from the group. */ + removeMember: (leafIndex: number) => Promise; + /** New device: publish a key package and return the QR payload. */ + createDeviceLink: () => Promise; + /** Existing member: scan a new-device payload and add it. */ + addDevice: (raw: string) => Promise; /** True for interactive signers (Amber) — manual refresh/send, no polling. */ interactiveHintVisible: boolean; /** True for nsec/bunker: safe to poll while the private tab is hidden. */ @@ -326,6 +333,59 @@ export function useCordnNative({ } }, [groupId]); + const removeMember = useCallback( + async (leafIndex: number) => { + const session = sessionRef.current; + if (!session || !group) return false; + const removed = await run(async () => { + const next = await session.removeMember(group.gid, leafIndex); + setGroup(next); + setMessages(session.messages(group.gid)); + return true; + }); + return removed === true; + }, + [group, run], + ); + + const createDeviceLink = useCallback(async () => { + const session = sessionRef.current; + if (!session || !groupId || !coordinator) return null; + const code = await run(async () => { + const keyPackage = await session.publishKeyPackage(`device-${Date.now()}`); + return encodeDeviceLink({ + gid: groupId, + kpRef: keyPackage.keyPackageRef, + pubkey: session.pubkey, + coordinatorPubkey: coordinator.serverPubkey, + relays: coordinator.relays, + }); + }); + return code ?? null; + }, [groupId, coordinator, run]); + + const addDevice = useCallback( + async (raw: string) => { + const session = sessionRef.current; + const link = decodeDeviceLink(raw); + if (!session || !group || !coordinator || !link) return false; + if (link.gid !== groupId || link.coordinatorPubkey !== coordinator.serverPubkey) return false; + const added = await run(async () => { + try { + await session.addMemberByIdentifier(group.gid, link.kpRef); + } catch { + await session.addMemberByIdentifier(group.gid, link.pubkey); + } + const next = session.listGroups().find((g) => g.gid === group.gid) ?? group; + setGroup(next); + setMessages(session.messages(group.gid)); + return true; + }); + return added === true; + }, + [group, groupId, coordinator, run], + ); + const approve = useCallback( async (identifier: string) => { const session = sessionRef.current; @@ -358,6 +418,9 @@ export function useCordnNative({ loadRequests, loadRequestsQuiet, approve, + removeMember, + createDeviceLink, + addDevice, interactiveHintVisible: interactive, passiveSigner, groupRef, diff --git a/src/i18n/ca/batch1.ts b/src/i18n/ca/batch1.ts index c1d14c1..f564ed0 100644 --- a/src/i18n/ca/batch1.ts +++ b/src/i18n/ca/batch1.ts @@ -975,6 +975,17 @@ const batch1 = { errorDetails: "Detalls", unreachable: "Inabastable", dismiss: "Tanca", + linkDevice: "Enllaça aquest dispositiu", + linkDeviceTitle: "Enllaça un altre dispositiu", + linkDeviceHint: "Escaneja aquest codi en un dispositiu que ja sigui a la sala.", + linkDeviceNote: "El codi conté un paquet de claus públiques. La teva clau privada es queda al dispositiu nou.", + waitingDevice: "S’està esperant l’altre dispositiu…", + addDevice: "Afegeix un dispositiu", + deviceLinked: "Dispositiu afegit.", + deviceLinkFailed: "No s’ha pogut afegir aquest dispositiu.", + removeMemberConfirm: "Vols treure {{name}} del xat privat? Aquest dispositiu perdrà l’accés.", + removeMemberFailed: "No s’ha pogut treure aquest membre.", + reply: "Respon", }, communityUi: { "link": "Comunitat", diff --git a/src/i18n/cs/drift.ts b/src/i18n/cs/drift.ts index 388911b..ffd8e38 100644 --- a/src/i18n/cs/drift.ts +++ b/src/i18n/cs/drift.ts @@ -353,6 +353,17 @@ const drift = { errorDetails: "Podrobnosti", unreachable: "Nedostupné", dismiss: "Zavřít", + linkDevice: "Propojit toto zařízení", + linkDeviceTitle: "Propojit jiné zařízení", + linkDeviceHint: "Naskenujte tento kód na zařízení, které už je v místnosti.", + linkDeviceNote: "Kód obsahuje balíček veřejných klíčů. Váš soukromý klíč zůstává na novém zařízení.", + waitingDevice: "Čekání na druhé zařízení…", + addDevice: "Přidat zařízení", + deviceLinked: "Zařízení přidáno.", + deviceLinkFailed: "Toto zařízení se nepodařilo přidat.", + removeMemberConfirm: "Odebrat {{name}} ze soukromého chatu? Toto zařízení ztratí přístup.", + removeMemberFailed: "Tohoto člena se nepodařilo odebrat.", + reply: "Odpovědět", }, communityUi: { "link": "Komunita", diff --git a/src/i18n/de/drift.ts b/src/i18n/de/drift.ts index dc07bd4..91303ef 100644 --- a/src/i18n/de/drift.ts +++ b/src/i18n/de/drift.ts @@ -355,6 +355,17 @@ const drift = { errorDetails: "Details", unreachable: "Nicht erreichbar", dismiss: "Schließen", + linkDevice: "Dieses Gerät verknüpfen", + linkDeviceTitle: "Weiteres Gerät verknüpfen", + linkDeviceHint: "Scanne diesen Code auf einem Gerät, das bereits im Raum ist.", + linkDeviceNote: "Der Code enthält ein öffentliches Schlüsselpaket. Dein privater Schlüssel bleibt auf dem neuen Gerät.", + waitingDevice: "Warte auf das andere Gerät…", + addDevice: "Gerät hinzufügen", + deviceLinked: "Gerät hinzugefügt.", + deviceLinkFailed: "Dieses Gerät konnte nicht hinzugefügt werden.", + removeMemberConfirm: "{{name}} aus dem privaten Chat entfernen? Dieses Gerät verliert den Zugriff.", + removeMemberFailed: "Dieses Mitglied konnte nicht entfernt werden.", + reply: "Antworten", }, communityUi: { link: "Community", diff --git a/src/i18n/el/drift.ts b/src/i18n/el/drift.ts index 138a7f4..a05728f 100644 --- a/src/i18n/el/drift.ts +++ b/src/i18n/el/drift.ts @@ -357,6 +357,17 @@ const drift = { errorDetails: "Λεπτομέρειες", unreachable: "Μη προσβάσιμο", dismiss: "Κλείσιμο", + linkDevice: "Σύνδεση αυτής της συσκευής", + linkDeviceTitle: "Σύνδεση άλλης συσκευής", + linkDeviceHint: "Σάρωσε αυτόν τον κώδικα σε μια συσκευή που βρίσκεται ήδη στην αίθουσα.", + linkDeviceNote: "Ο κώδικας περιέχει ένα πακέτο δημόσιων κλειδιών. Το ιδιωτικό σου κλειδί μένει στη νέα συσκευή.", + waitingDevice: "Αναμονή για την άλλη συσκευή…", + addDevice: "Προσθήκη συσκευής", + deviceLinked: "Η συσκευή προστέθηκε.", + deviceLinkFailed: "Δεν ήταν δυνατή η προσθήκη αυτής της συσκευής.", + removeMemberConfirm: "Αφαίρεση του {{name}} από την ιδιωτική συνομιλία; Αυτή η συσκευή θα χάσει την πρόσβαση.", + removeMemberFailed: "Δεν ήταν δυνατή η αφαίρεση αυτού του μέλους.", + reply: "Απάντηση", }, communityUi: { link: "Κοινότητα", diff --git a/src/i18n/eu/batch1.ts b/src/i18n/eu/batch1.ts index 7d48f1c..d5a8688 100644 --- a/src/i18n/eu/batch1.ts +++ b/src/i18n/eu/batch1.ts @@ -975,6 +975,17 @@ const batch1 = { errorDetails: "Xehetasunak", unreachable: "Ezin iritsi", dismiss: "Itxi", + linkDevice: "Estekatu gailu hau", + linkDeviceTitle: "Estekatu beste gailu bat", + linkDeviceHint: "Eskaneatu kode hau jada gelan dagoen gailu batean.", + linkDeviceNote: "Kodeak gako publikoen paketea dauka. Zure gako pribatua gailu berrian geratzen da.", + waitingDevice: "Beste gailuaren zain…", + addDevice: "Gehitu gailu bat", + deviceLinked: "Gailua gehituta.", + deviceLinkFailed: "Ezin izan da gailu hori gehitu.", + removeMemberConfirm: "{{name}} txat pribatutik kendu? Gailu horrek sarbidea galduko du.", + removeMemberFailed: "Ezin izan da kide hori kendu.", + reply: "Erantzun", }, communityUi: { link: "Komunitatea", diff --git a/src/i18n/fr/drift.ts b/src/i18n/fr/drift.ts index 99dc842..df80ba4 100644 --- a/src/i18n/fr/drift.ts +++ b/src/i18n/fr/drift.ts @@ -355,6 +355,17 @@ const drift = { errorDetails: "Détails", unreachable: "Injoignable", dismiss: "Fermer", + linkDevice: "Lier cet appareil", + linkDeviceTitle: "Lier un autre appareil", + linkDeviceHint: "Scannez ce code sur un appareil déjà présent dans le salon.", + linkDeviceNote: "Le code contient un paquet de clés publiques. Votre clé privée reste sur le nouvel appareil.", + waitingDevice: "En attente de l’autre appareil…", + addDevice: "Ajouter un appareil", + deviceLinked: "Appareil ajouté.", + deviceLinkFailed: "Impossible d’ajouter cet appareil.", + removeMemberConfirm: "Retirer {{name}} du chat privé ? Cet appareil perdra l’accès.", + removeMemberFailed: "Impossible de retirer ce membre.", + reply: "Répondre", }, communityUi: { link: "Communauté", diff --git a/src/i18n/gl/batch1.ts b/src/i18n/gl/batch1.ts index 95e325a..0b757b7 100644 --- a/src/i18n/gl/batch1.ts +++ b/src/i18n/gl/batch1.ts @@ -975,6 +975,17 @@ const batch1 = { errorDetails: "Detalles", unreachable: "Inaccesible", dismiss: "Pechar", + linkDevice: "Vincular este dispositivo", + linkDeviceTitle: "Vincular outro dispositivo", + linkDeviceHint: "Escanea este código nun dispositivo que xa estea na sala.", + linkDeviceNote: "O código contén un paquete de chaves públicas. A túa chave privada queda no novo dispositivo.", + waitingDevice: "Agardando polo outro dispositivo…", + addDevice: "Engadir un dispositivo", + deviceLinked: "Dispositivo engadido.", + deviceLinkFailed: "Non se puido engadir ese dispositivo.", + removeMemberConfirm: "Quitar {{name}} do chat privado? Ese dispositivo perderá o acceso.", + removeMemberFailed: "Non se puido quitar ese membro.", + reply: "Responder", }, communityUi: { link: "Comunidade", diff --git a/src/i18n/hi/batch1.ts b/src/i18n/hi/batch1.ts index 95df178..10bcbd7 100644 --- a/src/i18n/hi/batch1.ts +++ b/src/i18n/hi/batch1.ts @@ -975,6 +975,17 @@ const batch1 = { errorDetails: "विवरण", unreachable: "पहुँच से बाहर", dismiss: "बंद करें", + linkDevice: "इस डिवाइस को लिंक करें", + linkDeviceTitle: "दूसरा डिवाइस लिंक करें", + linkDeviceHint: "यह कोड उस डिवाइस पर स्कैन करें जो पहले से रूम में है।", + linkDeviceNote: "कोड में सार्वजनिक कुंजी पैकेज है। आपकी निजी कुंजी नए डिवाइस पर ही रहती है।", + waitingDevice: "दूसरे डिवाइस की प्रतीक्षा हो रही है…", + addDevice: "डिवाइस जोड़ें", + deviceLinked: "डिवाइस जोड़ दिया गया।", + deviceLinkFailed: "वह डिवाइस जोड़ा नहीं जा सका।", + removeMemberConfirm: "{{name}} को निजी चैट से हटाएँ? वह डिवाइस एक्सेस खो देगा।", + removeMemberFailed: "वह सदस्य हटाया नहीं जा सका।", + reply: "जवाब दें", }, communityUi: { "link": "कम्युनिटी", diff --git a/src/i18n/id/drift.ts b/src/i18n/id/drift.ts index a323d9a..da45102 100644 --- a/src/i18n/id/drift.ts +++ b/src/i18n/id/drift.ts @@ -349,6 +349,17 @@ const driftStrings = { errorDetails: "Detail", unreachable: "Tidak terjangkau", dismiss: "Tutup", + linkDevice: "Tautkan perangkat ini", + linkDeviceTitle: "Tautkan perangkat lain", + linkDeviceHint: "Pindai kode ini di perangkat yang sudah ada di ruangan.", + linkDeviceNote: "Kode ini berisi paket kunci publik. Kunci pribadi kamu tetap di perangkat baru.", + waitingDevice: "Menunggu perangkat lain…", + addDevice: "Tambah perangkat", + deviceLinked: "Perangkat ditambahkan.", + deviceLinkFailed: "Tidak dapat menambahkan perangkat itu.", + removeMemberConfirm: "Hapus {{name}} dari chat privat? Perangkat itu akan kehilangan akses.", + removeMemberFailed: "Tidak dapat menghapus anggota itu.", + reply: "Balas", }, communityUi: { link: "Komunitas", diff --git a/src/i18n/it/drift.ts b/src/i18n/it/drift.ts index 3174d23..401cb01 100644 --- a/src/i18n/it/drift.ts +++ b/src/i18n/it/drift.ts @@ -355,6 +355,17 @@ const drift = { errorDetails: "Dettagli", unreachable: "Non raggiungibile", dismiss: "Chiudi", + linkDevice: "Collega questo dispositivo", + linkDeviceTitle: "Collega un altro dispositivo", + linkDeviceHint: "Scansiona questo codice su un dispositivo già presente nella stanza.", + linkDeviceNote: "Il codice contiene un pacchetto di chiavi pubbliche. La tua chiave privata resta sul nuovo dispositivo.", + waitingDevice: "In attesa dell’altro dispositivo…", + addDevice: "Aggiungi un dispositivo", + deviceLinked: "Dispositivo aggiunto.", + deviceLinkFailed: "Impossibile aggiungere quel dispositivo.", + removeMemberConfirm: "Rimuovere {{name}} dalla chat privata? Quel dispositivo perderà l’accesso.", + removeMemberFailed: "Impossibile rimuovere quel membro.", + reply: "Rispondi", }, communityUi: { link: "Community", diff --git a/src/i18n/ja/drift.ts b/src/i18n/ja/drift.ts index 85b19e2..0d4e514 100644 --- a/src/i18n/ja/drift.ts +++ b/src/i18n/ja/drift.ts @@ -365,6 +365,17 @@ const drift: Dict = { errorDetails: "詳細", unreachable: "接続不可", dismiss: "閉じる", + linkDevice: "このデバイスをリンク", + linkDeviceTitle: "別のデバイスをリンク", + linkDeviceHint: "すでにルームに参加しているデバイスでこのコードをスキャンしてください。", + linkDeviceNote: "コードには公開鍵パッケージが含まれます。秘密鍵は新しいデバイスに残ります。", + waitingDevice: "もう一方のデバイスを待っています…", + addDevice: "デバイスを追加", + deviceLinked: "デバイスを追加しました。", + deviceLinkFailed: "そのデバイスを追加できませんでした。", + removeMemberConfirm: "{{name}} をプライベートチャットから削除しますか?そのデバイスはアクセスできなくなります。", + removeMemberFailed: "そのメンバーを削除できませんでした。", + reply: "返信", }, communityUi: { link: "コミュニティ", diff --git a/src/i18n/ko/drift.ts b/src/i18n/ko/drift.ts index ec9732b..7ed1032 100644 --- a/src/i18n/ko/drift.ts +++ b/src/i18n/ko/drift.ts @@ -365,6 +365,17 @@ const drift: Dict = { errorDetails: "세부정보", unreachable: "연결 불가", dismiss: "닫기", + linkDevice: "이 기기 연결", + linkDeviceTitle: "다른 기기 연결", + linkDeviceHint: "이미 방에 있는 기기에서 이 코드를 스캔하세요.", + linkDeviceNote: "코드에는 공개 키 패키지가 포함되어 있습니다. 비공개 키는 새 기기에 남습니다.", + waitingDevice: "다른 기기를 기다리는 중…", + addDevice: "기기 추가", + deviceLinked: "기기가 추가되었습니다.", + deviceLinkFailed: "해당 기기를 추가하지 못했습니다.", + removeMemberConfirm: "비공개 채팅에서 {{name}}을(를) 제거할까요? 해당 기기는 더 이상 접근할 수 없습니다.", + removeMemberFailed: "해당 멤버를 제거하지 못했습니다.", + reply: "답장", }, communityUi: { link: "커뮤니티", diff --git a/src/i18n/legacy/cordnUi.ts b/src/i18n/legacy/cordnUi.ts index d64526c..a48f1a5 100644 --- a/src/i18n/legacy/cordnUi.ts +++ b/src/i18n/legacy/cordnUi.ts @@ -79,6 +79,19 @@ const cordnUi = { errorDetails: 'Details', unreachable: 'Unreachable', dismiss: 'Dismiss', + linkDevice: 'Link this device', + linkDeviceTitle: 'Link another device', + linkDeviceHint: 'Scan this code on a device that is already in the room.', + linkDeviceNote: + 'The code contains a public key package. Your private key stays on the new device.', + waitingDevice: 'Waiting for the other device…', + addDevice: 'Add a device', + deviceLinked: 'Device added.', + deviceLinkFailed: 'Couldn’t add that device.', + removeMemberConfirm: + 'Remove {{name}} from the private chat? That device will lose access.', + removeMemberFailed: 'Couldn’t remove that member.', + reply: 'Reply', }, es: { title: 'Chat privado', @@ -155,6 +168,19 @@ const cordnUi = { errorDetails: 'Detalles', unreachable: 'Sin conexión', dismiss: 'Descartar', + linkDevice: 'Vincular este dispositivo', + linkDeviceTitle: 'Vincular otro dispositivo', + linkDeviceHint: 'Escanea este código en un dispositivo que ya esté en la sala.', + linkDeviceNote: + 'El código contiene un paquete de claves público. Tu clave privada se queda en el dispositivo nuevo.', + waitingDevice: 'Esperando al otro dispositivo…', + addDevice: 'Añadir un dispositivo', + deviceLinked: 'Dispositivo añadido.', + deviceLinkFailed: 'No se pudo añadir ese dispositivo.', + removeMemberConfirm: + '¿Quitar a {{name}} del chat privado? Ese dispositivo perderá el acceso.', + removeMemberFailed: 'No se pudo quitar a ese miembro.', + reply: 'Responder', }, }; diff --git a/src/i18n/nl/drift.ts b/src/i18n/nl/drift.ts index d4ac471..11e74ad 100644 --- a/src/i18n/nl/drift.ts +++ b/src/i18n/nl/drift.ts @@ -353,6 +353,17 @@ const drift = { errorDetails: "Details", unreachable: "Onbereikbaar", dismiss: "Sluiten", + linkDevice: "Dit apparaat koppelen", + linkDeviceTitle: "Een ander apparaat koppelen", + linkDeviceHint: "Scan deze code op een apparaat dat al in de ruimte is.", + linkDeviceNote: "De code bevat een openbaar sleutelpakket. Je privésleutel blijft op het nieuwe apparaat.", + waitingDevice: "Wachten op het andere apparaat…", + addDevice: "Apparaat toevoegen", + deviceLinked: "Apparaat toegevoegd.", + deviceLinkFailed: "Kon dat apparaat niet toevoegen.", + removeMemberConfirm: "{{name}} uit de privéchat verwijderen? Dat apparaat verliest de toegang.", + removeMemberFailed: "Kon dat lid niet verwijderen.", + reply: "Antwoorden", }, communityUi: { "link": "Community", diff --git a/src/i18n/pl/drift.ts b/src/i18n/pl/drift.ts index 010c2ac..08f5b90 100644 --- a/src/i18n/pl/drift.ts +++ b/src/i18n/pl/drift.ts @@ -353,6 +353,17 @@ const drift = { errorDetails: "Szczegóły", unreachable: "Nieosiągalne", dismiss: "Zamknij", + linkDevice: "Połącz to urządzenie", + linkDeviceTitle: "Połącz inne urządzenie", + linkDeviceHint: "Zeskanuj ten kod na urządzeniu, które jest już w pokoju.", + linkDeviceNote: "Kod zawiera pakiet kluczy publicznych. Twój klucz prywatny pozostaje na nowym urządzeniu.", + waitingDevice: "Oczekiwanie na drugie urządzenie…", + addDevice: "Dodaj urządzenie", + deviceLinked: "Dodano urządzenie.", + deviceLinkFailed: "Nie udało się dodać tego urządzenia.", + removeMemberConfirm: "Usunąć {{name}} z prywatnego czatu? To urządzenie utraci dostęp.", + removeMemberFailed: "Nie udało się usunąć tego członka.", + reply: "Odpowiedz", }, communityUi: { "link": "Społeczność", diff --git a/src/i18n/pt/drift.ts b/src/i18n/pt/drift.ts index 5b0410d..0dc7570 100644 --- a/src/i18n/pt/drift.ts +++ b/src/i18n/pt/drift.ts @@ -355,6 +355,17 @@ const drift = { errorDetails: "Detalhes", unreachable: "Inacessível", dismiss: "Fechar", + linkDevice: "Vincular este dispositivo", + linkDeviceTitle: "Vincular outro dispositivo", + linkDeviceHint: "Escaneie este código em um dispositivo que já esteja na sala.", + linkDeviceNote: "O código contém um pacote de chaves públicas. Sua chave privada permanece no novo dispositivo.", + waitingDevice: "Aguardando o outro dispositivo…", + addDevice: "Adicionar um dispositivo", + deviceLinked: "Dispositivo adicionado.", + deviceLinkFailed: "Não foi possível adicionar esse dispositivo.", + removeMemberConfirm: "Remover {{name}} do chat privado? Esse dispositivo perderá o acesso.", + removeMemberFailed: "Não foi possível remover esse membro.", + reply: "Responder", }, communityUi: { link: "Comunidade", diff --git a/src/i18n/ro/drift.ts b/src/i18n/ro/drift.ts index 49b9da9..be75b3b 100644 --- a/src/i18n/ro/drift.ts +++ b/src/i18n/ro/drift.ts @@ -353,6 +353,17 @@ const drift = { errorDetails: "Detalii", unreachable: "Inaccesibil", dismiss: "Închide", + linkDevice: "Conectează acest dispozitiv", + linkDeviceTitle: "Conectează alt dispozitiv", + linkDeviceHint: "Scanează acest cod pe un dispozitiv care este deja în cameră.", + linkDeviceNote: "Codul conține un pachet de chei publice. Cheia ta privată rămâne pe dispozitivul nou.", + waitingDevice: "Se așteaptă celălalt dispozitiv…", + addDevice: "Adaugă un dispozitiv", + deviceLinked: "Dispozitiv adăugat.", + deviceLinkFailed: "Nu s-a putut adăuga acel dispozitiv.", + removeMemberConfirm: "Elimini {{name}} din chatul privat? Dispozitivul respectiv va pierde accesul.", + removeMemberFailed: "Nu s-a putut elimina acel membru.", + reply: "Răspunde", }, communityUi: { "link": "Comunitate", diff --git a/src/i18n/ru/drift.ts b/src/i18n/ru/drift.ts index 228f929..e2018b2 100644 --- a/src/i18n/ru/drift.ts +++ b/src/i18n/ru/drift.ts @@ -357,6 +357,17 @@ const drift = { errorDetails: "Подробности", unreachable: "Недоступно", dismiss: "Закрыть", + linkDevice: "Привязать это устройство", + linkDeviceTitle: "Привязать другое устройство", + linkDeviceHint: "Отсканируйте этот код на устройстве, которое уже в комнате.", + linkDeviceNote: "Код содержит пакет открытых ключей. Ваш закрытый ключ останется на новом устройстве.", + waitingDevice: "Ожидание другого устройства…", + addDevice: "Добавить устройство", + deviceLinked: "Устройство добавлено.", + deviceLinkFailed: "Не удалось добавить это устройство.", + removeMemberConfirm: "Удалить {{name}} из приватного чата? Это устройство потеряет доступ.", + removeMemberFailed: "Не удалось удалить этого участника.", + reply: "Ответить", }, communityUi: { link: "Сообщество", diff --git a/src/i18n/sv/drift.ts b/src/i18n/sv/drift.ts index c0e04db..b5fa0fe 100644 --- a/src/i18n/sv/drift.ts +++ b/src/i18n/sv/drift.ts @@ -357,6 +357,17 @@ const drift = { errorDetails: "Detaljer", unreachable: "Kan inte nås", dismiss: "Stäng", + linkDevice: "Länka den här enheten", + linkDeviceTitle: "Länka en annan enhet", + linkDeviceHint: "Skanna den här koden på en enhet som redan är i rummet.", + linkDeviceNote: "Koden innehåller ett paket med publika nycklar. Din privata nyckel stannar på den nya enheten.", + waitingDevice: "Väntar på den andra enheten…", + addDevice: "Lägg till en enhet", + deviceLinked: "Enhet tillagd.", + deviceLinkFailed: "Det gick inte att lägga till den enheten.", + removeMemberConfirm: "Ta bort {{name}} från den privata chatten? Enheten förlorar åtkomsten.", + removeMemberFailed: "Det gick inte att ta bort den medlemmen.", + reply: "Svara", }, communityUi: { link: "Community", diff --git a/src/i18n/th/drift.ts b/src/i18n/th/drift.ts index f81fb0b..2499423 100644 --- a/src/i18n/th/drift.ts +++ b/src/i18n/th/drift.ts @@ -349,6 +349,17 @@ const driftStrings = { errorDetails: "รายละเอียด", unreachable: "เชื่อมต่อไม่ได้", dismiss: "ปิด", + linkDevice: "เชื่อมโยงอุปกรณ์นี้", + linkDeviceTitle: "เชื่อมโยงอุปกรณ์อื่น", + linkDeviceHint: "สแกนรหัสนี้บนอุปกรณ์ที่อยู่ในห้องแล้ว", + linkDeviceNote: "รหัสนี้มีแพ็กเกจคีย์สาธารณะ คีย์ส่วนตัวของคุณจะอยู่บนอุปกรณ์ใหม่เท่านั้น", + waitingDevice: "กำลังรออุปกรณ์อื่น…", + addDevice: "เพิ่มอุปกรณ์", + deviceLinked: "เพิ่มอุปกรณ์แล้ว", + deviceLinkFailed: "ไม่สามารถเพิ่มอุปกรณ์นั้นได้", + removeMemberConfirm: "ต้องการนำ {{name}} ออกจากแชทส่วนตัวหรือไม่ อุปกรณ์นั้นจะสูญเสียการเข้าถึง", + removeMemberFailed: "ไม่สามารถนำสมาชิกนั้นออกได้", + reply: "ตอบกลับ", }, communityUi: { link: "ชุมชน", diff --git a/src/i18n/tr/drift.ts b/src/i18n/tr/drift.ts index 70bf13e..1d9c6db 100644 --- a/src/i18n/tr/drift.ts +++ b/src/i18n/tr/drift.ts @@ -349,6 +349,17 @@ const driftStrings = { errorDetails: "Ayrıntılar", unreachable: "Ulaşılamıyor", dismiss: "Kapat", + linkDevice: "Bu cihazı bağla", + linkDeviceTitle: "Başka bir cihaz bağla", + linkDeviceHint: "Bu kodu odada zaten bulunan bir cihazda tara.", + linkDeviceNote: "Kod bir açık anahtar paketi içerir. Özel anahtarın yeni cihazda kalır.", + waitingDevice: "Diğer cihaz bekleniyor…", + addDevice: "Cihaz ekle", + deviceLinked: "Cihaz eklendi.", + deviceLinkFailed: "O cihaz eklenemedi.", + removeMemberConfirm: "{{name}} özel sohbetten çıkarılsın mı? O cihaz erişimini kaybedecek.", + removeMemberFailed: "O üye çıkarılamadı.", + reply: "Yanıtla", }, communityUi: { link: "Topluluk", diff --git a/src/i18n/uk/drift.ts b/src/i18n/uk/drift.ts index 2102d9b..5617b52 100644 --- a/src/i18n/uk/drift.ts +++ b/src/i18n/uk/drift.ts @@ -357,6 +357,17 @@ const drift = { errorDetails: "Подробиці", unreachable: "Недоступно", dismiss: "Закрити", + linkDevice: "Прив’язати цей пристрій", + linkDeviceTitle: "Прив’язати інший пристрій", + linkDeviceHint: "Відскануйте цей код на пристрої, який уже є в кімнаті.", + linkDeviceNote: "Код містить пакет відкритих ключів. Ваш закритий ключ залишиться на новому пристрої.", + waitingDevice: "Очікування іншого пристрою…", + addDevice: "Додати пристрій", + deviceLinked: "Пристрій додано.", + deviceLinkFailed: "Не вдалося додати цей пристрій.", + removeMemberConfirm: "Видалити {{name}} з приватного чату? Цей пристрій втратить доступ.", + removeMemberFailed: "Не вдалося видалити цього учасника.", + reply: "Відповісти", }, communityUi: { link: "Спільнота", diff --git a/src/i18n/vi/drift.ts b/src/i18n/vi/drift.ts index b0d7fa6..829befd 100644 --- a/src/i18n/vi/drift.ts +++ b/src/i18n/vi/drift.ts @@ -349,6 +349,17 @@ const driftStrings = { errorDetails: "Chi tiết", unreachable: "Không thể kết nối", dismiss: "Đóng", + linkDevice: "Liên kết thiết bị này", + linkDeviceTitle: "Liên kết thiết bị khác", + linkDeviceHint: "Quét mã này trên thiết bị đã có trong phòng.", + linkDeviceNote: "Mã chứa gói khóa công khai. Khóa riêng tư của bạn vẫn ở trên thiết bị mới.", + waitingDevice: "Đang chờ thiết bị kia…", + addDevice: "Thêm thiết bị", + deviceLinked: "Đã thêm thiết bị.", + deviceLinkFailed: "Không thể thêm thiết bị đó.", + removeMemberConfirm: "Xóa {{name}} khỏi chat riêng tư? Thiết bị đó sẽ mất quyền truy cập.", + removeMemberFailed: "Không thể xóa thành viên đó.", + reply: "Trả lời", }, communityUi: { link: "Cộng đồng", diff --git a/src/i18n/zh-CN/drift.ts b/src/i18n/zh-CN/drift.ts index 54d3382..310faf6 100644 --- a/src/i18n/zh-CN/drift.ts +++ b/src/i18n/zh-CN/drift.ts @@ -365,6 +365,17 @@ const drift: Dict = { errorDetails: "详情", unreachable: "无法连接", dismiss: "关闭", + linkDevice: "关联此设备", + linkDeviceTitle: "关联另一台设备", + linkDeviceHint: "请在已在房间中的设备上扫描此代码。", + linkDeviceNote: "此代码包含公钥包。你的私钥会留在新设备上。", + waitingDevice: "正在等待另一台设备…", + addDevice: "添加设备", + deviceLinked: "设备已添加。", + deviceLinkFailed: "无法添加该设备。", + removeMemberConfirm: "要将 {{name}} 移出私密聊天吗?该设备将失去访问权限。", + removeMemberFailed: "无法移除该成员。", + reply: "回复", }, communityUi: { link: "社区", diff --git a/src/i18n/zh-TW/drift.ts b/src/i18n/zh-TW/drift.ts index 905f3c0..6255f5d 100644 --- a/src/i18n/zh-TW/drift.ts +++ b/src/i18n/zh-TW/drift.ts @@ -365,6 +365,17 @@ const drift: Dict = { errorDetails: "詳細資料", unreachable: "無法連線", dismiss: "關閉", + linkDevice: "連結此裝置", + linkDeviceTitle: "連結另一台裝置", + linkDeviceHint: "請在已在房間中的裝置上掃描此代碼。", + linkDeviceNote: "此代碼包含公鑰包。你的私鑰會留在新裝置上。", + waitingDevice: "正在等待另一台裝置…", + addDevice: "新增裝置", + deviceLinked: "已新增裝置。", + deviceLinkFailed: "無法新增該裝置。", + removeMemberConfirm: "要將 {{name}} 移出私密聊天嗎?該裝置將失去存取權限。", + removeMemberFailed: "無法移除該成員。", + reply: "回覆", }, communityUi: { link: "社群", diff --git a/src/lib/cordn/deviceLink.test.ts b/src/lib/cordn/deviceLink.test.ts new file mode 100644 index 0000000..2a138b6 --- /dev/null +++ b/src/lib/cordn/deviceLink.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from 'vitest'; +import { decodeDeviceLink, encodeDeviceLink } from './deviceLink'; + +const link = { + gid: 'skatespots:community', + kpRef: 'kp_ref_123', + pubkey: 'a'.repeat(64), + coordinatorPubkey: 'b'.repeat(64), + relays: ['wss://relay.skate-spots.org'], +}; + +describe('deviceLink', () => { + it('round-trips a valid link', () => { + expect(decodeDeviceLink(encodeDeviceLink(link))).toEqual(link); + }); + + it('rejects malformed or unsafe links', () => { + expect(decodeDeviceLink('')).toBeNull(); + expect(decodeDeviceLink('https://example.com')).toBeNull(); + expect(decodeDeviceLink('cordn-device:not-base64')).toBeNull(); + const bad = encodeDeviceLink({ ...link, pubkey: 'javascript:' }); + expect(decodeDeviceLink(bad)).toBeNull(); + const badRelay = encodeDeviceLink({ ...link, relays: ['javascript:alert(1)'] }); + expect(decodeDeviceLink(badRelay)).toBeNull(); + }); +}); diff --git a/src/lib/cordn/deviceLink.ts b/src/lib/cordn/deviceLink.ts new file mode 100644 index 0000000..14f49a3 --- /dev/null +++ b/src/lib/cordn/deviceLink.ts @@ -0,0 +1,81 @@ +/** + * Two-device handoff payload. + * + * The NEW device publishes a key package to the coordinator and shows this + * compact code as a QR. An EXISTING member scans it, consumes the key package + * by ref, and the coordinator stores the Welcome for the new device. The + * private key never leaves the new device, and the code is short enough for a + * normal QR. + */ +export interface CordnDeviceLink { + gid: string; + kpRef: string; + pubkey: string; + coordinatorPubkey: string; + relays: string[]; +} + +const PREFIX = 'cordn-device:'; +const HEX64 = /^[0-9a-f]{64}$/; + +function b64urlEncode(value: string): string { + return btoa(value).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); +} + +function b64urlDecode(value: string): string { + const padded = value.replace(/-/g, '+').replace(/_/g, '/'); + const pad = padded.length % 4 === 0 ? '' : '='.repeat(4 - (padded.length % 4)); + return atob(padded + pad); +} + +export function encodeDeviceLink(link: CordnDeviceLink): string { + const json = JSON.stringify({ + g: link.gid, + k: link.kpRef, + p: link.pubkey, + c: link.coordinatorPubkey, + r: link.relays, + }); + return `${PREFIX}${b64urlEncode(json)}`; +} + +export function decodeDeviceLink(raw: string | undefined | null): CordnDeviceLink | null { + if (!raw || !raw.startsWith(PREFIX)) return null; + try { + const parsed = JSON.parse(b64urlDecode(raw.slice(PREFIX.length))) as { + g?: unknown; + k?: unknown; + p?: unknown; + c?: unknown; + r?: unknown; + }; + if ( + typeof parsed.g !== 'string' || + !parsed.g || + typeof parsed.k !== 'string' || + !parsed.k || + typeof parsed.p !== 'string' || + !HEX64.test(parsed.p) || + typeof parsed.c !== 'string' || + !HEX64.test(parsed.c) || + !Array.isArray(parsed.r) || + parsed.r.length === 0 + ) { + return null; + } + const relays = parsed.r.filter( + (relay): relay is string => + typeof relay === 'string' && /^wss?:\/\/[^\s]+$/i.test(relay), + ); + if (relays.length === 0) return null; + return { + gid: parsed.g, + kpRef: parsed.k, + pubkey: parsed.p, + coordinatorPubkey: parsed.c, + relays, + }; + } catch { + return null; + } +} diff --git a/src/lib/cordn/native/mls.ts b/src/lib/cordn/native/mls.ts index d218d82..1a389fa 100644 --- a/src/lib/cordn/native/mls.ts +++ b/src/lib/cordn/native/mls.ts @@ -258,6 +258,29 @@ export async function addMemberToGroup(params: { }; } +/** Remove a member leaf from the group and produce the commit to broadcast. */ +export async function removeMemberFromGroup(params: { + state: ClientState; + leafIndex: number; +}): Promise<{ newState: ClientState; commitMessageBase64: string }> { + const cipherSuite = await getCipherSuite(); + const result = await createCommit({ + context: { cipherSuite, authService: unsafeTestingAuthenticationService }, + state: params.state, + ratchetTreeExtension: true, + extraProposals: [ + { + proposalType: defaultProposalTypes.remove, + remove: { removed: params.leafIndex }, + }, + ], + }); + return { + newState: result.newState, + commitMessageBase64: encodeMlsMessage(result.commit), + }; +} + export async function joinFromWelcome(params: { welcome: Welcome; keyPackage: KeyPackage; diff --git a/src/lib/cordn/native/session.ts b/src/lib/cordn/native/session.ts index 82d76c4..aa4d2a8 100644 --- a/src/lib/cordn/native/session.ts +++ b/src/lib/cordn/native/session.ts @@ -19,6 +19,7 @@ import { joinFromWelcome, createGroupApplicationMessage, processGroupMessage, + removeMemberFromGroup, sealForCoordinator, openFromCoordinator, groupIdOf, @@ -397,6 +398,22 @@ export class CordnSession { return this.addMember(gid, memberKeyPackage); } + /** Remove a member leaf and post the MLS commit. */ + async removeMember(gid: string, leafIndex: number): Promise { + const group = this.requireGroup(gid); + const { newState, commitMessageBase64 } = await removeMemberFromGroup({ + state: group.state, + leafIndex, + }); + const sealed = await sealForCoordinator({ state: group.state, messageBase64: commitMessageBase64 }); + const posted = await this.getClient().postGroupMessage(group.gid, sealed); + group.posted.add(sealed); + group.state = newState; + group.cursor = Math.max(group.cursor, posted.cursor); + await this.persist(); + return this.describeGroup(group); + } + /** Join a group from a Welcome produced by {@link addMember}. */ async joinGroup(params: { welcomeBase64: string;