From e81dd377d84c68bf6bf278c1c7a583677864a706 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Thu, 19 Mar 2026 21:53:30 +0000 Subject: [PATCH] feat(vpn transport): add QUIC transport support with auto fallback to WebSocket --- changelog.md | 9 + package.json | 15 +- pnpm-lock.yaml | 2806 +++++++++++------------------- readme.md | 155 +- rust/Cargo.lock | 556 +++++- rust/Cargo.toml | 8 + rust/src/client.rs | 154 +- rust/src/lib.rs | 2 + rust/src/quic_transport.rs | 546 ++++++ rust/src/ratelimit.rs | 4 +- rust/src/server.rs | 208 ++- rust/src/transport_trait.rs | 116 ++ test/test.quic.node.ts | 242 +++ ts/00_commitinfo_data.ts | 2 +- ts/smartvpn.classes.vpnconfig.ts | 7 +- ts/smartvpn.interfaces.ts | 10 + 16 files changed, 2952 insertions(+), 1888 deletions(-) create mode 100644 rust/src/quic_transport.rs create mode 100644 rust/src/transport_trait.rs create mode 100644 test/test.quic.node.ts diff --git a/changelog.md b/changelog.md index fdf0d73..4d2c328 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ # Changelog +## 2026-03-19 - 1.4.0 - feat(vpn transport) +add QUIC transport support with auto fallback to WebSocket + +- introduces a transport abstraction in the Rust daemon so client and server can operate over WebSocket or QUIC +- adds dual-mode server configuration with websocket, quic, and both transport modes plus QUIC idle timeout and listen address options +- adds client transport selection with auto mode that attempts QUIC first and falls back to WebSocket +- adds QUIC certificate hash pinning support and required Rust dependencies for QUIC and TLS +- updates TypeScript interfaces, config validation, tests, and documentation to cover the new transport modes + ## 2026-03-17 - 1.3.0 - feat(tests,client) add flow control and load test coverage and honor configured keepalive intervals diff --git a/package.json b/package.json index 1c4bf3e..8006ab3 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "typings": "dist_ts/index.d.ts", "scripts": { "build": "(tsbuild tsfolders --allowimplicitany) && (tsrust)", + "test:before": "(tsrust)", "test": "tstest test/ --verbose", "buildDocs": "tsdoc" }, @@ -28,15 +29,15 @@ ], "license": "MIT", "dependencies": { - "@push.rocks/smartrust": "^1.3.0", - "@push.rocks/smartpath": "^5.0.18" + "@push.rocks/smartpath": "^6.0.0", + "@push.rocks/smartrust": "^1.3.2" }, "devDependencies": { - "@git.zone/tsbuild": "^2.2.12", - "@git.zone/tsrun": "^1.3.3", - "@git.zone/tstest": "^1.0.96", - "@git.zone/tsrust": "^1.0.29", - "@types/node": "^22.0.0" + "@git.zone/tsbuild": "^4.3.0", + "@git.zone/tsrun": "^2.0.1", + "@git.zone/tsrust": "^1.3.0", + "@git.zone/tstest": "^3.5.0", + "@types/node": "^25.5.0" }, "files": [ "ts/**/*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7de23c8..7771523 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,27 +9,27 @@ importers: .: dependencies: '@push.rocks/smartpath': - specifier: ^5.0.18 - version: 5.1.0 + specifier: ^6.0.0 + version: 6.0.0 '@push.rocks/smartrust': - specifier: ^1.3.0 - version: 1.3.1 + specifier: ^1.3.2 + version: 1.3.2 devDependencies: '@git.zone/tsbuild': - specifier: ^2.2.12 - version: 2.7.3 + specifier: ^4.3.0 + version: 4.3.0 '@git.zone/tsrun': - specifier: ^1.3.3 - version: 1.6.2 + specifier: ^2.0.1 + version: 2.0.1 '@git.zone/tsrust': - specifier: ^1.0.29 + specifier: ^1.3.0 version: 1.3.0 '@git.zone/tstest': - specifier: ^1.0.96 - version: 1.11.5(socks@2.8.7)(typescript@5.9.3) + specifier: ^3.5.0 + version: 3.5.0(socks@2.8.7)(typescript@5.9.3) '@types/node': - specifier: ^22.0.0 - version: 22.19.12 + specifier: ^25.5.0 + version: 25.5.0 packages: @@ -42,17 +42,6 @@ packages: '@api.global/typedrequest@3.2.6': resolution: {integrity: sha512-CnvbjYjnGGw3rwL+7bTHSgRHEpDujzhs3cv7l1xgCXMPQe3DcPg74+9ep1Y5cu21T/w0pxNnDCJpbb0SHqHzAw==} - '@api.global/typedserver@3.0.80': - resolution: {integrity: sha512-dcp0oXsjBL+XdFg1wUUP08uJQid5bQ0Yv3V3Y3lnI2QCbat0FU+Tsb0TZRnZ4+P150Vj/ITBqJUgDzFsF34grA==} - - '@api.global/typedsocket@3.1.1': - resolution: {integrity: sha512-Wkz3NlhmfdZMKqXXI2c2dMtGGmSmhdOegZiziL+9b2mqPYdc7Gd8AZRdEOKvbSoIvc9G22/5BEadIWHrfq66TA==} - peerDependencies: - '@push.rocks/smartserve': '>=1.0.0' - peerDependenciesMeta: - '@push.rocks/smartserve': - optional: true - '@aws-crypto/crc32@5.2.0': resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} engines: {node: '>=16.0.0'} @@ -227,19 +216,12 @@ packages: '@borewit/text-codec@0.2.1': resolution: {integrity: sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==} - '@cloudflare/workers-types@4.20260305.0': - resolution: {integrity: sha512-sCgPFnQ03SVpC2OVW8wysONLZW/A8hlp9Mq2ckG/h1oId4kr9NawA6vUiOmOjCWRn2hIohejBYVQ+Vu20rCdKA==} - - '@colors/colors@1.6.0': - resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} - engines: {node: '>=0.1.90'} + '@cfworker/json-schema@4.1.1': + resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==} '@configvault.io/interfaces@1.0.17': resolution: {integrity: sha512-bEcCUR2VBDJsTin8HQh8Uw/mlYl2v8A3jMIaQ+MTB9Hrqd6CZL2dL7iJdWyFl/3EIX+LDxWFR+Oq7liIq7w+1Q==} - '@dabh/diagnostics@2.0.8': - resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==} - '@design.estate/dees-comms@1.0.30': resolution: {integrity: sha512-KchMlklJfKAjQiJiR0xmofXtQ27VgZtBIxcMwPE9d+h3jJRv+lPZxzBQVOM0eyM0uS44S5vJMZ11IeV4uDXSHg==} @@ -414,33 +396,182 @@ packages: cpu: [x64] os: [win32] - '@git.zone/tsbuild@2.7.3': - resolution: {integrity: sha512-GMM6VU6TcVvYINfV6b1ZVGZXYhdtriYyAHifvrn8IdRar6thIN3ig3N2J/S1kmX2KLrBbx0JyF3tNChHdNR+wA==} + '@git.zone/tsbuild@4.3.0': + resolution: {integrity: sha512-lb6eMQ8RQPaJqAB4kC++GIElOiTAH1pClmoND/q7XHuiMZxv6cXz2/U/sZt339mon2c40dXRG2tkLF2jRsP0pQ==} hasBin: true - '@git.zone/tsbundle@2.9.0': - resolution: {integrity: sha512-itXX/oiJjrRHUlIGTHUEqSwPuGwsG4Cq8kh7aqFOm8mYzJwtXYE1gBqLJTWZma6gI5n+xAk5qTxTyfikuPgWQA==} + '@git.zone/tsbundle@2.9.1': + resolution: {integrity: sha512-JW1xjSv7UjAm2lwAQPxhCWs14wqs+UIq5FqIGUPuI6rrDBWIMT2d0gpP6iP6TqXqgm6XpBlfU4rHcHheUXzXbQ==} hasBin: true - '@git.zone/tspublish@1.11.0': - resolution: {integrity: sha512-dkgaDBTzZJ53lAV72r7OW/W7l/KqpkncFuPojr11JO35OKAbjjDhZbAwPv4oGX9NplyXrhC5VJRPNX/orqNTHA==} + '@git.zone/tspublish@1.11.2': + resolution: {integrity: sha512-BcGap1OzXDgXpfQXMh9W17r/CkWNhPsJ3WzjG2wrGE+ePUJCJAm9w6+J8G5WdZZcZKPqTB07cp707LbSiksc5A==} hasBin: true - '@git.zone/tsrun@1.6.2': - resolution: {integrity: sha512-SOHbQqBg3/769/jPQcdpPCmugdEtIJINiG0O6aWx+su91GvGhheha5dAhccsCutJYErr+aJcBqBYuUYfhOfkFQ==} + '@git.zone/tsrun@2.0.1': + resolution: {integrity: sha512-NEcnsjvlC1o3Z6SS3VhKCf6Ev+Sh4EAinmggslrIR/ppMrvjDbXNFXoyr3PB+GLeSAR0JRZ1fGvVYjpEzjBdIg==} hasBin: true '@git.zone/tsrust@1.3.0': resolution: {integrity: sha512-dvmTAiM04Pkd7J1Gail3fu7aasmILQhC5vKL71/g6HYhpvl16/c+Dj3We5G4HsFr0jvAr+Xu570ZGEuZrtRcCg==} hasBin: true - '@git.zone/tstest@1.11.5': - resolution: {integrity: sha512-7YHFNGMjUd3WOFXi0DlUieQcdxzwYqxL7n2XDE7SOUd8XpMxVsGsY2SuwBKXlbT10By/H3thQTsy+Hjy9ahGWA==} + '@git.zone/tstest@3.5.0': + resolution: {integrity: sha512-ugIJzdVkbgqSSw08SZajE7TB01GIYjEAmIy67O5skhvOyszGifwzJdR+8dS1VbQGlUUWQZMGQ2IowllHbAZYJQ==} hasBin: true - '@happy-dom/global-registrator@15.11.7': - resolution: {integrity: sha512-mfOoUlIw8VBiJYPrl5RZfMzkXC/z7gbSpi2ecycrj/gRWLq2CMV+Q+0G+JPjeOmuNFgg0skEIzkVFzVYFP6URw==} - engines: {node: '>=18.0.0'} + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] '@inquirer/checkbox@3.0.1': resolution: {integrity: sha512-0hm2nrToWUdD6/UHnel/UKGdk1//ke5zGUpHIvk5ZWmaKezlGxZkOJXNSWsdxO/rEqTkbB3lNC2J6nBElV2aAQ==} @@ -502,9 +633,117 @@ packages: resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==} engines: {node: '>=18'} - '@koa/router@9.4.0': - resolution: {integrity: sha512-dOOXgzqaDoHu5qqMEPLKEgLz5CeIA7q8+1W62mCvFVCOqeC71UoTGJ4u1xUSOpIl2J1x2pqrNULkFteUeZW3/A==} - engines: {node: '>= 8.0.0'} + '@jimp/core@1.6.0': + resolution: {integrity: sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w==} + engines: {node: '>=18'} + + '@jimp/diff@1.6.0': + resolution: {integrity: sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw==} + engines: {node: '>=18'} + + '@jimp/file-ops@1.6.0': + resolution: {integrity: sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ==} + engines: {node: '>=18'} + + '@jimp/js-bmp@1.6.0': + resolution: {integrity: sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw==} + engines: {node: '>=18'} + + '@jimp/js-gif@1.6.0': + resolution: {integrity: sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g==} + engines: {node: '>=18'} + + '@jimp/js-jpeg@1.6.0': + resolution: {integrity: sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA==} + engines: {node: '>=18'} + + '@jimp/js-png@1.6.0': + resolution: {integrity: sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg==} + engines: {node: '>=18'} + + '@jimp/js-tiff@1.6.0': + resolution: {integrity: sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw==} + engines: {node: '>=18'} + + '@jimp/plugin-blit@1.6.0': + resolution: {integrity: sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA==} + engines: {node: '>=18'} + + '@jimp/plugin-blur@1.6.0': + resolution: {integrity: sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw==} + engines: {node: '>=18'} + + '@jimp/plugin-circle@1.6.0': + resolution: {integrity: sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw==} + engines: {node: '>=18'} + + '@jimp/plugin-color@1.6.0': + resolution: {integrity: sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA==} + engines: {node: '>=18'} + + '@jimp/plugin-contain@1.6.0': + resolution: {integrity: sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ==} + engines: {node: '>=18'} + + '@jimp/plugin-cover@1.6.0': + resolution: {integrity: sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA==} + engines: {node: '>=18'} + + '@jimp/plugin-crop@1.6.0': + resolution: {integrity: sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang==} + engines: {node: '>=18'} + + '@jimp/plugin-displace@1.6.0': + resolution: {integrity: sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q==} + engines: {node: '>=18'} + + '@jimp/plugin-dither@1.6.0': + resolution: {integrity: sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ==} + engines: {node: '>=18'} + + '@jimp/plugin-fisheye@1.6.0': + resolution: {integrity: sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA==} + engines: {node: '>=18'} + + '@jimp/plugin-flip@1.6.0': + resolution: {integrity: sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg==} + engines: {node: '>=18'} + + '@jimp/plugin-hash@1.6.0': + resolution: {integrity: sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q==} + engines: {node: '>=18'} + + '@jimp/plugin-mask@1.6.0': + resolution: {integrity: sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA==} + engines: {node: '>=18'} + + '@jimp/plugin-print@1.6.0': + resolution: {integrity: sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A==} + engines: {node: '>=18'} + + '@jimp/plugin-quantize@1.6.0': + resolution: {integrity: sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg==} + engines: {node: '>=18'} + + '@jimp/plugin-resize@1.6.0': + resolution: {integrity: sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==} + engines: {node: '>=18'} + + '@jimp/plugin-rotate@1.6.0': + resolution: {integrity: sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw==} + engines: {node: '>=18'} + + '@jimp/plugin-threshold@1.6.0': + resolution: {integrity: sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w==} + engines: {node: '>=18'} + + '@jimp/types@1.6.0': + resolution: {integrity: sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg==} + engines: {node: '>=18'} + + '@jimp/utils@1.6.0': + resolution: {integrity: sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA==} + engines: {node: '>=18'} '@lit-labs/ssr-dom-shim@1.5.1': resolution: {integrity: sha512-Aou5UdlSpr5whQe8AA/bZG0jMj96CoJIWbGfZ91qieWu5AWUMKw8VR/pAkQkJYvBNhmCcWnZlyyk5oze8JIqYA==} @@ -608,9 +847,6 @@ packages: '@push.rocks/early@4.0.4': resolution: {integrity: sha512-ak6/vqZ1PlFV08fSFQ6UwiBrr+K6IsfieZWWzT7eex1Ls6GvWEi8wZ3REFDPJq/qckNLWSgEy0EsqzRtltkaCA==} - '@push.rocks/isohash@2.0.1': - resolution: {integrity: sha512-UulhEui8O9Ei9fSqTldsB73TUmAFNqEBk82tHsJSLLpNK9gJZQE82iaSNsQUakoUQ2c9KueueMfwC3IoDaYRrQ==} - '@push.rocks/isounique@1.0.5': resolution: {integrity: sha512-Z0BVqZZOCif1THTbIKWMgg0wxCzt9CyBtBBqQJiZ+jJ0KlQFrQHNHrPt81/LXe/L4x0cxWsn0bpL6W5DNSvNLw==} @@ -632,8 +868,8 @@ packages: '@push.rocks/smartarchive@4.2.4': resolution: {integrity: sha512-uiqVAXPxmr8G5rv3uZvZFMOCt8l7cZC3nzvsy4YQqKf/VkPhKIEX+b7LkAeNlxPSYUiBQUkNRoawg9+5BaMcHg==} - '@push.rocks/smartbrowser@2.0.8': - resolution: {integrity: sha512-0KWRZj3TuKo/sNwgPbiSE6WL+TMeR19t1JmXBZWh9n8iA2mpc4HhMrQAndEUdRCkx5ofSaHWojIRVFzGChj0Dg==} + '@push.rocks/smartbrowser@2.0.11': + resolution: {integrity: sha512-Rj3+VodlFbW7CkvXqYxH4dEhtbYy987gVY7wsM76B+ipvnkSfjfEGuKUeMfKe4DscUBSmhlbJVYEYaH8RafUoQ==} '@push.rocks/smartbucket@3.3.10': resolution: {integrity: sha512-0H2MioALspC8Aj0Q1FPCs2w4k2u9oJg7Q5yM8+1TZo7aRfrdxgM5HQ7z3apUaqC3ZEDewW6vSlttjHFHhMEC3A==} @@ -644,10 +880,6 @@ packages: '@push.rocks/smartcache@1.0.18': resolution: {integrity: sha512-3+cmLu9chbnmi4yD4kjlFP/Tn4NReaZIoicEcGTtwbcokTrSDMs3YPdJzIpDZkAs83PW7OcVSHa3Ak5KU5OWzA==} - '@push.rocks/smartchok@1.2.0': - resolution: {integrity: sha512-I5fR++k6nR1wiMQUwcTYzO0g8FwD6gN+3HwfIzpQlolrM91i6Q5TaoMlt3aysbvKCSNvkL3FlJLOfUHNGWoWkA==} - deprecated: This package has been renamed. Please use @push.rocks/smartwatch instead. - '@push.rocks/smartcli@4.0.20': resolution: {integrity: sha512-gCo4ItvsPj8WoVAJw/6vkuoGA5FtIoACux2ktcCeH0nrFe7/xGR6waJ1aZcYAi7QN4gi52TlsgwuKz7BzXqhmQ==} @@ -678,12 +910,12 @@ packages: '@push.rocks/smartexit@1.1.0': resolution: {integrity: sha512-GD8VLIbxQuwvhPXwK4eH162XAYSj+M3wGKWGNO3i1iY4bj8P3BARcgsWx6/ntN3aCo5ygWtrevrfD5iecYY2Ng==} + '@push.rocks/smartexit@2.0.3': + resolution: {integrity: sha512-ZWpZ3Elorpv/rKtUcCUejUHG4BIE5B3QWysBAgb7lTcA7y0vGdFY32Y5/Q5tHpZM6PPxl/WTdUOYtSojQTq+pA==} + '@push.rocks/smartexpect@2.5.0': resolution: {integrity: sha512-yoyuCoQ3tTiAriuvF+/09fNbVfFnacudL2SwHSzPhX/ugaE7VTSWXQ9A34eKOWvil0MPyDcOY36fVZDxvrPd8A==} - '@push.rocks/smartfeed@1.4.0': - resolution: {integrity: sha512-bvj/3cGQI6TbbjbqrgC1uufcqprd/VthefuIsS8KHiHyCqYD5Z6RTjrbQY9WOCsmub/dcuMavfXQZqe9g2+OrQ==} - '@push.rocks/smartfile-interfaces@1.0.7': resolution: {integrity: sha512-MeOl/200UOvSO4Pgq/DVFiBVZpL9gjOBQM+4XYNjSxda8c6VBvchHAntaFLQUlO8U1ckNaP9i+nMO4O4/0ymyw==} @@ -693,8 +925,8 @@ packages: '@push.rocks/smartfile@13.1.2': resolution: {integrity: sha512-DaEhwmnGEpX4coeeToaw4cZe3pNBhH7CY1iGr+d3pIXihozREvzzAR9/0i2r7bUXXL5+Lgy8YYIk5ZS+fwxMKA==} - '@push.rocks/smartfs@1.3.1': - resolution: {integrity: sha512-ZSduVS8tM+/erbyCTvRRvc9gLWwbpqN5xdIIkMr+gub7fowSeJb7tR2rnGwySa63DyimU0q2KTp79VV9YqGLeg==} + '@push.rocks/smartfs@1.5.0': + resolution: {integrity: sha512-QwMD44HgX3d9PPxUwR0uS+0PEMtesKvKbZR+s4pezL2er6oPneKJMLkO6TJPvJ38nug6Lmlk9Bu7UrwR2kS3Vw==} '@push.rocks/smartguard@3.1.0': resolution: {integrity: sha512-J23q84f1O+TwFGmd4lrO9XLHUh2DaLXo9PN/9VmTWYzTkQDv5JehmifXVI0esophXcCIfbdIu6hbt7/aHlDF4A==} @@ -705,15 +937,15 @@ packages: '@push.rocks/smartinteract@2.0.16': resolution: {integrity: sha512-eltvVRRUKBKd77DSFA4DPY2g4V4teZLNe8A93CDy/WglglYcUjxMoLY/b0DFTWCWKYT+yjk6Fe6p0FRrvX9Yvg==} + '@push.rocks/smartjimp@1.2.0': + resolution: {integrity: sha512-SPz8p2ZuphNqIXK/UDsNFrnpJn/jr6FbuBSMQc0V2v2ffQIF32ZqktKQpXpitiqD1K5JEYS56JAhlYHgrAu7yw==} + '@push.rocks/smartjson@5.2.0': resolution: {integrity: sha512-710e8UwovRfPgUtaBHcd6unaODUjV5fjxtGcGCqtaTcmvOV6VpasdVfT66xMDzQmWH2E9ZfHDJeso9HdDQzNQA==} '@push.rocks/smartjson@6.0.0': resolution: {integrity: sha512-FYfJnmukt66WePn6xrVZ3BLmRQl9W82LcsICK3VU9sGW7kasig090jKXPm+yX8ibQcZAO/KyR/Q8tMIYZNxGew==} - '@push.rocks/smartlog-destination-devtools@1.0.12': - resolution: {integrity: sha512-zvsIkrqByc0JRaBgIyhh+PSz2SY/e/bmhZdUcr/OW6pudgAcqe2sso68EzrKux0w9OMl1P9ZnzF3FpCZPFWD/A==} - '@push.rocks/smartlog-destination-local@9.0.2': resolution: {integrity: sha512-htzIY+4+hU61Z2J4Oz+IHnAB3RGe+fpS0VKCKnAoppZqzMWnJ3UOgYIyr4djDBy2WtgpXV/16KdisKrOmwuuvw==} @@ -723,9 +955,6 @@ packages: '@push.rocks/smartlog@3.2.1': resolution: {integrity: sha512-x9/P59pfzY6HOGYmYrhqmoRl/pliTVx44g2Vbb8dIr/0zA39cAJHlPze1+UGncn37XKGmutK2iLSsJLEsexD0A==} - '@push.rocks/smartmanifest@2.0.2': - resolution: {integrity: sha512-QGc5C9vunjfUbYsPGz5bynV/mVmPHkrQDkWp8ZO8VJtK1GZe+njgbrNyxn2SUHR0IhSAbSXl1j4JvBqYf5eTVg==} - '@push.rocks/smartmarkdown@3.0.3': resolution: {integrity: sha512-9KhKZxDQKPk4P/2CYdVqJa5dpGfTA8w1cxqoVZL3e8RPA7EGxbdYEqMp0n2d9mth0btk/m0KHHV+G09LfCVeBw==} @@ -738,29 +967,26 @@ packages: '@push.rocks/smartmongo@2.2.0': resolution: {integrity: sha512-ovVCNoJ3D0aBuKtoKaQWWQKvBngaGJq9fAPQigzji1EHsS1XyGpXWCpe5nq/ptGvBROOcpqZcOFEGAcrnb+OjA==} + '@push.rocks/smartmongo@5.1.0': + resolution: {integrity: sha512-2tpKf8K+SMdLHOEpafgKPIN+ypWTLwHc33hCUDNMQ1KaL7vokkavA44+fHxQydOGPMtDi22tSMFeVMCcUSzs4w==} + '@push.rocks/smartnetwork@4.4.0': resolution: {integrity: sha512-OvFtz41cvQ7lcXwaIOhghNUUlNoMxvwKDctbDvMyuZyEH08SpLjhyv2FuKbKL/mgwA/WxakTbohoC8SW7t+kiw==} '@push.rocks/smartnpm@2.0.6': resolution: {integrity: sha512-7anKDOjX6gXWs1IAc+YWz9ZZ8gDsTwaLh+CxRnGHjAawOmK788NrrgVCg2Fb3qojrPnoxecc46F8Ivp1BT7Izw==} - '@push.rocks/smartntml@2.0.8': - resolution: {integrity: sha512-LIYeOQbmav2m2kZQz4pGS74xvWAm4YAGQnbPkofA2oas4RW9SGR1JTRpFd9pxGCOXd6djYdNGsZZ/xz+k/vRPQ==} - '@push.rocks/smartobject@1.0.12': resolution: {integrity: sha512-xSMiqXiZXXUOixT3QIPsOUKOWjL3YA/1h9/YTiCzqs5C0D3tyfTbojnfcp6YbKZoBzans2I5LghaDHsGid2DKQ==} - '@push.rocks/smartopen@2.0.0': - resolution: {integrity: sha512-eVT0GhtQ2drb95j/kktYst/Toh1zCwCqjTJFYtaYFUnnBnBUajPtBZDFnPQo01DN8JxoeCTo8jggq+PCvzcfww==} - '@push.rocks/smartpath@5.1.0': resolution: {integrity: sha512-pJ4UGATHV/C6Dw5DU0D3MJaPMASlKAgeS+Hl9dkhD2ceYArn86Ky3Z/g7LNj40Oz6cUe77/AP1chztmJZISrpw==} '@push.rocks/smartpath@6.0.0': resolution: {integrity: sha512-r94u1MbBaIOSy+517PZp2P7SuZPSe9LkwJ8l3dXQKHeIOri/zDxk/RQPiFM+j4N9301ztkRyhvRj7xgUDroOsg==} - '@push.rocks/smartpdf@3.3.0': - resolution: {integrity: sha512-k4mBZAIl/TVBHDYQXaBZAgC8DdmHXsIZ3hRrLY3ysLr143YJ1VkwqQ2poqWh3A2SZQDrVfpKRYliUtjFRFrYVw==} + '@push.rocks/smartpdf@4.2.0': + resolution: {integrity: sha512-+egzby5QKJGO10MDvWp+N69cJ8i5M354l9ntc+uLRpxuq/FEY9kigpRwMvRYF5qwOBTuGTLqvmvILzlLtboAQg==} '@push.rocks/smartping@1.0.8': resolution: {integrity: sha512-Fvx1Db6hSsDOI6pdiCuS9GjtOX8ugx865YQrPg5vK2iw6Qj/srwyXcWLFYt+19WVKtvtWDJIAKbW+q3bXFsCeA==} @@ -783,23 +1009,20 @@ packages: '@push.rocks/smartrouter@1.3.3': resolution: {integrity: sha512-1+xZEnWlhzqLWAaJ1zFNhQ0zgbfCWQl1DBT72LygLxTs+P0K8AwJKgqo/IX6CT55kGCFnPAZIYSbVJlGsgrB0w==} - '@push.rocks/smartrust@1.3.1': - resolution: {integrity: sha512-3ApbgF6yGeE2TRQxBY9Y48H1JlpcRheIp7QDBLSSfk80Uoe6fjdgBAfNz3Ir8hW3RZ3b7hA3sm1ZshCok58SEA==} + '@push.rocks/smartrust@1.3.2': + resolution: {integrity: sha512-HPzSJgDnKUdE5fkn2+BC9JvFXk7wl6aURAiHAXjHSCBLtzfgB7jEXjlg+K6CEfMjwQV7sy+hYABlq5DLXcFseQ==} '@push.rocks/smartrx@3.0.10': resolution: {integrity: sha512-USjIYcsSfzn14cwOsxgq/bBmWDTTzy3ouWAnW5NdMyRRzEbmeNrvmy6TRqNeDlJ2PsYNTt1rr/zGUqvIy72ITg==} - '@push.rocks/smarts3@2.2.7': - resolution: {integrity: sha512-9ZXGMlmUL2Wd+YJO0xOB8KyqPf4V++fWJvTq4s76bnqEuaCr9OLfq6czhban+i4cD3ZdIjehfuHqctzjuLw8Jw==} + '@push.rocks/smartserve@2.0.1': + resolution: {integrity: sha512-YQb2qexfCzCqOlLWBBXKMg6xG4zahCPAxomz/KEKAwHtW6wMTtuHKSTSkRTQ0vl9jssLMAmRz2OyafiL9XGJXQ==} '@push.rocks/smartshell@3.3.0': resolution: {integrity: sha512-m0w618H6YBs+vXGz1CgS4nPi5CUAnqRtckcS9/koGwfcIx1IpjqmiP47BoCTbdgcv0IPUxQVBG1IXTHPuZ8Z5g==} - '@push.rocks/smartsitemap@2.0.4': - resolution: {integrity: sha512-76dYWG/o/EjV4vYCK7ZKM35T9xgrI+oHEiiIE6E2MDaFIU6QnSfciTfbscH5nc0vxx8Ah+I0HPEJO94BM2S39w==} - - '@push.rocks/smartsocket@2.1.0': - resolution: {integrity: sha512-etOGyfiDFQz/1WJnD3jFL2N7ykujTjiudAz6qZTz82xE5oabKuKX+Cn8SdM9dOwzyWmBUKbUdll8QhovAXjn+g==} + '@push.rocks/smartshell@3.3.8': + resolution: {integrity: sha512-t9J/py0vnea4ZtOs7Anc9dc6lcvg6EDvYBw5eE1mB+KUWxMQf/ROIQwWMo6B9SMNY4JS2UwvfuJQJ8makP/7Tg==} '@push.rocks/smartspawn@3.0.3': resolution: {integrity: sha512-DyrGPV69wwOiJgKkyruk5hS3UEGZ99xFAqBE9O2nM8VXCRLbbty3xt1Ug5Z092ZZmJYaaGMSnMw3ijyZJFCT0Q==} @@ -807,6 +1030,9 @@ packages: '@push.rocks/smartstate@2.0.30': resolution: {integrity: sha512-IuNW8XtSumXIr7g7MIFyWg5PBwLF2mwsymTJbSEycK2Pa9ZLk4yjRHnR907xCilxgiMU9ixQZyNdpa5MMF999A==} + '@push.rocks/smartstorage@6.0.1': + resolution: {integrity: sha512-W5PEVwO0J2K9YUZRTbKXadC11h6/IBzzqU+P0TIE/xpJZC4K1duEXwEhxGWcbfhCkPRRa51xH8Z5mAmzzm8qxA==} + '@push.rocks/smartstream@3.2.5': resolution: {integrity: sha512-PLGGIFDy8JLNVUnnntMSIYN4W081YSbNC7Y/sWpvUT8PAXtbEXXUiDFgK5o3gcI0ptpKQxHAwxhzNlPj0sbFVg==} @@ -825,15 +1051,13 @@ packages: '@push.rocks/smartversion@3.0.5': resolution: {integrity: sha512-8MZSo1yqyaKxKq0Q5N188l4un++9GFWVbhCAX5mXJwewZHn97ujffTeL+eOQYpWFTEpUhaq1QhL4NhqObBCt1Q==} - '@push.rocks/smartxml@2.0.0': - resolution: {integrity: sha512-1d06zYJX4Zt8s5w5qFOUg2LAEz9ykrh9d6CQPK4WAgOBIefb1xzVEWHc7yoxicc2OkzNgC3IBCEg3s6BncZKWw==} + '@push.rocks/smartwatch@6.3.0': + resolution: {integrity: sha512-TeZ1PGBoBMpC4/CK8StIj5InEiFfKp7xWJSm3aYMjB/uaoeRP0vXqv1ORIC/TKYGJuEDuAXUsit8tZVjn0qT1Q==} + engines: {node: '>=20.0.0'} '@push.rocks/smartyaml@2.0.5': resolution: {integrity: sha512-tBcf+HaOIfeEsTMwgUZDtZERCxXQyRsWO8Ar5DjBdiSRchbhVGZQEBzXswMS0W5ZoRenjgPK+4tPW3JQGRTfbg==} - '@push.rocks/smartyaml@3.0.4': - resolution: {integrity: sha512-1JRt+hnoc2zHw3AW+vXKlCdSVwqOmY/01fu+2HBviS0UDjoZCa+/rp6E3GaQb5lEEafKi8ENbffAfjXXp3N2xQ==} - '@push.rocks/taskbuffer@3.5.0': resolution: {integrity: sha512-Y9WwIEIyp6oVFdj06j84tfrZIvjhbMb3DF52rYxlTeYLk3W7RPhSg1bGPCbtkXWeKdBrSe37V90BkOG7Qq8Pqg==} @@ -852,38 +1076,14 @@ packages: '@push.rocks/webstream@1.0.10': resolution: {integrity: sha512-45CcR0I4/9v0qSjLvz2dYTGMkR0YP3x66ItpStdad5hidJm86t1lfHF06d0oiEvJTpvQkeyIX/8YKAumf21d/Q==} - '@pushrocks/isounique@1.0.5': - resolution: {integrity: sha512-XYeoKGkmIdsWX64NlPA1fuA41n/1bQ7LdYXytlU/QqYeW7ojgA0ARRhBSh/2phL6o0Jpw6K/7gJ8jc7ab/Tc+w==} - deprecated: This package has been deprecated in favour of the new package at @push.rocks/isounique - '@pushrocks/smartdelay@3.0.1': resolution: {integrity: sha512-I+i/QhC6kLsXsWyW19UgD1vH2r1YWVxK19VMxt2CEuvxMyC6tuCd0vqud9vv5JxaxsJwxWlOsrURkgL4tXeILQ==} deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartdelay - '@pushrocks/smartenv@5.0.5': - resolution: {integrity: sha512-VWON1OJ4qV2/9hzJbgRquRekaO9am3b8W82tgCwgO6LBg23ea2tanfd+gESVMbRFduxHVoFLvlhSBcDGM5zsLA==} - deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartenv - - '@pushrocks/smarthash@3.0.2': - resolution: {integrity: sha512-jXW4f8k6iqOQRvkCmXMID1C+qXyNvUMKm7apPETxnO+L172VlzxP1dml0Ey1+vjfpU2luKCteJWX7W95sOdLDg==} - deprecated: This package has been deprecated in favour of the new package at @push.rocks/smarthash - - '@pushrocks/smartjson@5.0.6': - resolution: {integrity: sha512-9OJbnRgLTaCRQz+pqu5tB3ZCqRs5Zh0hnBe7t7URE+TgwIZ8aiELUIbWRkgn4mSGVzHyL6pqTyIowP6AjUCG3w==} - deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartjson - - '@pushrocks/smartpromise@3.1.10': - resolution: {integrity: sha512-VeTurbZ1+ZMxBDJk1Y1LV8SN9xLI+oDXKVeCFw41FAGEKOUEqordqFpi6t+7Vhe/TXUZzCVpZ5bXxAxrGf8yTQ==} - deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartpromise - '@pushrocks/smartpromise@4.0.2': resolution: {integrity: sha512-bqorOaGXPOuiOSV81luTKrTghg4O4NBRD0zyv7TIqmrMGf4a0uoozaUMp1X8vQdZW+y0gTzUJP9wkzAE6Cci0g==} deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartpromise - '@pushrocks/smartstring@4.0.7': - resolution: {integrity: sha512-TxHSar7Cj29E+GOcIj4DeZKWCNVzHKdqnrBRqcBqLqmeYZvzFosLXpFKoaCJDq7MSxuPoCvu5woSdp9YmPXyog==} - deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartstring - '@rolldown/binding-android-arm64@1.0.0-beta.52': resolution: {integrity: sha512-MBGIgysimZPqTDcLXI+i9VveijkP5C3EAncEogXhqfax6YXj1Tr2LY3DVuEOMIjWfMPMhtQSPup4fSTAmgjqIw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1265,12 +1465,6 @@ packages: resolution: {integrity: sha512-dSfDCeihDmZlV2oyr0yWPTUfh07suS+R5OB+FZGiv/hHyK3hrFBW5rR1UYjfa57vBsrP9lciFkRPzebaV1Qujw==} engines: {node: '>=18.0.0'} - '@so-ric/colorspace@1.1.6': - resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==} - - '@socket.io/component-emitter@3.1.2': - resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} - '@szmarczak/http-timer@5.0.1': resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} @@ -1311,30 +1505,12 @@ packages: '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} - '@types/body-parser@1.19.6': - resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} - - '@types/buffer-json@2.0.3': - resolution: {integrity: sha512-ItD4UfF3Q5jA+PEV6ZUWEHvlWaXJbd0rpuBKOIrEebM053FHaJddKsgUf0vy7nLSTs44nqFj3Mh8J3TiT0xv4g==} - '@types/clean-css@4.2.11': resolution: {integrity: sha512-Y8n81lQVTAfP2TOdtJJEsCoYl1AnOkqDqMvXb9/7pfgZZ7r8YrEyurrAvAoAjHOGXKRybay+5CsExqIH6liccw==} - '@types/connect@3.4.38': - resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} - - '@types/cors@2.8.19': - resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} - '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} - '@types/express-serve-static-core@5.1.1': - resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} - - '@types/express@5.0.6': - resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} - '@types/fs-extra@11.0.4': resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} @@ -1347,9 +1523,6 @@ packages: '@types/http-cache-semantics@4.2.0': resolution: {integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==} - '@types/http-errors@2.0.5': - resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} - '@types/js-yaml@3.12.10': resolution: {integrity: sha512-/Mtaq/wf+HxXpvhzFYzrzCqNRcA958sW++7JOFC8nPrZcvfi/TrzOaaGbvt27ltJB2NQbHVAg5a1wUCsyMH7NA==} @@ -1377,36 +1550,24 @@ packages: '@types/node-forge@1.3.14': resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==} + '@types/node@16.9.1': + resolution: {integrity: sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==} + '@types/node@22.19.12': resolution: {integrity: sha512-0QEp0aPJYSyf6RrTjDB7HlKgNMTY+V2C7ESTaVt6G9gQ0rPLzTGz7OF2NXTLR5vcy7HJEtIUsyWLsfX0kTqJBA==} + '@types/node@25.5.0': + resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} + '@types/ping@0.4.4': resolution: {integrity: sha512-ifvo6w2f5eJYlXm+HiVx67iJe8WZp87sfa683nlqED5Vnt9Z93onkokNoWqOG21EaE8fMxyKPobE+mkPEyxsdw==} - '@types/qs@6.14.0': - resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} - - '@types/randomatic@3.1.5': - resolution: {integrity: sha512-VCwCTw6qh1pRRw+5rNTAwqPmf6A+hdrkdM7dBpZVmhl7g+em3ONXlYK/bWPVKqVGMWgP0d1bog8Vc/X6zRwRRQ==} - - '@types/range-parser@1.2.7': - resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} - '@types/relateurl@0.2.33': resolution: {integrity: sha512-bTQCKsVbIdzLqZhLkF5fcJQreE4y1ro4DIyVrlDNSCJRRwHhB8Z+4zXXa8jN6eDvc2HbRsEYgbvrnGvi54EpSw==} - '@types/s3rver@3.7.4': - resolution: {integrity: sha512-CMCmdNszxS2FsIznWvBMVCl6fpvr5ueaFCaY0iSoH7Ud5maGcLghukpDvsXBnIcp92cv2HeVnVqI1p8yPcab9Q==} - '@types/semver@7.7.1': resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} - '@types/send@1.2.1': - resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} - - '@types/serve-static@2.2.0': - resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} - '@types/symbol-tree@3.2.5': resolution: {integrity: sha512-zXnnyENt1TYQcS21MkPaJCVjfcPq7p7yc5mo5JACuumXp6sly5jnlS0IokHd+xmmuCbx6V7JqkMBpswR+nZAcw==} @@ -1416,9 +1577,6 @@ packages: '@types/through2@2.0.41': resolution: {integrity: sha512-ryQ0tidWkb1O1JuYvWKyMLYEtOWDqF5mHerJzKz/gQpoAaJq2l/dsMPBF0B5BNVT34rbARYJ5/tsZwLfUi2kwQ==} - '@types/triple-beam@1.3.5': - resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} - '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -1455,13 +1613,9 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - accepts@1.3.8: - resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} - engines: {node: '>= 0.6'} - - accepts@2.0.0: - resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} - engines: {node: '>= 0.6'} + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} acme-client@5.4.0: resolution: {integrity: sha512-mORqg60S8iML6XSmVjqjGHJkINrCGLMj2QvDmFzI9vIlv1RGlyjmw3nrzaINJjkNsYXC41XhhD5pfy7CtuGcbA==} @@ -1487,14 +1641,13 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + any-base@1.1.0: + resolution: {integrity: sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==} + argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -1512,12 +1665,13 @@ packages: async-mutex@0.5.0: resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==} - async@3.2.6: - resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} - asynckit@0.4.0: resolution: {integrity: sha1-x57Zf380y48robyXkLzDZkdLS3k=} + await-to-js@3.0.0: + resolution: {integrity: sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==} + engines: {node: '>=6.0.0'} + axios@1.13.5: resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==} @@ -1580,17 +1734,12 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - base64id@2.0.0: - resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} - engines: {node: ^4.5.0 || >= 5.9} - basic-ftp@5.2.0: resolution: {integrity: sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==} engines: {node: '>=10.0.0'} - body-parser@2.2.2: - resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} - engines: {node: '>=18'} + bmp-ts@1.0.9: + resolution: {integrity: sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==} bowser@2.14.1: resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} @@ -1612,24 +1761,9 @@ packages: buffer-crc32@0.2.13: resolution: {integrity: sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=} - buffer-json@2.0.0: - resolution: {integrity: sha512-+jjPFVqyfF1esi9fvfUs3NqM0pH1ziZ36VP4hmA/y/Ssfo/5w5xHKfTw9BwQjoJ1w/oVtpLomqwUHKdefGyuHw==} - buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - busboy@0.3.1: - resolution: {integrity: sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==} - engines: {node: '>=4.5.0'} - - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} - - cache-content-type@1.0.1: - resolution: {integrity: sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==} - engines: {node: '>= 6.0.0'} - cacheable-lookup@7.0.0: resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==} engines: {node: '>=14.16'} @@ -1646,10 +1780,6 @@ packages: resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} engines: {node: '>= 0.4'} - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -1664,10 +1794,6 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - character-entities-html4@2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} @@ -1705,39 +1831,13 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} - co@4.6.0: - resolution: {integrity: sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=} - engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - color-convert@3.1.3: - resolution: {integrity: sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==} - engines: {node: '>=14.6'} - - color-name@1.1.3: - resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=} - color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - color-name@2.1.0: - resolution: {integrity: sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==} - engines: {node: '>=12.20'} - - color-string@2.1.4: - resolution: {integrity: sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==} - engines: {node: '>=18'} - - color@5.0.3: - resolution: {integrity: sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==} - engines: {node: '>=18'} - combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -1748,10 +1848,6 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - commander@5.1.0: - resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} - engines: {node: '>= 6'} - commondir@1.0.1: resolution: {integrity: sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=} @@ -1761,34 +1857,6 @@ packages: config-chain@1.1.13: resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} - content-disposition@0.5.4: - resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} - engines: {node: '>= 0.6'} - - content-disposition@1.0.1: - resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} - engines: {node: '>=18'} - - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - - cookie-signature@1.2.2: - resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} - engines: {node: '>=6.6.0'} - - cookie@0.7.2: - resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} - engines: {node: '>= 0.6'} - - cookies@0.9.1: - resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} - engines: {node: '>= 0.8'} - - cors@2.8.6: - resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} - engines: {node: '>= 0.10'} - cosmiconfig@9.0.0: resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} engines: {node: '>=14'} @@ -1806,10 +1874,6 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - crypto-random-string@5.0.0: - resolution: {integrity: sha512-KWjTXWwxFd6a94m5CdRGW/t82Tr8DoBc9dNnPCAbFI1EBweN6v1tv8y4Y1m7ndkp/nkIBRxUxAzpaBnR2k3bcQ==} - engines: {node: '>=14.16'} - data-uri-to-buffer@6.0.2: resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} engines: {node: '>= 14'} @@ -1820,15 +1884,6 @@ packages: dayjs@1.11.19: resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} - debug@4.3.7: - resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1845,9 +1900,6 @@ packages: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} - deep-equal@1.0.1: - resolution: {integrity: sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=} - deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -1860,10 +1912,6 @@ packages: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} - define-lazy-prop@2.0.0: - resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} - engines: {node: '>=8'} - define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} @@ -1876,24 +1924,13 @@ packages: resolution: {integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk=} engines: {node: '>=0.4.0'} - delegates@1.0.0: - resolution: {integrity: sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=} - - depd@1.1.2: - resolution: {integrity: sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=} - engines: {node: '>= 0.6'} - - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} - dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - destroy@1.2.0: - resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -1901,49 +1938,16 @@ packages: devtools-protocol@0.0.1566079: resolution: {integrity: sha512-MJfAEA1UfVhSs7fbSQOG4czavUp1ajfg6prlAN0+cmfa2zNjaIbvq8VneP7do1WAQQIvgNJWSMeP6UyI90gIlQ==} - dicer@0.3.0: - resolution: {integrity: sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==} - engines: {node: '>=4.5.0'} - dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - ee-first@1.1.1: - resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=} - emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - enabled@2.0.0: - resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} - - encodeurl@1.0.2: - resolution: {integrity: sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=} - engines: {node: '>= 0.8'} - - encodeurl@2.0.0: - resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} - engines: {node: '>= 0.8'} - end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - engine.io-client@6.6.4: - resolution: {integrity: sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==} - - engine.io-parser@5.2.3: - resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} - engines: {node: '>=10.0.0'} - - engine.io@6.6.4: - resolution: {integrity: sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==} - engines: {node: '>=10.2.0'} - - entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} - env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -1976,13 +1980,6 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - escape-html@1.0.3: - resolution: {integrity: sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=} - - escape-string-regexp@1.0.5: - resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=} - engines: {node: '>=0.8.0'} - escape-string-regexp@5.0.0: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} @@ -2009,9 +2006,9 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - etag@1.8.1: - resolution: {integrity: sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=} - engines: {node: '>= 0.6'} + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} @@ -2019,13 +2016,12 @@ packages: events-universal@1.0.1: resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} - express-force-ssl@0.3.2: - resolution: {integrity: sha1-AbK0mK5v0uQRUrIrV6Phc3c69n4=} - engines: {node: '>=0.2.2'} + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} - express@5.2.1: - resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} - engines: {node: '>= 18'} + exif-parser@0.1.12: + resolution: {integrity: sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==} extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -2043,10 +2039,6 @@ packages: resolution: {integrity: sha512-cB507r5T3D55DfclY01GLkninZLfU7HXV/mhVRTnTRm5k2u+fY7Fof2dBkr80p5t7G7dlA/G5dI87QiMdPpMCQ==} engines: {node: '>=18'} - fake-indexeddb@6.2.5: - resolution: {integrity: sha512-CGnyrvbhPlWYMngksqrSSUT1BAVP49dZocrHuK0SvtR0D5TMs5wP0o3j7jexDJW01KSadjBp1M/71o/KR3nD1w==} - engines: {node: '>=18'} - fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2056,34 +2048,16 @@ packages: fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - fast-xml-builder@1.0.0: - resolution: {integrity: sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==} - - fast-xml-parser@3.21.1: - resolution: {integrity: sha512-FTFVjYoBOZTJekiUsawGsSYV9QL0A+zDYCRj7y34IO6Jg+2IMYEtQa+bbictpdpV8dHxXywqU7C0gRDEOFtBFg==} - hasBin: true - - fast-xml-parser@4.5.4: - resolution: {integrity: sha512-jE8ugADnYOBsu1uaoayVl1tVKAMNOXyjwvv2U6udEA2ORBhDooJDWoGxTkhd4Qn4yh59JVVt/pKXtjPwx9OguQ==} - hasBin: true - fast-xml-parser@5.3.6: resolution: {integrity: sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==} hasBin: true - fast-xml-parser@5.4.1: - resolution: {integrity: sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==} - hasBin: true - fault@2.0.1: resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} fd-slicer@1.1.0: resolution: {integrity: sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=} - fecha@4.2.3: - resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} - fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} @@ -2091,6 +2065,10 @@ packages: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} engines: {node: '>=18'} + file-type@16.5.4: + resolution: {integrity: sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==} + engines: {node: '>=10'} + file-type@19.6.0: resolution: {integrity: sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==} engines: {node: '>=18'} @@ -2099,10 +2077,6 @@ packages: resolution: {integrity: sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA==} engines: {node: '>=20'} - finalhandler@2.1.1: - resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} - engines: {node: '>= 18.0.0'} - find-cache-dir@3.3.2: resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} engines: {node: '>=8'} @@ -2111,9 +2085,6 @@ packages: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} - fn.name@1.1.0: - resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} - follow-redirects@1.15.11: resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} engines: {node: '>=4.0'} @@ -2143,26 +2114,10 @@ packages: resolution: {integrity: sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs=} engines: {node: '>=0.4.x'} - forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} - - fresh@0.5.2: - resolution: {integrity: sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=} - engines: {node: '>= 0.6'} - - fresh@2.0.0: - resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} - engines: {node: '>= 0.8'} - fs-extra@11.3.3: resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==} engines: {node: '>=14.14'} - fs-extra@8.1.0: - resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} - engines: {node: '>=6 <7 || >=8'} - fs.realpath@1.0.0: resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} @@ -2174,10 +2129,6 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - generator-function@2.0.1: - resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} - engines: {node: '>= 0.4'} - get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -2209,6 +2160,9 @@ packages: resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==} engines: {node: '>= 14'} + gifwrap@0.10.1: + resolution: {integrity: sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==} + glob@11.1.0: resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} engines: {node: 20 || >=22} @@ -2231,14 +2185,6 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - happy-dom@15.11.7: - resolution: {integrity: sha512-KyrFvnl+J9US63TEzwoiJOQzZBJY7KgBushJA8X61DMbNsH+2ONkDuLDnCnwUiPTF42tLoEmrPyoqbenVA5zrg==} - engines: {node: '>=18.0.0'} - - has-flag@3.0.0: - resolution: {integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0=} - engines: {node: '>=4'} - has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} @@ -2275,21 +2221,9 @@ packages: html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - http-assert@1.5.0: - resolution: {integrity: sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==} - engines: {node: '>= 0.8'} - http-cache-semantics@4.2.0: resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} - http-errors@1.8.1: - resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} - engines: {node: '>= 0.6'} - - http-errors@2.0.1: - resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} - engines: {node: '>= 0.8'} - http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -2305,20 +2239,16 @@ packages: humanize-ms@1.2.1: resolution: {integrity: sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=} - humanize-number@0.0.2: - resolution: {integrity: sha1-EcCvakcWQ2M1iFiASPF5lUFInBg=} - iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} - iconv-lite@0.7.2: - resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} - engines: {node: '>=0.10.0'} - ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + image-q@4.0.0: + resolution: {integrity: sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==} + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -2340,34 +2270,17 @@ packages: resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} engines: {node: '>= 12'} - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} - is-arrayish@0.2.1: resolution: {integrity: sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=} - is-docker@2.2.1: - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} - engines: {node: '>=8'} - hasBin: true - is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-generator-function@1.1.2: - resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} - engines: {node: '>= 0.4'} - is-nan@1.3.2: resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} engines: {node: '>= 0.4'} - is-number@4.0.0: - resolution: {integrity: sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==} - engines: {node: '>=0.10.0'} - is-observable@2.1.0: resolution: {integrity: sha512-DailKdLb0WU+xX8K5w7VsJhapwHLZ9jjmazqCJq4X12CTgqq73TKnbRcnSLuXYPOoLQgV5IrD7ePiX/h1vnkBw==} engines: {node: '>=8'} @@ -2376,17 +2289,6 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} - is-promise@4.0.0: - resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - - is-regex@1.2.1: - resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} - engines: {node: '>= 0.4'} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - is-stream@4.0.1: resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} engines: {node: '>=18'} @@ -2399,10 +2301,6 @@ packages: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} - is-wsl@2.2.0: - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} - engines: {node: '>=8'} - isexe@2.0.0: resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=} @@ -2410,6 +2308,10 @@ packages: resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==} engines: {node: '>=18'} + isexe@4.0.0: + resolution: {integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==} + engines: {node: '>=20'} + isopen@1.3.0: resolution: {integrity: sha512-AN6Q9J0UlqHFl1fN/2xJCHCBLCBCFDjZhpGBO1gh3wzgRPsFSFBUL36I2Lbfd9qkuoj58axmE7j83iejTQsk8Q==} @@ -2417,8 +2319,12 @@ packages: resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==} engines: {node: 20 || >=22} - js-base64@3.7.8: - resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} + jimp@1.6.0: + resolution: {integrity: sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg==} + engines: {node: '>=18'} + + jpeg-js@0.4.4: + resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2437,41 +2343,12 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - jsonfile@4.0.0: - resolution: {integrity: sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=} - jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} - keygrip@1.1.0: - resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} - engines: {node: '>= 0.6'} - keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} - - koa-compose@4.1.0: - resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==} - - koa-convert@2.0.0: - resolution: {integrity: sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==} - engines: {node: '>= 10'} - - koa-logger@3.2.1: - resolution: {integrity: sha512-MjlznhLLKy9+kG8nAXKJLM0/ClsQp/Or2vI3a5rbSQmgl8IJBQO0KI5FA70BvW+hqjtxjp49SpH2E7okS6NmHg==} - engines: {node: '>= 7.6.0'} - - koa@2.16.4: - resolution: {integrity: sha512-3An0GCLDSR34tsCO4H8Tef8Pp2ngtaZDAZnsWJYelqXUK5wyiHvGItgK/xcSkmHLSTn1Jcho1mRQs2ehRzvKKw==} - engines: {node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4} - - kuler@2.0.0: - resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} - lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -2488,49 +2365,9 @@ packages: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} - lodash._baseassign@3.2.0: - resolution: {integrity: sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=} - - lodash._basecopy@3.0.1: - resolution: {integrity: sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=} - - lodash._bindcallback@3.0.1: - resolution: {integrity: sha1-5THCdkTPi1epnhftlbNcdIeJOS4=} - - lodash._createassigner@3.1.1: - resolution: {integrity: sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=} - - lodash._getnative@3.9.1: - resolution: {integrity: sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=} - - lodash._isiterateecall@3.0.9: - resolution: {integrity: sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=} - - lodash.assign@3.2.0: - resolution: {integrity: sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=} - lodash.clonedeep@4.5.0: resolution: {integrity: sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=} - lodash.isarguments@3.1.0: - resolution: {integrity: sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=} - - lodash.isarray@3.0.4: - resolution: {integrity: sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=} - - lodash.keys@3.1.2: - resolution: {integrity: sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=} - - lodash.restparam@3.6.1: - resolution: {integrity: sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=} - - lodash@4.17.23: - resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} - - logform@2.7.0: - resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==} - engines: {node: '>= 12.0.0'} - longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -2570,9 +2407,6 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - math-random@1.0.4: - resolution: {integrity: sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==} - mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} @@ -2612,25 +2446,9 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} - media-typer@0.3.0: - resolution: {integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=} - engines: {node: '>= 0.6'} - - media-typer@1.1.0: - resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} - engines: {node: '>= 0.8'} - memory-pager@1.5.0: resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} - merge-descriptors@2.0.0: - resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} - engines: {node: '>=18'} - - methods@1.1.2: - resolution: {integrity: sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=} - engines: {node: '>= 0.6'} - micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -2722,17 +2540,14 @@ packages: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} - mime-db@1.54.0: - resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} - engines: {node: '>= 0.6'} - mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - mime-types@3.0.2: - resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} - engines: {node: '>=18'} + mime@3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true mime@4.1.0: resolution: {integrity: sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==} @@ -2821,14 +2636,6 @@ packages: engines: {node: ^14 || ^16 || >=18} hasBin: true - negotiator@0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} - engines: {node: '>= 0.6'} - - negotiator@1.0.0: - resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} - engines: {node: '>= 0.6'} - netmask@2.0.2: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} @@ -2844,22 +2651,10 @@ packages: resolution: {integrity: sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==} engines: {node: '>= 6.13.0'} - normalize-newline@4.1.0: - resolution: {integrity: sha512-ff4jKqMI8Xl50/4Mms/9jPobzAV/UK+kXG2XJ/7AqOmxIx8mqfqTIHYxuAnEgJ2AQeBbLnlbmZ5+38Y9A0w/YA==} - engines: {node: '>=12'} - normalize-url@8.1.1: resolution: {integrity: sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==} engines: {node: '>=14.16'} - object-assign@4.1.1: - resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=} - engines: {node: '>=0.10.0'} - - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} @@ -2871,23 +2666,12 @@ packages: observable-fns@0.6.1: resolution: {integrity: sha512-9gRK4+sRWzeN6AOewNBTLXir7Zl/i3GB6Yl26gK4flxz8BXVpD3kt8amREmWNb0mxYOGDotvE5a4N+PtGGKdkg==} - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} + omggif@1.0.10: + resolution: {integrity: sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==} once@1.4.0: resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} - one-time@1.0.0: - resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} - - only@0.0.2: - resolution: {integrity: sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=} - - open@8.4.2: - resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} - engines: {node: '>=12'} - os-tmpdir@1.0.2: resolution: {integrity: sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=} engines: {node: '>=0.10.0'} @@ -2945,6 +2729,15 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-bmfont-ascii@1.0.6: + resolution: {integrity: sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==} + + parse-bmfont-binary@1.0.6: + resolution: {integrity: sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==} + + parse-bmfont-xml@1.1.6: + resolution: {integrity: sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==} + parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} @@ -2953,13 +2746,6 @@ packages: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - - passthrough-counter@1.0.0: - resolution: {integrity: sha1-GWfZ5m2lcrXAI8eH2xEqOHqxZvo=} - path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2976,21 +2762,22 @@ packages: resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} engines: {node: 18 || 20 || >=22} - path-to-regexp@6.3.0: - resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} - path-to-regexp@8.3.0: resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} pdf-lib@1.17.1: resolution: {integrity: sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==} - pdf2json@3.2.0: - resolution: {integrity: sha512-5RJYU5zWFXTQ5iRXAo75vlhK5ybZOyqEyg/szw2VtHc6ZOPcC7ruX4nnXk1OqqlY56Z7XT+WCFhV+/XPj4QwtQ==} + pdf2json@4.0.2: + resolution: {integrity: sha512-iiRSuRmLihoEJ4YGkoqSq3/r4MR0OmkMTYDda0Pq7DAWqJwMylTilXu46T16gfS3DUp3fhiVuz7NtRMbk3uBhw==} engines: {node: '>=20.18.0'} hasBin: true bundledDependencies: [] + peek-readable@4.1.0: + resolution: {integrity: sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==} + engines: {node: '>=8'} + peek-readable@5.4.2: resolution: {integrity: sha512-peBp3qZyuS6cNIJ2akRNG1uo1WJ1d0wTxg/fxMdZ0BqCVhx242bSFHM9eNqflfJVS9SsgkzgT/1UgnsurBOTMg==} engines: {node: '>=14.16'} @@ -3009,14 +2796,30 @@ packages: resolution: {integrity: sha512-56ZMC0j7SCsMMLdOoUg12VZCfj/+ZO+yfOSjaNCRrmZZr6GLbN2X/Ui56T15dI8NhiHckaw5X2pvyfAomanwqQ==} engines: {node: '>=4.0.0'} + pixelmatch@5.3.0: + resolution: {integrity: sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==} + hasBin: true + pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} + pngjs@6.0.0: + resolution: {integrity: sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==} + engines: {node: '>=12.13.0'} + + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + pretty-ms@9.3.0: resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} engines: {node: '>=18'} + process@0.11.10: + resolution: {integrity: sha1-czIwDoQBYb2j5podHZGn1LwW8YI=} + engines: {node: '>= 0.6.0'} + progress@2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} @@ -3027,10 +2830,6 @@ packages: proto-list@1.2.4: resolution: {integrity: sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=} - proxy-addr@2.0.7: - resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} - engines: {node: '>= 0.10'} - proxy-agent@6.5.0: resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} engines: {node: '>= 14'} @@ -3041,9 +2840,6 @@ packages: pump@3.0.3: resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} - punycode@1.4.1: - resolution: {integrity: sha1-wNWmOycYgArY4esPpSachN1BhF4=} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -3064,26 +2860,10 @@ packages: resolution: {integrity: sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==} engines: {node: '>=16.0.0'} - qs@6.15.0: - resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} - engines: {node: '>=0.6'} - quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} - randomatic@3.1.1: - resolution: {integrity: sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==} - engines: {node: '>= 0.10.0'} - - range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} - - raw-body@3.0.2: - resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} - engines: {node: '>= 0.10'} - rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -3092,6 +2872,14 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + readable-stream@4.7.0: + resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + readable-web-to-node-stream@3.0.4: + resolution: {integrity: sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==} + engines: {node: '>=8'} + readdirp@5.0.0: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} @@ -3126,10 +2914,6 @@ packages: remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} - replace-buffer@1.2.1: - resolution: {integrity: sha512-ly3OKwKu+3T55DjP5PjIMzxgz9lFx6dQnBmAIxryZyRKl8f22juy12ShOyuq8WrQE5UlFOseZgQZDua0iF9DHw==} - engines: {node: '>=4'} - require-directory@2.1.1: resolution: {integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I=} engines: {node: '>=0.10.0'} @@ -3157,10 +2941,6 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - router@2.2.0: - resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} - engines: {node: '>= 18'} - run-async@3.0.0: resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} engines: {node: '>=0.12.0'} @@ -3168,25 +2948,16 @@ packages: rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - s3rver@3.7.1: - resolution: {integrity: sha512-H9KIX6n8NqcfoE4ziFNbQASBQfjcNJgb+3wbT9L5iotEqfOncFO1c38cfJSFSo7xXTu1zM9HA6t2u9xKNlYRaA==} - engines: {node: '>=8.3.0'} - hasBin: true - safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safe-regex-test@1.1.0: - resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} - engines: {node: '>= 0.4'} - - safe-stable-stringify@2.5.0: - resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} - engines: {node: '>=10'} - safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sax@1.6.0: + resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} + engines: {node: '>=11.0.0'} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -3196,20 +2967,13 @@ packages: engines: {node: '>=10'} hasBin: true - send@1.2.1: - resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} - engines: {node: '>= 18'} - - serve-static@2.2.1: - resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} - engines: {node: '>= 18'} - set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} @@ -3219,22 +2983,6 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} - signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -3242,6 +2990,10 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-xml-to-json@1.2.4: + resolution: {integrity: sha512-3MY16e0ocMHL7N1ufpdObURGyX+lCo0T/A+y6VCwosLdH1HSda4QZl1Sdt/O+2qWp48WFi26XEp5rF0LoaL0Dg==} + engines: {node: '>=20.12.2'} + smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} @@ -3250,21 +3002,6 @@ packages: resolution: {integrity: sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==} engines: {node: '>= 18'} - socket.io-adapter@2.5.6: - resolution: {integrity: sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==} - - socket.io-client@4.8.1: - resolution: {integrity: sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==} - engines: {node: '>=10.0.0'} - - socket.io-parser@4.2.5: - resolution: {integrity: sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==} - engines: {node: '>=10.0.0'} - - socket.io@4.8.1: - resolution: {integrity: sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==} - engines: {node: '>=10.2.0'} - socks-proxy-agent@8.0.5: resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} engines: {node: '>= 14'} @@ -3290,21 +3027,6 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=} - stack-trace@0.0.10: - resolution: {integrity: sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=} - - statuses@1.5.0: - resolution: {integrity: sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=} - engines: {node: '>= 0.6'} - - statuses@2.0.2: - resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} - engines: {node: '>= 0.8'} - - streamsearch@0.1.2: - resolution: {integrity: sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=} - engines: {node: '>=0.8.0'} - streamx@2.23.0: resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} @@ -3322,17 +3044,10 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-indent@4.1.1: - resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} - engines: {node: '>=12'} - strip-json-comments@2.0.1: resolution: {integrity: sha1-PFMZQukIwml8DsNEhYwobHygpgo=} engines: {node: '>=0.10.0'} - strnum@1.1.2: - resolution: {integrity: sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==} - strnum@2.1.2: resolution: {integrity: sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==} @@ -3340,14 +3055,14 @@ packages: resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} engines: {node: '>=18'} + strtok3@6.3.0: + resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==} + engines: {node: '>=10'} + strtok3@9.1.1: resolution: {integrity: sha512-FhwotcEqjr241ZbjFzjlIYg6c5/L/s4yBGWSMvJ9UoExiSqL+FnFA/CaeZx17WGaZMS/4SOZp8wH18jSS4R4lw==} engines: {node: '>=16'} - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - sweet-scroll@4.0.0: resolution: {integrity: sha512-mR6fRsAQANtm3zpzhUE73KAOt2aT4ZsWzNSggiEsSqdO6Zh4gM7ioJG81EngrZEl0XAc3ZvzEfhxggOoEBc8jA==} @@ -3372,9 +3087,6 @@ packages: text-decoder@1.2.7: resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} - text-hex@1.0.0: - resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} - threads@1.7.0: resolution: {integrity: sha512-Mx5NBSHX3sQYR6iI9VYbgHKBLisyB+xROCBGjjWm1O9wb9vfLxdaGtmT/KCjUqMsSNW6nERzCW3T6H43LqjDZQ==} @@ -3384,13 +3096,16 @@ packages: tiny-worker@2.3.0: resolution: {integrity: sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==} + tinycolor2@1.6.0: + resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} + token-types@4.2.1: + resolution: {integrity: sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==} + engines: {node: '>=10'} token-types@6.1.2: resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} @@ -3407,10 +3122,6 @@ packages: trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - triple-beam@1.4.1: - resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} - engines: {node: '>= 14.0.0'} - trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} @@ -3420,10 +3131,6 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsscmp@1.0.6: - resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} - engines: {node: '>=0.6.x'} - tsx@4.21.0: resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} engines: {node: '>=18.0.0'} @@ -3443,22 +3150,10 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} - type-fest@2.19.0: - resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} - engines: {node: '>=12.20'} - type-fest@4.41.0: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} - type-is@1.6.18: - resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} - engines: {node: '>= 0.6'} - - type-is@2.0.1: - resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} - engines: {node: '>= 0.6'} - typed-query-selector@2.12.1: resolution: {integrity: sha512-uzR+FzI8qrUEIu96oaeBJmd9E7CFEiQ3goA5qCVgc4s5llSubcfGHq9yUstZx/k4s9dXHVKsE35YWoFyvEqEHA==} @@ -3479,6 +3174,9 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -3497,10 +3195,6 @@ packages: unist-util-visit@5.1.0: resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} - universalify@0.1.2: - resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} - engines: {node: '>= 4.0.0'} - universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -3508,16 +3202,11 @@ packages: unload@2.4.1: resolution: {integrity: sha512-IViSAm8Z3sRBYA+9wc0fLQmU9Nrxb16rcDmIiR6Y9LJSZzI7QY5QsDhqPpKOjAn0O9/kfK1TfNEMMAGPTIraPw==} - unpipe@1.0.0: - resolution: {integrity: sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=} - engines: {node: '>= 0.8'} - upper-case@1.1.3: resolution: {integrity: sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=} - url@0.11.4: - resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==} - engines: {node: '>= 0.4'} + utif2@4.1.0: + resolution: {integrity: sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==} util-deprecate@1.0.2: resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=} @@ -3526,10 +3215,6 @@ packages: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true - vary@1.1.2: - resolution: {integrity: sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=} - engines: {node: '>= 0.8'} - vfile-message@4.0.3: resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} @@ -3543,10 +3228,6 @@ packages: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} - whatwg-mimetype@3.0.0: - resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} - engines: {node: '>=12'} - whatwg-url@14.2.0: resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} engines: {node: '>=18'} @@ -3561,13 +3242,10 @@ packages: engines: {node: ^18.17.0 || >=20.5.0} hasBin: true - winston-transport@4.9.0: - resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==} - engines: {node: '>= 12.0.0'} - - winston@3.19.0: - resolution: {integrity: sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==} - engines: {node: '>= 12.0.0'} + which@6.0.1: + resolution: {integrity: sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} @@ -3580,30 +3258,6 @@ packages: wrappy@1.0.2: resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} - ws@8.17.1: - resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - ws@8.19.0: resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} engines: {node: '>=10.0.0'} @@ -3616,19 +3270,21 @@ packages: utf-8-validate: optional: true - xmlhttprequest-ssl@2.1.2: - resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==} - engines: {node: '>=0.4.0'} + xml-parse-from-string@1.0.1: + resolution: {integrity: sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==} + + xml2js@0.5.0: + resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} + engines: {node: '>=4.0.0'} + + xmlbuilder@11.0.1: + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + engines: {node: '>=4.0'} y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} - yaml@2.8.2: - resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} - engines: {node: '>= 14.6'} - hasBin: true - yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -3648,10 +3304,6 @@ packages: resolution: {integrity: sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==} engines: {node: '>=12'} - ylru@1.4.0: - resolution: {integrity: sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==} - engines: {node: '>= 4.0.0'} - yoctocolors-cjs@2.1.3: resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} engines: {node: '>=18'} @@ -3680,72 +3332,6 @@ snapshots: '@push.rocks/webrequest': 4.0.2 '@push.rocks/webstream': 1.0.10 - '@api.global/typedserver@3.0.80': - dependencies: - '@api.global/typedrequest': 3.2.6 - '@api.global/typedrequest-interfaces': 3.0.19 - '@api.global/typedsocket': 3.1.1 - '@cloudflare/workers-types': 4.20260305.0 - '@design.estate/dees-comms': 1.0.30 - '@push.rocks/lik': 6.2.2 - '@push.rocks/smartchok': 1.2.0 - '@push.rocks/smartdelay': 3.0.5 - '@push.rocks/smartenv': 5.0.13 - '@push.rocks/smartfeed': 1.4.0 - '@push.rocks/smartfile': 11.2.7 - '@push.rocks/smartjson': 5.2.0 - '@push.rocks/smartlog': 3.2.1 - '@push.rocks/smartlog-destination-devtools': 1.0.12 - '@push.rocks/smartlog-interfaces': 3.0.2 - '@push.rocks/smartmanifest': 2.0.2 - '@push.rocks/smartmatch': 2.0.0 - '@push.rocks/smartmime': 2.0.4 - '@push.rocks/smartntml': 2.0.8 - '@push.rocks/smartopen': 2.0.0 - '@push.rocks/smartpath': 6.0.0 - '@push.rocks/smartpromise': 4.2.3 - '@push.rocks/smartrequest': 4.4.2 - '@push.rocks/smartrx': 3.0.10 - '@push.rocks/smartsitemap': 2.0.4 - '@push.rocks/smartstream': 3.2.5 - '@push.rocks/smarttime': 4.2.3 - '@push.rocks/taskbuffer': 3.5.0 - '@push.rocks/webrequest': 3.0.37 - '@push.rocks/webstore': 2.0.20 - '@tsclass/tsclass': 9.3.0 - '@types/express': 5.0.6 - body-parser: 2.2.2 - cors: 2.8.6 - express: 5.2.1 - express-force-ssl: 0.3.2 - lit: 3.3.2 - transitivePeerDependencies: - - '@nuxt/kit' - - '@push.rocks/smartserve' - - bufferutil - - react - - supports-color - - utf-8-validate - - vue - - '@api.global/typedsocket@3.1.1': - dependencies: - '@api.global/typedrequest': 3.2.6 - '@api.global/typedrequest-interfaces': 3.0.19 - '@push.rocks/isohash': 2.0.1 - '@push.rocks/smartjson': 5.2.0 - '@push.rocks/smartrx': 3.0.10 - '@push.rocks/smartsocket': 2.1.0 - '@push.rocks/smartstring': 4.1.0 - '@push.rocks/smarturl': 3.1.0 - transitivePeerDependencies: - - '@nuxt/kit' - - bufferutil - - react - - supports-color - - utf-8-validate - - vue - '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 @@ -4200,20 +3786,12 @@ snapshots: '@borewit/text-codec@0.2.1': {} - '@cloudflare/workers-types@4.20260305.0': {} - - '@colors/colors@1.6.0': {} + '@cfworker/json-schema@4.1.1': {} '@configvault.io/interfaces@1.0.17': dependencies: '@api.global/typedrequest-interfaces': 3.0.19 - '@dabh/diagnostics@2.0.8': - dependencies: - '@so-ric/colorspace': 1.1.6 - enabled: 2.0.0 - kuler: 2.0.0 - '@design.estate/dees-comms@1.0.30': dependencies: '@api.global/typedrequest': 3.2.6 @@ -4353,13 +3931,14 @@ snapshots: '@esbuild/win32-x64@0.27.3': optional: true - '@git.zone/tsbuild@2.7.3': + '@git.zone/tsbuild@4.3.0': dependencies: - '@git.zone/tspublish': 1.11.0 + '@git.zone/tspublish': 1.11.2 '@push.rocks/early': 4.0.4 '@push.rocks/smartcli': 4.0.20 '@push.rocks/smartdelay': 3.0.5 - '@push.rocks/smartfile': 11.2.7 + '@push.rocks/smartfile': 13.1.2 + '@push.rocks/smartfs': 1.5.0 '@push.rocks/smartlog': 3.2.1 '@push.rocks/smartpath': 6.0.0 '@push.rocks/smartpromise': 4.2.3 @@ -4373,13 +3952,13 @@ snapshots: - supports-color - vue - '@git.zone/tsbundle@2.9.0': + '@git.zone/tsbundle@2.9.1': dependencies: '@push.rocks/early': 4.0.4 '@push.rocks/npmextra': 5.3.3 '@push.rocks/smartcli': 4.0.20 '@push.rocks/smartdelay': 3.0.5 - '@push.rocks/smartfs': 1.3.1 + '@push.rocks/smartfs': 1.5.0 '@push.rocks/smartinteract': 2.0.16 '@push.rocks/smartlog': 3.2.1 '@push.rocks/smartlog-destination-local': 9.0.2 @@ -4399,19 +3978,19 @@ snapshots: - supports-color - vue - '@git.zone/tspublish@1.11.0': + '@git.zone/tspublish@1.11.2': dependencies: '@push.rocks/consolecolor': 2.0.3 '@push.rocks/npmextra': 5.3.3 '@push.rocks/smartcli': 4.0.20 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartfile': 13.1.2 - '@push.rocks/smartfs': 1.3.1 + '@push.rocks/smartfs': 1.5.0 '@push.rocks/smartlog': 3.2.1 '@push.rocks/smartnpm': 2.0.6 '@push.rocks/smartpath': 6.0.0 '@push.rocks/smartrequest': 5.0.1 - '@push.rocks/smartshell': 3.3.0 + '@push.rocks/smartshell': 3.3.8 transitivePeerDependencies: - '@nuxt/kit' - aws-crt @@ -4421,9 +4000,9 @@ snapshots: - supports-color - vue - '@git.zone/tsrun@1.6.2': + '@git.zone/tsrun@2.0.1': dependencies: - '@push.rocks/smartfile': 11.2.7 + '@push.rocks/smartfile': 13.1.2 '@push.rocks/smartshell': 3.3.0 tsx: 4.21.0 @@ -4442,28 +4021,31 @@ snapshots: - supports-color - vue - '@git.zone/tstest@1.11.5(socks@2.8.7)(typescript@5.9.3)': + '@git.zone/tstest@3.5.0(socks@2.8.7)(typescript@5.9.3)': dependencies: - '@api.global/typedserver': 3.0.80 - '@git.zone/tsbundle': 2.9.0 - '@git.zone/tsrun': 1.6.2 + '@git.zone/tsbundle': 2.9.1 + '@git.zone/tsrun': 2.0.1 '@push.rocks/consolecolor': 2.0.3 '@push.rocks/qenv': 6.1.3 - '@push.rocks/smartbrowser': 2.0.8(typescript@5.9.3) + '@push.rocks/smartbrowser': 2.0.11(typescript@5.9.3) '@push.rocks/smartcrypto': 2.0.4 '@push.rocks/smartdelay': 3.0.5 - '@push.rocks/smartenv': 5.0.13 + '@push.rocks/smartenv': 6.0.0 '@push.rocks/smartexpect': 2.5.0 - '@push.rocks/smartfile': 11.2.7 - '@push.rocks/smartjson': 5.2.0 + '@push.rocks/smartfile': 13.1.2 + '@push.rocks/smartfs': 1.5.0 + '@push.rocks/smartjson': 6.0.0 '@push.rocks/smartlog': 3.2.1 - '@push.rocks/smartmongo': 2.2.0(socks@2.8.7) - '@push.rocks/smartpath': 5.1.0 + '@push.rocks/smartmongo': 5.1.0(socks@2.8.7) + '@push.rocks/smartnetwork': 4.4.0 + '@push.rocks/smartpath': 6.0.0 '@push.rocks/smartpromise': 4.2.3 - '@push.rocks/smartrequest': 2.1.0 - '@push.rocks/smarts3': 2.2.7 - '@push.rocks/smartshell': 3.3.0 + '@push.rocks/smartrequest': 5.0.1 + '@push.rocks/smartserve': 2.0.1 + '@push.rocks/smartshell': 3.3.8 + '@push.rocks/smartstorage': 6.0.1 '@push.rocks/smarttime': 4.2.3 + '@push.rocks/smartwatch': 6.3.0 '@types/ws': 8.18.1 figures: 6.1.0 ws: 8.19.0 @@ -4471,7 +4053,6 @@ snapshots: - '@aws-sdk/credential-providers' - '@mongodb-js/zstd' - '@nuxt/kit' - - '@push.rocks/smartserve' - '@swc/helpers' - aws-crt - bare-abort-controller @@ -4489,9 +4070,101 @@ snapshots: - utf-8-validate - vue - '@happy-dom/global-registrator@15.11.7': + '@img/colour@1.1.0': {} + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': dependencies: - happy-dom: 15.11.7 + '@emnapi/runtime': 1.8.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true '@inquirer/checkbox@3.0.1': dependencies: @@ -4591,15 +4264,194 @@ snapshots: '@isaacs/cliui@9.0.0': {} - '@koa/router@9.4.0': + '@jimp/core@1.6.0': dependencies: - debug: 4.4.3 - http-errors: 1.8.1 - koa-compose: 4.1.0 - methods: 1.1.2 - path-to-regexp: 6.3.0 - transitivePeerDependencies: - - supports-color + '@jimp/file-ops': 1.6.0 + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + await-to-js: 3.0.0 + exif-parser: 0.1.12 + file-type: 16.5.4 + mime: 3.0.0 + + '@jimp/diff@1.6.0': + dependencies: + '@jimp/plugin-resize': 1.6.0 + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + pixelmatch: 5.3.0 + + '@jimp/file-ops@1.6.0': {} + + '@jimp/js-bmp@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + bmp-ts: 1.0.9 + + '@jimp/js-gif@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/types': 1.6.0 + gifwrap: 0.10.1 + omggif: 1.0.10 + + '@jimp/js-jpeg@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/types': 1.6.0 + jpeg-js: 0.4.4 + + '@jimp/js-png@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/types': 1.6.0 + pngjs: 7.0.0 + + '@jimp/js-tiff@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/types': 1.6.0 + utif2: 4.1.0 + + '@jimp/plugin-blit@1.6.0': + dependencies: + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-blur@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/utils': 1.6.0 + + '@jimp/plugin-circle@1.6.0': + dependencies: + '@jimp/types': 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-color@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + tinycolor2: 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-contain@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/plugin-blit': 1.6.0 + '@jimp/plugin-resize': 1.6.0 + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-cover@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/plugin-crop': 1.6.0 + '@jimp/plugin-resize': 1.6.0 + '@jimp/types': 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-crop@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-displace@1.6.0': + dependencies: + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-dither@1.6.0': + dependencies: + '@jimp/types': 1.6.0 + + '@jimp/plugin-fisheye@1.6.0': + dependencies: + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-flip@1.6.0': + dependencies: + '@jimp/types': 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-hash@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/js-bmp': 1.6.0 + '@jimp/js-jpeg': 1.6.0 + '@jimp/js-png': 1.6.0 + '@jimp/js-tiff': 1.6.0 + '@jimp/plugin-color': 1.6.0 + '@jimp/plugin-resize': 1.6.0 + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + any-base: 1.1.0 + + '@jimp/plugin-mask@1.6.0': + dependencies: + '@jimp/types': 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-print@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/js-jpeg': 1.6.0 + '@jimp/js-png': 1.6.0 + '@jimp/plugin-blit': 1.6.0 + '@jimp/types': 1.6.0 + parse-bmfont-ascii: 1.0.6 + parse-bmfont-binary: 1.0.6 + parse-bmfont-xml: 1.1.6 + simple-xml-to-json: 1.2.4 + zod: 3.25.76 + + '@jimp/plugin-quantize@1.6.0': + dependencies: + image-q: 4.0.0 + zod: 3.25.76 + + '@jimp/plugin-resize@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/types': 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-rotate@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/plugin-crop': 1.6.0 + '@jimp/plugin-resize': 1.6.0 + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + zod: 3.25.76 + + '@jimp/plugin-threshold@1.6.0': + dependencies: + '@jimp/core': 1.6.0 + '@jimp/plugin-color': 1.6.0 + '@jimp/plugin-hash': 1.6.0 + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + zod: 3.25.76 + + '@jimp/types@1.6.0': + dependencies: + zod: 3.25.76 + + '@jimp/utils@1.6.0': + dependencies: + '@jimp/types': 1.6.0 + tinycolor2: 1.6.0 '@lit-labs/ssr-dom-shim@1.5.1': {} @@ -4788,11 +4640,6 @@ snapshots: '@push.rocks/consolecolor': 2.0.3 '@push.rocks/smartpromise': 4.2.3 - '@push.rocks/isohash@2.0.1': - dependencies: - '@pushrocks/smartenv': 5.0.5 - '@pushrocks/smarthash': 3.0.2 - '@push.rocks/isounique@1.0.5': {} '@push.rocks/levelcache@3.2.0': @@ -4891,20 +4738,24 @@ snapshots: - react-native-b4a - supports-color - '@push.rocks/smartbrowser@2.0.8(typescript@5.9.3)': + '@push.rocks/smartbrowser@2.0.11(typescript@5.9.3)': dependencies: '@push.rocks/smartdelay': 3.0.5 - '@push.rocks/smartpdf': 3.3.0(typescript@5.9.3) + '@push.rocks/smartpdf': 4.2.0(typescript@5.9.3) '@push.rocks/smartpuppeteer': 2.0.5(typescript@5.9.3) '@push.rocks/smartunique': 3.0.9 transitivePeerDependencies: + - '@nuxt/kit' + - aws-crt - bare-abort-controller - bare-buffer - bufferutil + - react - react-native-b4a - supports-color - typescript - utf-8-validate + - vue '@push.rocks/smartbucket@3.3.10': dependencies: @@ -4932,14 +4783,6 @@ snapshots: '@push.rocks/smartpromise': 4.2.3 '@push.rocks/smarttime': 4.2.3 - '@push.rocks/smartchok@1.2.0': - dependencies: - '@push.rocks/lik': 6.2.2 - '@push.rocks/smartpromise': 4.2.3 - '@push.rocks/smartrx': 3.0.10 - chokidar: 5.0.0 - picomatch: 4.0.3 - '@push.rocks/smartcli@4.0.20': dependencies: '@push.rocks/lik': 6.2.2 @@ -5002,7 +4845,7 @@ snapshots: '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartenv': 6.0.0 '@push.rocks/smartpromise': 4.2.3 - '@push.rocks/smartrust': 1.3.1 + '@push.rocks/smartrust': 1.3.2 '@tsclass/tsclass': 9.3.0 acme-client: 5.4.0 minimatch: 10.2.4 @@ -5029,17 +4872,17 @@ snapshots: '@push.rocks/smartpromise': 4.2.3 tree-kill: 1.2.2 + '@push.rocks/smartexit@2.0.3': + dependencies: + '@push.rocks/lik': 6.2.2 + '@push.rocks/smartpromise': 4.2.3 + '@push.rocks/smartexpect@2.5.0': dependencies: '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartpromise': 4.2.3 fast-deep-equal: 3.1.3 - '@push.rocks/smartfeed@1.4.0': - dependencies: - '@tsclass/tsclass': 9.3.0 - fast-xml-parser: 4.5.4 - '@push.rocks/smartfile-interfaces@1.0.7': {} '@push.rocks/smartfile@11.2.7': @@ -5065,7 +4908,7 @@ snapshots: '@push.rocks/lik': 6.2.2 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartfile-interfaces': 1.0.7 - '@push.rocks/smartfs': 1.3.1 + '@push.rocks/smartfs': 1.5.0 '@push.rocks/smarthash': 3.2.6 '@push.rocks/smartjson': 5.2.0 '@push.rocks/smartmime': 2.0.4 @@ -5077,9 +4920,10 @@ snapshots: glob: 11.1.0 js-yaml: 4.1.1 - '@push.rocks/smartfs@1.3.1': + '@push.rocks/smartfs@1.5.0': dependencies: '@push.rocks/smartpath': 6.0.0 + '@push.rocks/smartrust': 1.3.2 '@push.rocks/smartguard@3.1.0': dependencies: @@ -5101,6 +4945,22 @@ snapshots: '@push.rocks/smartpromise': 4.2.3 inquirer: 11.1.0 + '@push.rocks/smartjimp@1.2.0': + dependencies: + '@push.rocks/levelcache': 3.2.0 + '@push.rocks/smartfile': 11.2.7 + '@push.rocks/smarthash': 3.2.6 + '@push.rocks/smartpath': 6.0.0 + '@push.rocks/smartrequest': 4.4.2 + jimp: 1.6.0 + sharp: 0.34.5 + transitivePeerDependencies: + - '@nuxt/kit' + - aws-crt + - react + - supports-color + - vue + '@push.rocks/smartjson@5.2.0': dependencies: '@push.rocks/smartenv': 5.0.13 @@ -5115,10 +4975,6 @@ snapshots: fast-json-stable-stringify: 2.1.0 lodash.clonedeep: 4.5.0 - '@push.rocks/smartlog-destination-devtools@1.0.12': - dependencies: - '@push.rocks/smartlog-interfaces': 3.0.2 - '@push.rocks/smartlog-destination-local@9.0.2': dependencies: '@push.rocks/consolecolor': 2.0.3 @@ -5143,8 +4999,6 @@ snapshots: '@push.rocks/webrequest': 4.0.2 '@tsclass/tsclass': 9.3.0 - '@push.rocks/smartmanifest@2.0.2': {} - '@push.rocks/smartmarkdown@3.0.3': dependencies: '@push.rocks/smartyaml': 2.0.5 @@ -5174,7 +5028,33 @@ snapshots: dependencies: '@push.rocks/mongodump': 1.1.0(socks@2.8.7) '@push.rocks/smartdata': 5.16.7(socks@2.8.7) - '@push.rocks/smartfs': 1.3.1 + '@push.rocks/smartfs': 1.5.0 + '@push.rocks/smartpath': 5.1.0 + '@push.rocks/smartpromise': 4.2.3 + '@push.rocks/smartrx': 3.0.10 + bson: 6.10.4 + mingo: 7.2.0 + mongodb-memory-server: 10.4.3(socks@2.8.7) + transitivePeerDependencies: + - '@aws-sdk/credential-providers' + - '@mongodb-js/zstd' + - '@nuxt/kit' + - bare-abort-controller + - gcp-metadata + - kerberos + - mongodb-client-encryption + - react + - react-native-b4a + - snappy + - socks + - supports-color + - vue + + '@push.rocks/smartmongo@5.1.0(socks@2.8.7)': + dependencies: + '@push.rocks/mongodump': 1.1.0(socks@2.8.7) + '@push.rocks/smartdata': 5.16.7(socks@2.8.7) + '@push.rocks/smartfs': 1.5.0 '@push.rocks/smartpath': 5.1.0 '@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartrx': 3.0.10 @@ -5228,54 +5108,42 @@ snapshots: - supports-color - vue - '@push.rocks/smartntml@2.0.8': - dependencies: - '@design.estate/dees-element': 2.1.6 - '@happy-dom/global-registrator': 15.11.7 - '@push.rocks/smartpromise': 4.2.3 - fake-indexeddb: 6.2.5 - transitivePeerDependencies: - - '@nuxt/kit' - - react - - supports-color - - vue - '@push.rocks/smartobject@1.0.12': dependencies: fast-deep-equal: 3.1.3 minimatch: 9.0.8 - '@push.rocks/smartopen@2.0.0': - dependencies: - open: 8.4.2 - '@push.rocks/smartpath@5.1.0': {} '@push.rocks/smartpath@6.0.0': {} - '@push.rocks/smartpdf@3.3.0(typescript@5.9.3)': + '@push.rocks/smartpdf@4.2.0(typescript@5.9.3)': dependencies: '@push.rocks/smartbuffer': 3.0.5 '@push.rocks/smartdelay': 3.0.5 - '@push.rocks/smartfile': 11.2.7 + '@push.rocks/smartfs': 1.5.0 + '@push.rocks/smartjimp': 1.2.0 '@push.rocks/smartnetwork': 4.4.0 '@push.rocks/smartpath': 6.0.0 '@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartpuppeteer': 2.0.5(typescript@5.9.3) + '@push.rocks/smartserve': 2.0.1 '@push.rocks/smartunique': 3.0.9 '@tsclass/tsclass': 9.3.0 - '@types/express': 5.0.6 - express: 5.2.1 pdf-lib: 1.17.1 - pdf2json: 3.2.0 + pdf2json: 4.0.2 transitivePeerDependencies: + - '@nuxt/kit' + - aws-crt - bare-abort-controller - bare-buffer - bufferutil + - react - react-native-b4a - supports-color - typescript - utf-8-validate + - vue '@push.rocks/smartping@1.0.8': dependencies: @@ -5287,7 +5155,7 @@ snapshots: '@push.rocks/smartpuppeteer@2.0.5(typescript@5.9.3)': dependencies: '@push.rocks/smartdelay': 3.0.5 - '@push.rocks/smartshell': 3.3.0 + '@push.rocks/smartshell': 3.3.8 puppeteer: 24.37.5(typescript@5.9.3) tree-kill: 1.2.2 transitivePeerDependencies: @@ -5330,7 +5198,7 @@ snapshots: '@push.rocks/smartrx': 3.0.10 path-to-regexp: 8.3.0 - '@push.rocks/smartrust@1.3.1': + '@push.rocks/smartrust@1.3.2': dependencies: '@push.rocks/smartpath': 6.0.0 @@ -5339,17 +5207,18 @@ snapshots: '@push.rocks/smartpromise': 4.2.3 rxjs: 7.8.2 - '@push.rocks/smarts3@2.2.7': + '@push.rocks/smartserve@2.0.1': dependencies: - '@push.rocks/smartbucket': 3.3.10 - '@push.rocks/smartfile': 11.2.7 + '@api.global/typedrequest': 3.2.6 + '@cfworker/json-schema': 4.1.1 + '@push.rocks/lik': 6.2.2 + '@push.rocks/smartenv': 6.0.0 + '@push.rocks/smartlog': 3.2.1 '@push.rocks/smartpath': 6.0.0 - '@tsclass/tsclass': 9.3.0 - '@types/s3rver': 3.7.4 - s3rver: 3.7.1 + ws: 8.19.0 transitivePeerDependencies: - - aws-crt - - supports-color + - bufferutil + - utf-8-validate '@push.rocks/smartshell@3.3.0': dependencies: @@ -5360,39 +5229,13 @@ snapshots: tree-kill: 1.2.2 which: 5.0.0 - '@push.rocks/smartsitemap@2.0.4': + '@push.rocks/smartshell@3.3.8': dependencies: - '@push.rocks/smartcache': 1.0.18 - '@push.rocks/smartfeed': 1.4.0 - '@push.rocks/smartxml': 2.0.0 - '@push.rocks/smartyaml': 3.0.4 - '@push.rocks/webrequest': 4.0.2 - '@tsclass/tsclass': 9.3.0 - - '@push.rocks/smartsocket@2.1.0': - dependencies: - '@api.global/typedrequest-interfaces': 3.0.19 - '@api.global/typedserver': 3.0.80 - '@push.rocks/isohash': 2.0.1 - '@push.rocks/isounique': 1.0.5 - '@push.rocks/lik': 6.2.2 '@push.rocks/smartdelay': 3.0.5 - '@push.rocks/smartenv': 5.0.13 - '@push.rocks/smartjson': 5.2.0 - '@push.rocks/smartlog': 3.2.1 + '@push.rocks/smartexit': 2.0.3 '@push.rocks/smartpromise': 4.2.3 - '@push.rocks/smartrx': 3.0.10 - '@push.rocks/smarttime': 4.2.3 - engine.io: 6.6.4 - socket.io: 4.8.1 - socket.io-client: 4.8.1 - transitivePeerDependencies: - - '@nuxt/kit' - - bufferutil - - react - - supports-color - - utf-8-validate - - vue + '@types/which': 3.0.4 + which: 6.0.1 '@push.rocks/smartspawn@3.0.3': dependencies: @@ -5412,6 +5255,12 @@ snapshots: '@push.rocks/smartrx': 3.0.10 '@push.rocks/webstore': 2.0.20 + '@push.rocks/smartstorage@6.0.1': + dependencies: + '@push.rocks/smartpath': 6.0.0 + '@push.rocks/smartrust': 1.3.2 + '@tsclass/tsclass': 9.3.0 + '@push.rocks/smartstream@3.2.5': dependencies: '@push.rocks/lik': 6.2.2 @@ -5447,19 +5296,20 @@ snapshots: '@types/semver': 7.7.1 semver: 7.7.4 - '@push.rocks/smartxml@2.0.0': + '@push.rocks/smartwatch@6.3.0': dependencies: - fast-xml-parser: 5.4.1 + '@push.rocks/lik': 6.2.2 + '@push.rocks/smartenv': 6.0.0 + '@push.rocks/smartpromise': 4.2.3 + '@push.rocks/smartrx': 3.0.10 + chokidar: 5.0.0 + picomatch: 4.0.3 '@push.rocks/smartyaml@2.0.5': dependencies: '@types/js-yaml': 3.12.10 js-yaml: 3.14.2 - '@push.rocks/smartyaml@3.0.4': - dependencies: - yaml: 2.8.2 - '@push.rocks/taskbuffer@3.5.0': dependencies: '@design.estate/dees-element': 2.1.6 @@ -5513,48 +5363,12 @@ snapshots: dependencies: '@push.rocks/smartenv': 5.0.13 - '@pushrocks/isounique@1.0.5': {} - '@pushrocks/smartdelay@3.0.1': dependencies: '@pushrocks/smartpromise': 4.0.2 - '@pushrocks/smartenv@5.0.5': - dependencies: - '@pushrocks/smartpromise': 3.1.10 - - '@pushrocks/smarthash@3.0.2': - dependencies: - '@pushrocks/smartjson': 5.0.6 - '@pushrocks/smartpromise': 3.1.10 - '@types/through2': 2.0.41 - through2: 4.0.2 - - '@pushrocks/smartjson@5.0.6': - dependencies: - '@pushrocks/smartstring': 4.0.7 - '@types/buffer-json': 2.0.3 - buffer-json: 2.0.0 - fast-json-stable-stringify: 2.1.0 - lodash.clonedeep: 4.5.0 - - '@pushrocks/smartpromise@3.1.10': {} - '@pushrocks/smartpromise@4.0.2': {} - '@pushrocks/smartstring@4.0.7': - dependencies: - '@pushrocks/isounique': 1.0.5 - '@pushrocks/smartenv': 5.0.5 - '@types/randomatic': 3.1.5 - buffer: 6.0.3 - crypto-random-string: 5.0.0 - js-base64: 3.7.8 - normalize-newline: 4.1.0 - randomatic: 3.1.1 - strip-indent: 4.1.1 - url: 0.11.4 - '@rolldown/binding-android-arm64@1.0.0-beta.52': optional: true @@ -5996,13 +5810,6 @@ snapshots: dependencies: tslib: 2.8.1 - '@so-ric/colorspace@1.1.6': - dependencies: - color: 5.0.3 - text-hex: 1.0.0 - - '@socket.io/component-emitter@3.1.2': {} - '@szmarczak/http-timer@5.0.1': dependencies: defer-to-connect: 2.0.1 @@ -6035,43 +5842,15 @@ snapshots: tslib: 2.8.1 optional: true - '@types/body-parser@1.19.6': - dependencies: - '@types/connect': 3.4.38 - '@types/node': 22.19.12 - - '@types/buffer-json@2.0.3': {} - '@types/clean-css@4.2.11': dependencies: '@types/node': 22.19.12 source-map: 0.6.1 - '@types/connect@3.4.38': - dependencies: - '@types/node': 22.19.12 - - '@types/cors@2.8.19': - dependencies: - '@types/node': 22.19.12 - '@types/debug@4.1.12': dependencies: '@types/ms': 2.1.0 - '@types/express-serve-static-core@5.1.1': - dependencies: - '@types/node': 22.19.12 - '@types/qs': 6.14.0 - '@types/range-parser': 1.2.7 - '@types/send': 1.2.1 - - '@types/express@5.0.6': - dependencies: - '@types/body-parser': 1.19.6 - '@types/express-serve-static-core': 5.1.1 - '@types/serve-static': 2.2.0 - '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 @@ -6089,8 +5868,6 @@ snapshots: '@types/http-cache-semantics@4.2.0': {} - '@types/http-errors@2.0.5': {} - '@types/js-yaml@3.12.10': {} '@types/js-yaml@4.0.9': {} @@ -6117,35 +5894,22 @@ snapshots: dependencies: '@types/node': 22.19.12 + '@types/node@16.9.1': {} + '@types/node@22.19.12': dependencies: undici-types: 6.21.0 + '@types/node@25.5.0': + dependencies: + undici-types: 7.18.2 + '@types/ping@0.4.4': {} - '@types/qs@6.14.0': {} - - '@types/randomatic@3.1.5': {} - - '@types/range-parser@1.2.7': {} - '@types/relateurl@0.2.33': {} - '@types/s3rver@3.7.4': - dependencies: - '@types/node': 22.19.12 - '@types/semver@7.7.1': {} - '@types/send@1.2.1': - dependencies: - '@types/node': 22.19.12 - - '@types/serve-static@2.2.0': - dependencies: - '@types/http-errors': 2.0.5 - '@types/node': 22.19.12 - '@types/symbol-tree@3.2.5': {} '@types/tar-stream@3.1.4': @@ -6156,8 +5920,6 @@ snapshots: dependencies: '@types/node': 22.19.12 - '@types/triple-beam@1.3.5': {} - '@types/trusted-types@2.0.7': {} '@types/turndown@5.0.6': {} @@ -6191,15 +5953,9 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - accepts@1.3.8: + abort-controller@3.0.0: dependencies: - mime-types: 2.1.35 - negotiator: 0.6.3 - - accepts@2.0.0: - dependencies: - mime-types: 3.0.2 - negotiator: 1.0.0 + event-target-shim: 5.0.1 acme-client@5.4.0: dependencies: @@ -6225,14 +5981,12 @@ snapshots: ansi-regex@5.0.1: {} - ansi-styles@3.2.1: - dependencies: - color-convert: 1.9.3 - ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 + any-base@1.1.0: {} + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -6253,10 +6007,10 @@ snapshots: dependencies: tslib: 2.8.1 - async@3.2.6: {} - asynckit@0.4.0: {} + await-to-js@3.0.0: {} + axios@1.13.5(debug@4.4.3): dependencies: follow-redirects: 1.15.11(debug@4.4.3) @@ -6313,23 +6067,9 @@ snapshots: base64-js@1.5.1: {} - base64id@2.0.0: {} - basic-ftp@5.2.0: {} - body-parser@2.2.2: - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 4.4.3 - http-errors: 2.0.1 - iconv-lite: 0.7.2 - on-finished: 2.4.1 - qs: 6.15.0 - raw-body: 3.0.2 - type-is: 2.0.1 - transitivePeerDependencies: - - supports-color + bmp-ts@1.0.9: {} bowser@2.14.1: {} @@ -6353,24 +6093,11 @@ snapshots: buffer-crc32@0.2.13: {} - buffer-json@2.0.0: {} - buffer@6.0.3: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 - busboy@0.3.1: - dependencies: - dicer: 0.3.0 - - bytes@3.1.2: {} - - cache-content-type@1.0.1: - dependencies: - mime-types: 2.1.35 - ylru: 1.4.0 - cacheable-lookup@7.0.0: {} cacheable-request@10.2.14: @@ -6395,11 +6122,6 @@ snapshots: get-intrinsic: 1.3.0 set-function-length: 1.2.2 - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 - callsites@3.1.0: {} camel-case@3.0.0: @@ -6411,12 +6133,6 @@ snapshots: ccount@2.0.1: {} - chalk@2.4.2: - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - character-entities-html4@2.1.0: {} character-entities-legacy@3.0.0: {} @@ -6449,35 +6165,12 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - co@4.6.0: {} - - color-convert@1.9.3: - dependencies: - color-name: 1.1.3 - color-convert@2.0.1: dependencies: color-name: 1.1.4 - color-convert@3.1.3: - dependencies: - color-name: 2.1.0 - - color-name@1.1.3: {} - color-name@1.1.4: {} - color-name@2.1.0: {} - - color-string@2.1.4: - dependencies: - color-name: 2.1.0 - - color@5.0.3: - dependencies: - color-convert: 3.1.3 - color-string: 2.1.4 - combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -6486,8 +6179,6 @@ snapshots: commander@2.20.3: {} - commander@5.1.0: {} - commondir@1.0.1: {} concat-map@0.0.1: {} @@ -6497,28 +6188,6 @@ snapshots: ini: 1.3.8 proto-list: 1.2.4 - content-disposition@0.5.4: - dependencies: - safe-buffer: 5.2.1 - - content-disposition@1.0.1: {} - - content-type@1.0.5: {} - - cookie-signature@1.2.2: {} - - cookie@0.7.2: {} - - cookies@0.9.1: - dependencies: - depd: 2.0.0 - keygrip: 1.1.0 - - cors@2.8.6: - dependencies: - object-assign: 4.1.1 - vary: 1.1.2 - cosmiconfig@9.0.0(typescript@5.9.3): dependencies: env-paths: 2.2.1 @@ -6536,20 +6205,12 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - crypto-random-string@5.0.0: - dependencies: - type-fest: 2.19.0 - data-uri-to-buffer@6.0.2: {} date-fns@4.1.0: {} dayjs@1.11.19: {} - debug@4.3.7: - dependencies: - ms: 2.1.3 - debug@4.4.3: dependencies: ms: 2.1.3 @@ -6562,8 +6223,6 @@ snapshots: dependencies: mimic-response: 3.1.0 - deep-equal@1.0.1: {} - deep-extend@0.6.0: {} defer-to-connect@2.0.1: {} @@ -6574,8 +6233,6 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - define-lazy-prop@2.0.0: {} - define-properties@1.2.1: dependencies: define-data-property: 1.1.4 @@ -6590,15 +6247,9 @@ snapshots: delayed-stream@1.0.0: {} - delegates@1.0.0: {} - - depd@1.1.2: {} - - depd@2.0.0: {} - dequal@2.0.3: {} - destroy@1.2.0: {} + detect-libc@2.1.2: {} devlop@1.1.0: dependencies: @@ -6606,62 +6257,18 @@ snapshots: devtools-protocol@0.0.1566079: {} - dicer@0.3.0: - dependencies: - streamsearch: 0.1.2 - dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 es-errors: 1.3.0 gopd: 1.2.0 - ee-first@1.1.1: {} - emoji-regex@8.0.0: {} - enabled@2.0.0: {} - - encodeurl@1.0.2: {} - - encodeurl@2.0.0: {} - end-of-stream@1.4.5: dependencies: once: 1.4.0 - engine.io-client@6.6.4: - dependencies: - '@socket.io/component-emitter': 3.1.2 - debug: 4.4.3 - engine.io-parser: 5.2.3 - ws: 8.18.3 - xmlhttprequest-ssl: 2.1.2 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - engine.io-parser@5.2.3: {} - - engine.io@6.6.4: - dependencies: - '@types/cors': 2.8.19 - '@types/node': 22.19.12 - accepts: 1.3.8 - base64id: 2.0.0 - cookie: 0.7.2 - cors: 2.8.6 - debug: 4.3.7 - engine.io-parser: 5.2.3 - ws: 8.17.1 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - entities@4.5.0: {} - env-paths@2.2.1: {} error-ex@1.3.4: @@ -6714,10 +6321,6 @@ snapshots: escalade@3.2.0: {} - escape-html@1.0.3: {} - - escape-string-regexp@1.0.5: {} - escape-string-regexp@5.0.0: {} escodegen@2.1.0: @@ -6736,7 +6339,7 @@ snapshots: esutils@2.0.3: {} - etag@1.8.1: {} + event-target-shim@5.0.1: {} eventemitter3@4.0.7: {} @@ -6746,42 +6349,9 @@ snapshots: transitivePeerDependencies: - bare-abort-controller - express-force-ssl@0.3.2: - dependencies: - lodash.assign: 3.2.0 + events@3.3.0: {} - express@5.2.1: - dependencies: - accepts: 2.0.0 - body-parser: 2.2.2 - content-disposition: 1.0.1 - content-type: 1.0.5 - cookie: 0.7.2 - cookie-signature: 1.2.2 - debug: 4.4.3 - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 2.1.1 - fresh: 2.0.0 - http-errors: 2.0.1 - merge-descriptors: 2.0.0 - mime-types: 3.0.2 - on-finished: 2.4.1 - once: 1.4.0 - parseurl: 1.3.3 - proxy-addr: 2.0.7 - qs: 6.15.0 - range-parser: 1.2.1 - router: 2.2.0 - send: 1.2.1 - serve-static: 2.2.1 - statuses: 2.0.2 - type-is: 2.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color + exif-parser@0.1.12: {} extend@3.0.2: {} @@ -6803,33 +6373,16 @@ snapshots: fake-indexeddb@5.0.2: {} - fake-indexeddb@6.2.5: {} - fast-deep-equal@3.1.3: {} fast-fifo@1.3.2: {} fast-json-stable-stringify@2.1.0: {} - fast-xml-builder@1.0.0: {} - - fast-xml-parser@3.21.1: - dependencies: - strnum: 1.1.2 - - fast-xml-parser@4.5.4: - dependencies: - strnum: 1.1.2 - fast-xml-parser@5.3.6: dependencies: strnum: 2.1.2 - fast-xml-parser@5.4.1: - dependencies: - fast-xml-builder: 1.0.0 - strnum: 2.1.2 - fault@2.0.1: dependencies: format: 0.2.2 @@ -6838,14 +6391,18 @@ snapshots: dependencies: pend: 1.2.0 - fecha@4.2.3: {} - fflate@0.8.2: {} figures@6.1.0: dependencies: is-unicode-supported: 2.1.0 + file-type@16.5.4: + dependencies: + readable-web-to-node-stream: 3.0.4 + strtok3: 6.3.0 + token-types: 4.2.1 + file-type@19.6.0: dependencies: get-stream: 9.0.1 @@ -6862,17 +6419,6 @@ snapshots: transitivePeerDependencies: - supports-color - finalhandler@2.1.1: - dependencies: - debug: 4.4.3 - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - find-cache-dir@3.3.2: dependencies: commondir: 1.0.1 @@ -6884,8 +6430,6 @@ snapshots: locate-path: 5.0.0 path-exists: 4.0.0 - fn.name@1.1.0: {} - follow-redirects@1.15.11(debug@4.4.3): optionalDependencies: debug: 4.4.3 @@ -6912,24 +6456,12 @@ snapshots: format@0.2.2: {} - forwarded@0.2.0: {} - - fresh@0.5.2: {} - - fresh@2.0.0: {} - fs-extra@11.3.3: dependencies: graceful-fs: 4.2.11 jsonfile: 6.2.0 universalify: 2.0.1 - fs-extra@8.1.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 4.0.0 - universalify: 0.1.2 - fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -6937,8 +6469,6 @@ snapshots: function-bind@1.1.2: {} - generator-function@2.0.1: {} - get-caller-file@2.0.5: {} get-intrinsic@1.3.0: @@ -6982,6 +6512,11 @@ snapshots: transitivePeerDependencies: - supports-color + gifwrap@0.10.1: + dependencies: + image-q: 4.0.0 + omggif: 1.0.10 + glob@11.1.0: dependencies: foreground-child: 3.3.1 @@ -7020,14 +6555,6 @@ snapshots: graceful-fs@4.2.11: {} - happy-dom@15.11.7: - dependencies: - entities: 4.5.0 - webidl-conversions: 7.0.0 - whatwg-mimetype: 3.0.0 - - has-flag@3.0.0: {} - has-property-descriptors@1.0.2: dependencies: es-define-property: 1.0.1 @@ -7080,29 +6607,8 @@ snapshots: html-void-elements@3.0.0: {} - http-assert@1.5.0: - dependencies: - deep-equal: 1.0.1 - http-errors: 1.8.1 - http-cache-semantics@4.2.0: {} - http-errors@1.8.1: - dependencies: - depd: 1.1.2 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 1.5.0 - toidentifier: 1.0.1 - - http-errors@2.0.1: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.2 - toidentifier: 1.0.1 - http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 @@ -7126,18 +6632,16 @@ snapshots: dependencies: ms: 2.1.3 - humanize-number@0.0.2: {} - iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 - iconv-lite@0.7.2: - dependencies: - safer-buffer: 2.1.2 - ieee754@1.2.1: {} + image-q@4.0.0: + dependencies: + '@types/node': 16.9.1 + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -7165,65 +6669,68 @@ snapshots: ip-address@10.1.0: {} - ipaddr.js@1.9.1: {} - is-arrayish@0.2.1: {} - is-docker@2.2.1: {} - is-fullwidth-code-point@3.0.0: {} - is-generator-function@1.1.2: - dependencies: - call-bound: 1.0.4 - generator-function: 2.0.1 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 - is-nan@1.3.2: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - is-number@4.0.0: {} - is-observable@2.1.0: {} is-plain-obj@4.1.0: {} - is-promise@4.0.0: {} - - is-regex@1.2.1: - dependencies: - call-bound: 1.0.4 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - - is-stream@2.0.1: {} - is-stream@4.0.1: {} is-unicode-supported@2.1.0: {} is-windows@1.0.2: {} - is-wsl@2.2.0: - dependencies: - is-docker: 2.2.1 - isexe@2.0.0: {} isexe@3.1.5: {} + isexe@4.0.0: {} + isopen@1.3.0: {} jackspeak@4.2.3: dependencies: '@isaacs/cliui': 9.0.0 - js-base64@3.7.8: {} + jimp@1.6.0: + dependencies: + '@jimp/core': 1.6.0 + '@jimp/diff': 1.6.0 + '@jimp/js-bmp': 1.6.0 + '@jimp/js-gif': 1.6.0 + '@jimp/js-jpeg': 1.6.0 + '@jimp/js-png': 1.6.0 + '@jimp/js-tiff': 1.6.0 + '@jimp/plugin-blit': 1.6.0 + '@jimp/plugin-blur': 1.6.0 + '@jimp/plugin-circle': 1.6.0 + '@jimp/plugin-color': 1.6.0 + '@jimp/plugin-contain': 1.6.0 + '@jimp/plugin-cover': 1.6.0 + '@jimp/plugin-crop': 1.6.0 + '@jimp/plugin-displace': 1.6.0 + '@jimp/plugin-dither': 1.6.0 + '@jimp/plugin-fisheye': 1.6.0 + '@jimp/plugin-flip': 1.6.0 + '@jimp/plugin-hash': 1.6.0 + '@jimp/plugin-mask': 1.6.0 + '@jimp/plugin-print': 1.6.0 + '@jimp/plugin-quantize': 1.6.0 + '@jimp/plugin-resize': 1.6.0 + '@jimp/plugin-rotate': 1.6.0 + '@jimp/plugin-threshold': 1.6.0 + '@jimp/types': 1.6.0 + '@jimp/utils': 1.6.0 + + jpeg-js@0.4.4: {} js-tokens@4.0.0: {} @@ -7240,70 +6747,16 @@ snapshots: json-parse-even-better-errors@2.3.1: {} - jsonfile@4.0.0: - optionalDependencies: - graceful-fs: 4.2.11 - jsonfile@6.2.0: dependencies: universalify: 2.0.1 optionalDependencies: graceful-fs: 4.2.11 - keygrip@1.1.0: - dependencies: - tsscmp: 1.0.6 - keyv@4.5.4: dependencies: json-buffer: 3.0.1 - kind-of@6.0.3: {} - - koa-compose@4.1.0: {} - - koa-convert@2.0.0: - dependencies: - co: 4.6.0 - koa-compose: 4.1.0 - - koa-logger@3.2.1: - dependencies: - bytes: 3.1.2 - chalk: 2.4.2 - humanize-number: 0.0.2 - passthrough-counter: 1.0.0 - - koa@2.16.4: - dependencies: - accepts: 1.3.8 - cache-content-type: 1.0.1 - content-disposition: 0.5.4 - content-type: 1.0.5 - cookies: 0.9.1 - debug: 4.4.3 - delegates: 1.0.0 - depd: 2.0.0 - destroy: 1.2.0 - encodeurl: 1.0.2 - escape-html: 1.0.3 - fresh: 0.5.2 - http-assert: 1.5.0 - http-errors: 1.8.1 - is-generator-function: 1.1.2 - koa-compose: 4.1.0 - koa-convert: 2.0.0 - on-finished: 2.4.1 - only: 0.0.2 - parseurl: 1.3.3 - statuses: 1.5.0 - type-is: 1.6.18 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - - kuler@2.0.0: {} - lines-and-columns@1.2.4: {} lit-element@4.2.2: @@ -7326,56 +6779,8 @@ snapshots: dependencies: p-locate: 4.1.0 - lodash._baseassign@3.2.0: - dependencies: - lodash._basecopy: 3.0.1 - lodash.keys: 3.1.2 - - lodash._basecopy@3.0.1: {} - - lodash._bindcallback@3.0.1: {} - - lodash._createassigner@3.1.1: - dependencies: - lodash._bindcallback: 3.0.1 - lodash._isiterateecall: 3.0.9 - lodash.restparam: 3.6.1 - - lodash._getnative@3.9.1: {} - - lodash._isiterateecall@3.0.9: {} - - lodash.assign@3.2.0: - dependencies: - lodash._baseassign: 3.2.0 - lodash._createassigner: 3.1.1 - lodash.keys: 3.1.2 - lodash.clonedeep@4.5.0: {} - lodash.isarguments@3.1.0: {} - - lodash.isarray@3.0.4: {} - - lodash.keys@3.1.2: - dependencies: - lodash._getnative: 3.9.1 - lodash.isarguments: 3.1.0 - lodash.isarray: 3.0.4 - - lodash.restparam@3.6.1: {} - - lodash@4.17.23: {} - - logform@2.7.0: - dependencies: - '@colors/colors': 1.6.0 - '@types/triple-beam': 1.3.5 - fecha: 4.2.3 - ms: 2.1.3 - safe-stable-stringify: 2.5.0 - triple-beam: 1.4.1 - longest-streak@3.1.0: {} lower-case@1.1.4: {} @@ -7404,8 +6809,6 @@ snapshots: math-intrinsics@1.1.0: {} - math-random@1.0.4: {} - mdast-util-find-and-replace@3.0.2: dependencies: '@types/mdast': 4.0.4 @@ -7531,16 +6934,8 @@ snapshots: dependencies: '@types/mdast': 4.0.4 - media-typer@0.3.0: {} - - media-typer@1.1.0: {} - memory-pager@1.5.0: {} - merge-descriptors@2.0.0: {} - - methods@1.1.2: {} - micromark-core-commonmark@2.0.3: dependencies: decode-named-character-reference: 1.3.0 @@ -7741,15 +7136,11 @@ snapshots: mime-db@1.52.0: {} - mime-db@1.54.0: {} - mime-types@2.1.35: dependencies: mime-db: 1.52.0 - mime-types@3.0.2: - dependencies: - mime-db: 1.54.0 + mime@3.0.0: {} mime@4.1.0: {} @@ -7838,10 +7229,6 @@ snapshots: nanoid@4.0.2: {} - negotiator@0.6.3: {} - - negotiator@1.0.0: {} - netmask@2.0.2: {} new-find-package-json@2.0.0: @@ -7856,42 +7243,20 @@ snapshots: node-forge@1.3.3: {} - normalize-newline@4.1.0: - dependencies: - replace-buffer: 1.2.1 - normalize-url@8.1.1: {} - object-assign@4.1.1: {} - - object-inspect@1.13.4: {} - object-keys@1.1.1: {} oblivious-set@2.0.0: {} observable-fns@0.6.1: {} - on-finished@2.4.1: - dependencies: - ee-first: 1.1.1 + omggif@1.0.10: {} once@1.4.0: dependencies: wrappy: 1.0.2 - one-time@1.0.0: - dependencies: - fn.name: 1.1.0 - - only@0.0.2: {} - - open@8.4.2: - dependencies: - define-lazy-prop: 2.0.0 - is-docker: 2.2.1 - is-wsl: 2.2.0 - os-tmpdir@1.0.2: {} p-cancelable@3.0.0: {} @@ -7954,6 +7319,15 @@ snapshots: dependencies: callsites: 3.1.0 + parse-bmfont-ascii@1.0.6: {} + + parse-bmfont-binary@1.0.6: {} + + parse-bmfont-xml@1.1.6: + dependencies: + xml-parse-from-string: 1.0.1 + xml2js: 0.5.0 + parse-json@5.2.0: dependencies: '@babel/code-frame': 7.29.0 @@ -7963,10 +7337,6 @@ snapshots: parse-ms@4.0.0: {} - parseurl@1.3.3: {} - - passthrough-counter@1.0.0: {} - path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -7978,8 +7348,6 @@ snapshots: lru-cache: 11.2.6 minipass: 7.1.3 - path-to-regexp@6.3.0: {} - path-to-regexp@8.3.0: {} pdf-lib@1.17.1: @@ -7989,7 +7357,9 @@ snapshots: pako: 1.0.11 tslib: 1.14.1 - pdf2json@3.2.0: {} + pdf2json@4.0.2: {} + + peek-readable@4.1.0: {} peek-readable@5.4.2: {} @@ -8001,25 +7371,30 @@ snapshots: ping@0.4.4: {} + pixelmatch@5.3.0: + dependencies: + pngjs: 6.0.0 + pkg-dir@4.2.0: dependencies: find-up: 4.1.0 + pngjs@6.0.0: {} + + pngjs@7.0.0: {} + pretty-ms@9.3.0: dependencies: parse-ms: 4.0.0 + process@0.11.10: {} + progress@2.0.3: {} property-information@7.1.0: {} proto-list@1.2.4: {} - proxy-addr@2.0.7: - dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 - proxy-agent@6.5.0: dependencies: agent-base: 7.1.4 @@ -8040,8 +7415,6 @@ snapshots: end-of-stream: 1.4.5 once: 1.4.0 - punycode@1.4.1: {} - punycode@2.3.1: {} puppeteer-core@24.37.5: @@ -8084,27 +7457,8 @@ snapshots: pvutils@1.1.5: {} - qs@6.15.0: - dependencies: - side-channel: 1.1.0 - quick-lru@5.1.1: {} - randomatic@3.1.1: - dependencies: - is-number: 4.0.0 - kind-of: 6.0.3 - math-random: 1.0.4 - - range-parser@1.2.1: {} - - raw-body@3.0.2: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.1 - iconv-lite: 0.7.2 - unpipe: 1.0.0 - rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -8118,6 +7472,18 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 + readable-stream@4.7.0: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + readable-web-to-node-stream@3.0.4: + dependencies: + readable-stream: 4.7.0 + readdirp@5.0.0: {} reflect-metadata@0.2.2: {} @@ -8175,8 +7541,6 @@ snapshots: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 - replace-buffer@1.2.1: {} - require-directory@2.1.1: {} resolve-alpn@1.2.1: {} @@ -8213,79 +7577,22 @@ snapshots: '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.52 '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.52 - router@2.2.0: - dependencies: - debug: 4.4.3 - depd: 2.0.0 - is-promise: 4.0.0 - parseurl: 1.3.3 - path-to-regexp: 8.3.0 - transitivePeerDependencies: - - supports-color - run-async@3.0.0: {} rxjs@7.8.2: dependencies: tslib: 2.8.1 - s3rver@3.7.1: - dependencies: - '@koa/router': 9.4.0 - busboy: 0.3.1 - commander: 5.1.0 - fast-xml-parser: 3.21.1 - fs-extra: 8.1.0 - he: 1.2.0 - koa: 2.16.4 - koa-logger: 3.2.1 - lodash: 4.17.23 - statuses: 2.0.2 - winston: 3.19.0 - transitivePeerDependencies: - - supports-color - safe-buffer@5.2.1: {} - safe-regex-test@1.1.0: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-regex: 1.2.1 - - safe-stable-stringify@2.5.0: {} - safer-buffer@2.1.2: {} + sax@1.6.0: {} + semver@6.3.1: {} semver@7.7.4: {} - send@1.2.1: - dependencies: - debug: 4.4.3 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 2.0.0 - http-errors: 2.0.1 - mime-types: 3.0.2 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - - serve-static@2.2.1: - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 1.2.1 - transitivePeerDependencies: - - supports-color - set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -8295,7 +7602,36 @@ snapshots: gopd: 1.2.0 has-property-descriptors: 1.0.2 - setprototypeof@1.2.0: {} + sharp@0.34.5: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.7.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 shebang-command@2.0.0: dependencies: @@ -8303,83 +7639,16 @@ snapshots: shebang-regex@3.0.0: {} - side-channel-list@1.0.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - - side-channel-map@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - - side-channel-weakmap@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 - - side-channel@1.1.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.0 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 - signal-exit@3.0.7: {} signal-exit@4.1.0: {} + simple-xml-to-json@1.2.4: {} + smart-buffer@4.2.0: {} smol-toml@1.6.0: {} - socket.io-adapter@2.5.6: - dependencies: - debug: 4.4.3 - ws: 8.18.3 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - socket.io-client@4.8.1: - dependencies: - '@socket.io/component-emitter': 3.1.2 - debug: 4.3.7 - engine.io-client: 6.6.4 - socket.io-parser: 4.2.5 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - socket.io-parser@4.2.5: - dependencies: - '@socket.io/component-emitter': 3.1.2 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - socket.io@4.8.1: - dependencies: - accepts: 1.3.8 - base64id: 2.0.0 - cors: 2.8.6 - debug: 4.3.7 - engine.io: 6.6.4 - socket.io-adapter: 2.5.6 - socket.io-parser: 4.2.5 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - socks-proxy-agent@8.0.5: dependencies: agent-base: 7.1.4 @@ -8412,14 +7681,6 @@ snapshots: sprintf-js@1.0.3: {} - stack-trace@0.0.10: {} - - statuses@1.5.0: {} - - statuses@2.0.2: {} - - streamsearch@0.1.2: {} - streamx@2.23.0: dependencies: events-universal: 1.0.1 @@ -8448,27 +7709,24 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-indent@4.1.1: {} - strip-json-comments@2.0.1: {} - strnum@1.1.2: {} - strnum@2.1.2: {} strtok3@10.3.4: dependencies: '@tokenizer/token': 0.3.0 + strtok3@6.3.0: + dependencies: + '@tokenizer/token': 0.3.0 + peek-readable: 4.1.0 + strtok3@9.1.1: dependencies: '@tokenizer/token': 0.3.0 peek-readable: 5.4.2 - supports-color@5.5.0: - dependencies: - has-flag: 3.0.0 - sweet-scroll@4.0.0: {} symbol-tree@3.2.4: {} @@ -8510,8 +7768,6 @@ snapshots: transitivePeerDependencies: - react-native-b4a - text-hex@1.0.0: {} - threads@1.7.0: dependencies: callsites: 3.1.0 @@ -8531,11 +7787,16 @@ snapshots: dependencies: esm: 3.2.25 + tinycolor2@1.6.0: {} + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 - toidentifier@1.0.1: {} + token-types@4.2.1: + dependencies: + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 token-types@6.1.2: dependencies: @@ -8551,16 +7812,12 @@ snapshots: trim-lines@3.0.1: {} - triple-beam@1.4.1: {} - trough@2.2.0: {} tslib@1.14.1: {} tslib@2.8.1: {} - tsscmp@1.0.6: {} - tsx@4.21.0: dependencies: esbuild: 0.27.3 @@ -8580,21 +7837,8 @@ snapshots: type-fest@0.21.3: {} - type-fest@2.19.0: {} - type-fest@4.41.0: {} - type-is@1.6.18: - dependencies: - media-typer: 0.3.0 - mime-types: 2.1.35 - - type-is@2.0.1: - dependencies: - content-type: 1.0.5 - media-typer: 1.1.0 - mime-types: 3.0.2 - typed-query-selector@2.12.1: {} typescript@5.9.3: {} @@ -8605,6 +7849,8 @@ snapshots: undici-types@6.21.0: {} + undici-types@7.18.2: {} + unified@11.0.5: dependencies: '@types/unist': 3.0.3 @@ -8638,27 +7884,20 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 - universalify@0.1.2: {} - universalify@2.0.1: {} unload@2.4.1: {} - unpipe@1.0.0: {} - upper-case@1.1.3: {} - url@0.11.4: + utif2@4.1.0: dependencies: - punycode: 1.4.1 - qs: 6.15.0 + pako: 1.0.11 util-deprecate@1.0.2: {} uuid@9.0.1: {} - vary@1.1.2: {} - vfile-message@4.0.3: dependencies: '@types/unist': 3.0.3 @@ -8673,8 +7912,6 @@ snapshots: webidl-conversions@7.0.0: {} - whatwg-mimetype@3.0.0: {} - whatwg-url@14.2.0: dependencies: tr46: 5.1.1 @@ -8688,25 +7925,9 @@ snapshots: dependencies: isexe: 3.1.5 - winston-transport@4.9.0: + which@6.0.1: dependencies: - logform: 2.7.0 - readable-stream: 3.6.2 - triple-beam: 1.4.1 - - winston@3.19.0: - dependencies: - '@colors/colors': 1.6.0 - '@dabh/diagnostics': 2.0.8 - async: 3.2.6 - is-stream: 2.0.1 - logform: 2.7.0 - one-time: 1.0.0 - readable-stream: 3.6.2 - safe-stable-stringify: 2.5.0 - stack-trace: 0.0.10 - triple-beam: 1.4.1 - winston-transport: 4.9.0 + isexe: 4.0.0 wrap-ansi@6.2.0: dependencies: @@ -8722,18 +7943,19 @@ snapshots: wrappy@1.0.2: {} - ws@8.17.1: {} - - ws@8.18.3: {} - ws@8.19.0: {} - xmlhttprequest-ssl@2.1.2: {} + xml-parse-from-string@1.0.1: {} + + xml2js@0.5.0: + dependencies: + sax: 1.6.0 + xmlbuilder: 11.0.1 + + xmlbuilder@11.0.1: {} y18n@5.0.8: {} - yaml@2.8.2: {} - yargs-parser@21.1.1: {} yargs-parser@22.0.0: {} @@ -8758,8 +7980,6 @@ snapshots: buffer-crc32: 0.2.13 pend: 1.2.0 - ylru@1.4.0: {} - yoctocolors-cjs@2.1.3: {} zod@3.25.76: {} diff --git a/readme.md b/readme.md index 27a1c72..cd467d0 100644 --- a/readme.md +++ b/readme.md @@ -2,6 +2,12 @@ A high-performance VPN with a **TypeScript control plane** and a **Rust data plane daemon**. Manage VPN connections with clean, fully-typed APIs while all networking heavy lifting — encryption, tunneling, QoS, rate limiting — runs at native speed in Rust. +🔒 **Noise NK** handshake + **XChaCha20-Poly1305** encryption +🚀 **Dual transport**: WebSocket (Cloudflare-friendly) and raw **QUIC** (with datagram support) +📊 **Adaptive QoS**: packet classification, priority queues, per-client rate limiting +🔄 **Auto-transport**: tries QUIC first, falls back to WebSocket seamlessly +📡 **Real-time telemetry**: RTT, jitter, loss, link health — all exposed via typed APIs + ## Issue Reporting and Security For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly. @@ -17,11 +23,13 @@ pnpm install @push.rocks/smartvpn ``` TypeScript (control plane) Rust (data plane) ┌──────────────────────────┐ ┌────────────────────────────────────┐ -│ VpnClient / VpnServer │ │ smartvpn_daemon │ +│ VpnClient / VpnServer │ │ smartvpn_daemon │ │ └─ VpnBridge │──stdio/──▶ │ ├─ management (JSON IPC) │ -│ └─ RustBridge │ socket │ ├─ transport (WebSocket/TLS) │ -│ (smartrust) │ │ ├─ crypto (Noise NK + XCha20) │ -└──────────────────────────┘ │ ├─ codec (binary framing) │ +│ └─ RustBridge │ socket │ ├─ transport_trait (abstraction) │ +│ (smartrust) │ │ │ ├─ transport (WebSocket/TLS) │ +└──────────────────────────┘ │ │ └─ quic_transport (QUIC/UDP) │ + │ ├─ crypto (Noise NK + XCha20) │ + │ ├─ codec (binary framing) │ │ ├─ keepalive (adaptive state FSM) │ │ ├─ telemetry (RTT/jitter/loss) │ │ ├─ qos (classify + priority Q) │ @@ -37,8 +45,10 @@ TypeScript (control plane) Rust (data plane) | Decision | Choice | Why | |----------|--------|-----| -| Transport | WebSocket over HTTPS | Works through Cloudflare and other terminating proxies | -| Encryption | Noise NK + XChaCha20-Poly1305 | Strong forward secrecy, large nonce space (no counter needed) | +| Transport | WebSocket + QUIC (dual) | WS works through Cloudflare; QUIC gives lower latency + unreliable datagrams | +| Auto-transport | QUIC first, WS fallback | Best performance when QUIC is available, graceful degradation when it's not | +| Encryption | Noise NK + XChaCha20-Poly1305 | Strong forward secrecy, large nonce space (no counter sync needed) | +| QUIC auth | Certificate hash pinning | WireGuard-style trust model — no CA needed, just pin the server cert hash | | Keepalive | Adaptive app-level pings | Cloudflare drops WS pings; interval adapts to link health (10–60s) | | QoS | Packet classification + priority queues | DNS/SSH/ICMP always drain first; bulk flows get deprioritized | | Rate limiting | Per-client token bucket | Byte-granular, dynamically reconfigurable via IPC | @@ -89,6 +99,39 @@ await client.disconnect(); client.stop(); ``` +### VPN Client with QUIC + +```typescript +import { VpnClient } from '@push.rocks/smartvpn'; + +// Explicit QUIC — serverUrl is host:port, pinned by cert hash +const quicClient = new VpnClient({ + transport: { transport: 'stdio' }, +}); + +await quicClient.start(); + +const { assignedIp } = await quicClient.connect({ + serverUrl: 'vpn.example.com:443', + serverPublicKey: 'BASE64_SERVER_PUBLIC_KEY', + transport: 'quic', + serverCertHash: 'BASE64_SHA256_CERT_HASH', // printed by server on startup +}); + +// Or use auto-transport: tries QUIC first (3s timeout), falls back to WS +const autoClient = new VpnClient({ + transport: { transport: 'stdio' }, +}); + +await autoClient.start(); + +await autoClient.connect({ + serverUrl: 'wss://vpn.example.com/tunnel', // WS URL — host:port extracted for QUIC attempt + serverPublicKey: 'BASE64_SERVER_PUBLIC_KEY', + transport: 'auto', // default — QUIC first, then WS +}); +``` + ### VPN Server ```typescript @@ -100,10 +143,9 @@ const server = new VpnServer({ // Generate a Noise keypair first await server.start(); -// If you don't have keys yet: const keypair = await server.generateKeypair(); -// Start the VPN listener (or pass config to start() directly) +// Start the VPN listener await server.start({ listenAddr: '0.0.0.0:443', privateKey: keypair.privateKey, @@ -112,6 +154,12 @@ await server.start({ dns: ['1.1.1.1'], mtu: 1420, enableNat: true, + // Transport mode: 'websocket', 'quic', or 'both' (default) + transportMode: 'both', + // Optional: separate QUIC listen address + quicListenAddr: '0.0.0.0:4433', + // Optional: QUIC idle timeout + quicIdleTimeoutSecs: 30, // Optional: default rate limit for all new clients defaultRateLimitBytesPerSec: 10_000_000, // 10 MB/s defaultBurstBytes: 20_000_000, // 20 MB burst @@ -247,11 +295,66 @@ Both `VpnClient` and `VpnServer` extend `EventEmitter`: ```typescript client.on('exit', ({ code, signal }) => { /* daemon exited */ }); client.on('reconnected', () => { /* socket reconnected */ }); +client.on('status', (status) => { /* IVpnStatus update */ }); +client.on('error', (error) => { /* error from daemon */ }); server.on('client-connected', (info) => { /* IVpnClientInfo */ }); server.on('client-disconnected', ({ clientId, reason }) => { /* ... */ }); +server.on('started', () => { /* server listener started */ }); +server.on('stopped', () => { /* server listener stopped */ }); ``` +## 🌐 Transport Modes + +smartvpn supports two transport protocols through a unified transport abstraction layer. Both use the same encryption, framing, and QoS pipeline — the transport is swappable without changing any application logic. + +### WebSocket (default) + +- Works through Cloudflare, reverse proxies, and HTTP load balancers +- Reliable delivery only (no datagram support) +- URL format: `wss://host/path` or `ws://host:port/path` + +### QUIC + +- Lower latency, built-in multiplexing, 0-RTT connection establishment +- Supports **unreliable datagrams** for IP packets (with automatic fallback to reliable if oversized) +- Certificate hash pinning — no CA chain needed, WireGuard-style trust +- URL format: `host:port` +- ALPN protocol: `smartvpn` + +### Auto-Transport (Recommended) + +The default `transport: 'auto'` mode gives you the best of both worlds: + +1. Extract `host:port` from the WebSocket URL +2. Attempt QUIC connection (3-second timeout) +3. If QUIC fails or times out → fall back to WebSocket +4. Completely transparent to the application + +```typescript +await client.connect({ + serverUrl: 'wss://vpn.example.com/tunnel', + serverPublicKey: '...', + transport: 'auto', // default — QUIC first, WS fallback +}); +``` + +### Server Dual-Mode + +The server can listen on both transports simultaneously: + +```typescript +await server.start({ + listenAddr: '0.0.0.0:443', // WebSocket listener + quicListenAddr: '0.0.0.0:4433', // QUIC listener (optional, defaults to listenAddr) + transportMode: 'both', // 'websocket' | 'quic' | 'both' (default) + quicIdleTimeoutSecs: 30, // QUIC connection idle timeout + // ... other config +}); +``` + +When using `'both'` mode, the server logs the QUIC certificate hash on startup — share this with clients for cert pinning. + ## 📊 QoS System The Rust daemon includes a full QoS stack that operates on decrypted IP packets: @@ -337,9 +440,26 @@ Post-handshake, all IP packets are encrypted with **XChaCha20-Poly1305**: - 16-byte authentication tags - Wire format: `[nonce:24B][ciphertext:var][tag:16B]` +### QUIC Certificate Pinning + +When using QUIC transport, the server generates a self-signed TLS certificate (or uses a configured PEM). Instead of relying on a CA chain, clients pin the server's certificate by its **SHA-256 hash** (base64-encoded) — a WireGuard-inspired trust model: + +```typescript +// Server logs the cert hash on startup: +// "QUIC cert hash: " + +// Client pins it: +await client.connect({ + serverUrl: 'vpn.example.com:443', + transport: 'quic', + serverCertHash: '', + serverPublicKey: '...', +}); +``` + ## 📦 Binary Protocol -Inside the WebSocket tunnel, packets use a simple binary framing: +Inside the tunnel (both WebSocket and QUIC reliable channels), packets use a simple binary framing: ``` ┌──────────┬──────────┬────────────────────┐ @@ -359,6 +479,8 @@ Inside the WebSocket tunnel, packets use a simple binary framing: | `SessionResumeErr` | `0x32` | Resume rejected | | `Disconnect` | `0x3F` | Graceful disconnect | +When QUIC datagrams are available, IP packets can optionally be sent via the unreliable datagram channel for lower latency. Packets that exceed the max datagram size automatically fall back to the reliable stream. + ## 🛠️ Rust Daemon CLI ```bash @@ -385,12 +507,12 @@ pnpm build # Build Rust only (debug) cd rust && cargo build -# Run all tests (71 Rust + 32 TypeScript) +# Run all tests (77 Rust + 59 TypeScript) cd rust && cargo test pnpm test ``` -## TypeScript Interfaces +## 📘 TypeScript Interfaces
Click to expand full type definitions @@ -410,8 +532,10 @@ type TVpnTransportOptions = // Client config interface IVpnClientConfig { - serverUrl: string; - serverPublicKey: string; + serverUrl: string; // WS: 'wss://host/path' | QUIC: 'host:port' + serverPublicKey: string; // Base64-encoded Noise static key + transport?: 'auto' | 'websocket' | 'quic'; // Default: 'auto' + serverCertHash?: string; // SHA-256 cert hash (base64) for QUIC pinning dns?: string[]; mtu?: number; keepaliveIntervalSecs?: number; @@ -429,6 +553,9 @@ interface IVpnServerConfig { mtu?: number; keepaliveIntervalSecs?: number; enableNat?: boolean; + transportMode?: 'websocket' | 'quic' | 'both'; // Default: 'both' + quicListenAddr?: string; // Separate QUIC bind address + quicIdleTimeoutSecs?: number; // QUIC idle timeout (default: 30) defaultRateLimitBytesPerSec?: number; defaultBurstBytes?: number; } @@ -537,7 +664,7 @@ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark G ### Company Information -Task Venture Capital GmbH +Task Venture Capital GmbH Registered at District Court Bremen HRB 35230 HB, Germany For any legal inquiries or further information, please contact us via email at hello@task.vc. diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 4ad13d7..c7ce335 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -120,6 +120,17 @@ version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -169,6 +180,12 @@ dependencies = [ "piper", ] +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + [[package]] name = "bytes" version = "1.11.1" @@ -205,6 +222,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.4" @@ -298,6 +321,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -307,6 +340,22 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -374,6 +423,15 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + [[package]] name = "digest" version = "0.10.7" @@ -416,6 +474,18 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fastbloom" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" +dependencies = [ + "getrandom 0.3.4", + "libm", + "rand 0.9.2", + "siphasher", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -549,8 +619,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -560,9 +632,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasip2", + "wasm-bindgen", ] [[package]] @@ -624,6 +698,38 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "js-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -646,6 +752,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "libmimalloc-sys" version = "0.1.44" @@ -671,6 +783,12 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "matchers" version = "0.2.0" @@ -727,6 +845,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + [[package]] name = "once_cell" version = "1.21.3" @@ -745,6 +869,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "parking" version = "2.2.1" @@ -774,6 +904,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -814,6 +954,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -832,6 +978,63 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "bytes", + "fastbloom", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.44" @@ -906,6 +1109,19 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "yasna", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -946,6 +1162,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc_version" version = "0.4.1" @@ -962,21 +1184,71 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "once_cell", + "ring", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ + "web-time", "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.103.9" @@ -988,12 +1260,59 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.27" @@ -1090,6 +1409,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + [[package]] name = "slab" version = "0.4.12" @@ -1107,23 +1432,31 @@ name = "smartvpn_daemon" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", "base64", "bytes", "chacha20poly1305", "clap", "futures-util", "mimalloc", + "quinn", "rand 0.8.5", + "rcgen", + "ring", + "rustls", + "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "snow", - "thiserror", + "thiserror 2.0.18", "tokio", "tokio-tungstenite", "tokio-util", "tracing", "tracing-subscriber", "tun", + "webpki-roots 1.0.6", ] [[package]] @@ -1175,13 +1508,33 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1204,6 +1557,40 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.49.0" @@ -1277,6 +1664,7 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -1346,7 +1734,7 @@ dependencies = [ "libc", "log", "nix", - "thiserror", + "thiserror 2.0.18", "tokio", "tokio-util", "windows-sys 0.59.0", @@ -1368,7 +1756,7 @@ dependencies = [ "rustls", "rustls-pki-types", "sha1", - "thiserror", + "thiserror 2.0.18", "utf-8", ] @@ -1424,6 +1812,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -1439,6 +1837,70 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "0.26.11" @@ -1457,12 +1919,30 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -1499,6 +1979,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -1532,6 +2027,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -1544,6 +2045,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -1556,6 +2063,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -1580,6 +2093,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -1592,6 +2111,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -1604,6 +2129,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -1616,6 +2147,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -1649,7 +2186,7 @@ dependencies = [ "futures", "libloading", "log", - "thiserror", + "thiserror 2.0.18", "windows-sys 0.61.2", "winreg", ] @@ -1660,6 +2197,15 @@ version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + [[package]] name = "zerocopy" version = "0.8.39" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 7687a9a..9ce9f7e 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -25,6 +25,14 @@ tun = { version = "0.7", features = ["async"] } bytes = "1" tokio-util = "0.7" futures-util = "0.3" +async-trait = "0.1" +quinn = "0.11" +rustls = { version = "0.23", default-features = false, features = ["ring", "std"] } +rcgen = "0.13" +ring = "0.17" +rustls-pki-types = "1" +rustls-pemfile = "2" +webpki-roots = "1" mimalloc = "0.1" [profile.release] diff --git a/rust/src/client.rs b/rust/src/client.rs index 9129bc4..ad1cadc 100644 --- a/rust/src/client.rs +++ b/rust/src/client.rs @@ -1,10 +1,8 @@ use anyhow::Result; use bytes::BytesMut; -use futures_util::{SinkExt, StreamExt}; use serde::Deserialize; use std::sync::Arc; use tokio::sync::{mpsc, watch, RwLock}; -use tokio_tungstenite::tungstenite::Message; use tracing::{info, error, warn, debug}; use crate::codec::{Frame, FrameCodec, PacketType}; @@ -12,6 +10,8 @@ use crate::crypto; use crate::keepalive::{self, KeepaliveSignal, LinkHealth}; use crate::telemetry::ConnectionQuality; use crate::transport; +use crate::transport_trait::{self, TransportSink, TransportStream}; +use crate::quic_transport; /// Client configuration (matches TS IVpnClientConfig). #[derive(Debug, Clone, Deserialize)] @@ -22,6 +22,10 @@ pub struct ClientConfig { pub dns: Option>, pub mtu: Option, pub keepalive_interval_secs: Option, + /// Transport type: "websocket" (default) or "quic". + pub transport: Option, + /// For QUIC: SHA-256 hash of server certificate (base64) for cert pinning. + pub server_cert_hash: Option, } /// Client statistics. @@ -106,9 +110,66 @@ impl VpnClient { &config.server_public_key, )?; - // Connect to WebSocket server - let ws = transport::connect_to_server(&config.server_url).await?; - let (mut ws_sink, mut ws_stream) = ws.split(); + // Create transport based on configuration + let (mut sink, mut stream): (Box, Box) = { + let transport_type = config.transport.as_deref().unwrap_or("auto"); + match transport_type { + "quic" => { + let server_addr = &config.server_url; // For QUIC, serverUrl is host:port + let cert_hash = config.server_cert_hash.as_deref(); + let conn = quic_transport::connect_quic(server_addr, cert_hash).await?; + let (quic_sink, quic_stream) = quic_transport::open_quic_streams(conn).await?; + info!("Connected via QUIC"); + (Box::new(quic_sink) as Box, + Box::new(quic_stream) as Box) + } + "websocket" => { + let ws = transport::connect_to_server(&config.server_url).await?; + let (ws_sink, ws_stream) = transport_trait::split_ws(ws); + info!("Connected via WebSocket"); + (Box::new(ws_sink), Box::new(ws_stream)) + } + _ => { + // "auto" (default): try QUIC first, fall back to WebSocket + // Extract host:port from the URL for QUIC attempt + let quic_addr = extract_host_port(&config.server_url); + let cert_hash = config.server_cert_hash.as_deref(); + + if let Some(ref addr) = quic_addr { + match tokio::time::timeout( + std::time::Duration::from_secs(3), + try_quic_connect(addr, cert_hash), + ).await { + Ok(Ok((quic_sink, quic_stream))) => { + info!("Auto: connected via QUIC to {}", addr); + (Box::new(quic_sink) as Box, + Box::new(quic_stream) as Box) + } + Ok(Err(e)) => { + debug!("Auto: QUIC failed ({}), falling back to WebSocket", e); + let ws = transport::connect_to_server(&config.server_url).await?; + let (ws_sink, ws_stream) = transport_trait::split_ws(ws); + info!("Auto: connected via WebSocket (QUIC unavailable)"); + (Box::new(ws_sink), Box::new(ws_stream)) + } + Err(_) => { + debug!("Auto: QUIC timed out, falling back to WebSocket"); + let ws = transport::connect_to_server(&config.server_url).await?; + let (ws_sink, ws_stream) = transport_trait::split_ws(ws); + info!("Auto: connected via WebSocket (QUIC timed out)"); + (Box::new(ws_sink), Box::new(ws_stream)) + } + } + } else { + // Can't extract host:port for QUIC, use WebSocket directly + let ws = transport::connect_to_server(&config.server_url).await?; + let (ws_sink, ws_stream) = transport_trait::split_ws(ws); + info!("Connected via WebSocket"); + (Box::new(ws_sink), Box::new(ws_stream)) + } + } + } + }; // Noise NK handshake (client side = initiator) *state.write().await = ClientState::Handshaking; @@ -123,13 +184,11 @@ impl VpnClient { }; let mut frame_bytes = BytesMut::new(); >::encode(&mut FrameCodec, init_frame, &mut frame_bytes)?; - ws_sink.send(Message::Binary(frame_bytes.to_vec().into())).await?; + sink.send_reliable(frame_bytes.to_vec()).await?; // <- e, ee - let resp_msg = match ws_stream.next().await { - Some(Ok(Message::Binary(data))) => data.to_vec(), - Some(Ok(_)) => anyhow::bail!("Expected binary handshake response"), - Some(Err(e)) => anyhow::bail!("WebSocket error during handshake: {}", e), + let resp_msg = match stream.recv_reliable().await? { + Some(data) => data, None => anyhow::bail!("Connection closed during handshake"), }; @@ -145,9 +204,9 @@ impl VpnClient { let mut noise_transport = initiator.into_transport_mode()?; // Receive assigned IP info (encrypted) - let info_msg = match ws_stream.next().await { - Some(Ok(Message::Binary(data))) => data.to_vec(), - _ => anyhow::bail!("Expected IP info message"), + let info_msg = match stream.recv_reliable().await? { + Some(data) => data, + None => anyhow::bail!("Connection closed before IP info"), }; let mut frame_buf = BytesMut::from(&info_msg[..]); @@ -184,8 +243,8 @@ impl VpnClient { // Spawn packet forwarding loop let assigned_ip_clone = assigned_ip.clone(); tokio::spawn(client_loop( - ws_sink, - ws_stream, + sink, + stream, noise_transport, state, stats, @@ -280,8 +339,8 @@ impl VpnClient { /// The main client packet forwarding loop (runs in a spawned task). async fn client_loop( - mut ws_sink: futures_util::stream::SplitSink, - mut ws_stream: futures_util::stream::SplitStream, + mut sink: Box, + mut stream: Box, mut noise_transport: snow::TransportState, state: Arc>, stats: Arc>, @@ -294,10 +353,10 @@ async fn client_loop( loop { tokio::select! { - msg = ws_stream.next() => { + msg = stream.recv_reliable() => { match msg { - Some(Ok(Message::Binary(data))) => { - let mut frame_buf = BytesMut::from(&data[..][..]); + Ok(Some(data)) => { + let mut frame_buf = BytesMut::from(&data[..]); if let Ok(Some(frame)) = ::decode(&mut FrameCodec, &mut frame_buf) { match frame.packet_type { PacketType::IpPacket => { @@ -328,17 +387,13 @@ async fn client_loop( } } } - Some(Ok(Message::Close(_))) | None => { + Ok(None) => { info!("Connection closed"); *state.write().await = ClientState::Disconnected; break; } - Some(Ok(Message::Ping(data))) => { - let _ = ws_sink.send(Message::Pong(data)).await; - } - Some(Ok(_)) => continue, - Some(Err(e)) => { - error!("WebSocket error: {}", e); + Err(e) => { + error!("Transport error: {}", e); *state.write().await = ClientState::Error(e.to_string()); break; } @@ -354,7 +409,7 @@ async fn client_loop( }; let mut frame_bytes = BytesMut::new(); if >::encode(&mut FrameCodec, ka_frame, &mut frame_bytes).is_ok() { - if ws_sink.send(Message::Binary(frame_bytes.to_vec().into())).await.is_err() { + if sink.send_reliable(frame_bytes.to_vec()).await.is_err() { warn!("Failed to send keepalive"); *state.write().await = ClientState::Disconnected; break; @@ -385,12 +440,51 @@ async fn client_loop( }; let mut frame_bytes = BytesMut::new(); if >::encode(&mut FrameCodec, dc_frame, &mut frame_bytes).is_ok() { - let _ = ws_sink.send(Message::Binary(frame_bytes.to_vec().into())).await; + let _ = sink.send_reliable(frame_bytes.to_vec()).await; } - let _ = ws_sink.close().await; + let _ = sink.close().await; *state.write().await = ClientState::Disconnected; break; } } } } + +/// Try to connect via QUIC. Returns transport halves on success. +async fn try_quic_connect( + addr: &str, + cert_hash: Option<&str>, +) -> Result<(quic_transport::QuicTransportSink, quic_transport::QuicTransportStream)> { + let conn = quic_transport::connect_quic(addr, cert_hash).await?; + let (sink, stream) = quic_transport::open_quic_streams(conn).await?; + Ok((sink, stream)) +} + +/// Extract host:port from a WebSocket URL for QUIC auto-fallback. +/// e.g. "ws://127.0.0.1:8080" -> Some("127.0.0.1:8080") +/// "wss://vpn.example.com/tunnel" -> Some("vpn.example.com:443") +/// "127.0.0.1:8080" -> Some("127.0.0.1:8080") (already host:port) +fn extract_host_port(url: &str) -> Option { + if url.starts_with("ws://") || url.starts_with("wss://") { + // Parse as URL + let stripped = if url.starts_with("wss://") { + &url[6..] + } else { + &url[5..] + }; + // Remove path + let host_port = stripped.split('/').next()?; + if host_port.contains(':') { + Some(host_port.to_string()) + } else { + // Default port + let default_port = if url.starts_with("wss://") { 443 } else { 80 }; + Some(format!("{}:{}", host_port, default_port)) + } + } else if url.contains(':') { + // Already host:port + Some(url.to_string()) + } else { + None + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index ba2386d..22937ce 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -5,6 +5,8 @@ pub mod management; pub mod codec; pub mod crypto; pub mod transport; +pub mod transport_trait; +pub mod quic_transport; pub mod keepalive; pub mod tunnel; pub mod network; diff --git a/rust/src/quic_transport.rs b/rust/src/quic_transport.rs new file mode 100644 index 0000000..7fc1d55 --- /dev/null +++ b/rust/src/quic_transport.rs @@ -0,0 +1,546 @@ +use anyhow::Result; +use async_trait::async_trait; +use quinn::crypto::rustls::QuicClientConfig; +use rustls_pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}; +use std::net::SocketAddr; +use std::sync::Arc; +use std::time::Duration; +use tracing::{info, warn, debug}; + +use crate::transport_trait::{TransportSink, TransportStream}; + +// ============================================================================ +// TLS / Certificate helpers +// ============================================================================ + +/// Generate a self-signed certificate and private key for QUIC. +pub fn generate_self_signed_cert() -> Result<(Vec>, PrivateKeyDer<'static>)> { + let cert = rcgen::generate_simple_self_signed(vec!["smartvpn".to_string()])?; + let cert_der = CertificateDer::from(cert.cert); + let key_der = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(cert.key_pair.serialize_der())); + Ok((vec![cert_der], key_der)) +} + +/// Compute the SHA-256 hash of a DER-encoded certificate and return it as base64. +pub fn cert_hash(cert_der: &CertificateDer<'_>) -> String { + use ring::digest; + let hash = digest::digest(&digest::SHA256, cert_der.as_ref()); + base64::Engine::encode(&base64::engine::general_purpose::STANDARD, hash.as_ref()) +} + +// ============================================================================ +// Server-side QUIC endpoint +// ============================================================================ + +/// Configuration for the QUIC server endpoint. +pub struct QuicServerConfig { + pub listen_addr: String, + pub cert_chain: Vec>, + pub private_key: PrivateKeyDer<'static>, + pub idle_timeout_secs: u64, +} + +/// Create a QUIC server endpoint bound to the given address. +pub fn create_quic_server(config: QuicServerConfig) -> Result { + let addr: SocketAddr = config.listen_addr.parse()?; + + let provider = Arc::new(rustls::crypto::ring::default_provider()); + let mut tls_config = rustls::ServerConfig::builder_with_provider(provider) + .with_safe_default_protocol_versions()? + .with_no_client_auth() + .with_single_cert(config.cert_chain, config.private_key)?; + tls_config.alpn_protocols = vec![b"smartvpn".to_vec()]; + + let mut server_config = quinn::ServerConfig::with_crypto(Arc::new( + quinn::crypto::rustls::QuicServerConfig::try_from(tls_config)?, + )); + + let mut transport = quinn::TransportConfig::default(); + transport.max_idle_timeout(Some( + quinn::IdleTimeout::try_from(Duration::from_secs(config.idle_timeout_secs))?, + )); + // Enable datagrams with a generous max size + transport.datagram_receive_buffer_size(Some(65535)); + transport.datagram_send_buffer_size(65535); + server_config.transport_config(Arc::new(transport)); + + let endpoint = quinn::Endpoint::server(server_config, addr)?; + info!("QUIC server listening on {}", addr); + Ok(endpoint) +} + +// ============================================================================ +// Client-side QUIC connection +// ============================================================================ + +/// A certificate verifier that accepts any server certificate. +/// Safe when Noise NK provides server authentication at the application layer. +#[derive(Debug)] +struct AcceptAnyCert; + +impl rustls::client::danger::ServerCertVerifier for AcceptAnyCert { + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &rustls::pki_types::ServerName<'_>, + _ocsp_response: &[u8], + _now: rustls::pki_types::UnixTime, + ) -> Result { + Ok(rustls::client::danger::ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> Result { + Err(rustls::Error::General("TLS 1.2 not supported".to_string())) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> Result { + rustls::crypto::verify_tls13_signature( + message, + cert, + dss, + &rustls::crypto::ring::default_provider().signature_verification_algorithms, + ) + } + + fn supported_verify_schemes(&self) -> Vec { + rustls::crypto::ring::default_provider() + .signature_verification_algorithms + .supported_schemes() + } +} + +/// A certificate verifier that accepts any certificate matching a given SHA-256 hash. +#[derive(Debug)] +struct CertHashVerifier { + expected_hash: String, +} + +impl rustls::client::danger::ServerCertVerifier for CertHashVerifier { + fn verify_server_cert( + &self, + end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &rustls::pki_types::ServerName<'_>, + _ocsp_response: &[u8], + _now: rustls::pki_types::UnixTime, + ) -> Result { + let actual_hash = cert_hash(end_entity); + if actual_hash == self.expected_hash { + Ok(rustls::client::danger::ServerCertVerified::assertion()) + } else { + Err(rustls::Error::General(format!( + "Certificate hash mismatch: expected {}, got {}", + self.expected_hash, actual_hash + ))) + } + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> Result { + // QUIC always uses TLS 1.3 + Err(rustls::Error::General("TLS 1.2 not supported".to_string())) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> Result { + rustls::crypto::verify_tls13_signature( + message, + cert, + dss, + &rustls::crypto::ring::default_provider().signature_verification_algorithms, + ) + } + + fn supported_verify_schemes(&self) -> Vec { + rustls::crypto::ring::default_provider() + .signature_verification_algorithms + .supported_schemes() + } +} + +/// Connect to a QUIC server. +/// +/// - If `server_cert_hash` is provided, verifies the server certificate matches +/// the given SHA-256 hash (cert pinning). +/// - If `server_cert_hash` is `None`, accepts any server certificate. This is +/// safe because the Noise NK handshake (which runs over the QUIC stream) +/// authenticates the server via its pre-shared public key — the same trust +/// model as WireGuard. +pub async fn connect_quic( + addr: &str, + server_cert_hash: Option<&str>, +) -> Result { + let remote: SocketAddr = addr.parse()?; + + let provider = Arc::new(rustls::crypto::ring::default_provider()); + let tls_config = if let Some(hash) = server_cert_hash { + // Pin to a specific certificate hash + let mut config = rustls::ClientConfig::builder_with_provider(provider) + .with_safe_default_protocol_versions()? + .dangerous() + .with_custom_certificate_verifier(Arc::new(CertHashVerifier { + expected_hash: hash.to_string(), + })) + .with_no_client_auth(); + config.alpn_protocols = vec![b"smartvpn".to_vec()]; + config + } else { + // Accept any cert — Noise NK provides server authentication + let mut config = rustls::ClientConfig::builder_with_provider(provider) + .with_safe_default_protocol_versions()? + .dangerous() + .with_custom_certificate_verifier(Arc::new(AcceptAnyCert)) + .with_no_client_auth(); + config.alpn_protocols = vec![b"smartvpn".to_vec()]; + config + }; + + let client_config = quinn::ClientConfig::new(Arc::new( + QuicClientConfig::try_from(tls_config)?, + )); + + let mut endpoint = quinn::Endpoint::client("0.0.0.0:0".parse()?)?; + endpoint.set_default_client_config(client_config); + + info!("Connecting to QUIC server at {}", addr); + let connection = endpoint.connect(remote, "smartvpn")?.await?; + info!("QUIC connection established"); + + Ok(connection) +} + +// ============================================================================ +// QUIC Transport Sink / Stream implementations +// ============================================================================ + +/// QUIC transport sink — wraps a SendStream (reliable) and Connection (datagrams). +pub struct QuicTransportSink { + send_stream: quinn::SendStream, + connection: quinn::Connection, +} + +impl QuicTransportSink { + pub fn new(send_stream: quinn::SendStream, connection: quinn::Connection) -> Self { + Self { + send_stream, + connection, + } + } +} + +#[async_trait] +impl TransportSink for QuicTransportSink { + async fn send_reliable(&mut self, data: Vec) -> Result<()> { + // Length-prefix framing: [4-byte big-endian length][payload] + let len = data.len() as u32; + self.send_stream.write_all(&len.to_be_bytes()).await?; + self.send_stream.write_all(&data).await?; + Ok(()) + } + + async fn send_datagram(&mut self, data: Vec) -> Result<()> { + let max_size = self.connection.max_datagram_size(); + match max_size { + Some(max) if data.len() <= max => { + self.connection.send_datagram(data.into())?; + Ok(()) + } + _ => { + // Datagram too large or datagrams disabled — fall back to reliable + debug!("Datagram too large ({}B), falling back to reliable stream", data.len()); + self.send_reliable(data).await + } + } + } + + async fn close(&mut self) -> Result<()> { + self.send_stream.finish()?; + Ok(()) + } +} + +/// QUIC transport stream — wraps a RecvStream (reliable) and Connection (datagrams). +pub struct QuicTransportStream { + recv_stream: quinn::RecvStream, + connection: quinn::Connection, +} + +impl QuicTransportStream { + pub fn new(recv_stream: quinn::RecvStream, connection: quinn::Connection) -> Self { + Self { + recv_stream, + connection, + } + } +} + +#[async_trait] +impl TransportStream for QuicTransportStream { + async fn recv_reliable(&mut self) -> Result>> { + // Read length prefix + let mut len_buf = [0u8; 4]; + match self.recv_stream.read_exact(&mut len_buf).await { + Ok(()) => {} + Err(quinn::ReadExactError::FinishedEarly(_)) => return Ok(None), + Err(quinn::ReadExactError::ReadError(quinn::ReadError::ConnectionLost(e))) => { + warn!("QUIC connection lost: {}", e); + return Ok(None); + } + Err(e) => return Err(anyhow::anyhow!("QUIC read error: {}", e)), + } + + let len = u32::from_be_bytes(len_buf) as usize; + if len > 65536 { + return Err(anyhow::anyhow!("Frame too large: {} bytes", len)); + } + + let mut data = vec![0u8; len]; + match self.recv_stream.read_exact(&mut data).await { + Ok(()) => Ok(Some(data)), + Err(quinn::ReadExactError::FinishedEarly(_)) => Ok(None), + Err(e) => Err(anyhow::anyhow!("QUIC read error: {}", e)), + } + } + + async fn recv_datagram(&mut self) -> Result>> { + match self.connection.read_datagram().await { + Ok(data) => Ok(Some(data.to_vec())), + Err(quinn::ConnectionError::ApplicationClosed(_)) => Ok(None), + Err(quinn::ConnectionError::LocallyClosed) => Ok(None), + Err(e) => Err(anyhow::anyhow!("QUIC datagram error: {}", e)), + } + } + + fn supports_datagrams(&self) -> bool { + self.connection.max_datagram_size().is_some() + } +} + +/// Accept a QUIC connection and open a bidirectional control stream. +/// Returns the transport sink/stream pair ready for the VPN handshake. +pub async fn accept_quic_connection( + conn: quinn::Connection, +) -> Result<(QuicTransportSink, QuicTransportStream)> { + // The client opens the bidirectional control stream + let (send, recv) = conn.accept_bi().await?; + info!("QUIC bidirectional control stream accepted"); + Ok(( + QuicTransportSink::new(send, conn.clone()), + QuicTransportStream::new(recv, conn), + )) +} + +/// Open a QUIC connection's bidirectional control stream (client side). +pub async fn open_quic_streams( + conn: quinn::Connection, +) -> Result<(QuicTransportSink, QuicTransportStream)> { + let (send, recv) = conn.open_bi().await?; + info!("QUIC bidirectional control stream opened"); + Ok(( + QuicTransportSink::new(send, conn.clone()), + QuicTransportStream::new(recv, conn), + )) +} + +// ============================================================================ +// Tests +// ============================================================================ + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_cert_generation_and_hash() { + let (certs, _key) = generate_self_signed_cert().unwrap(); + assert_eq!(certs.len(), 1); + let hash = cert_hash(&certs[0]); + // SHA-256 base64 is 44 characters + assert_eq!(hash.len(), 44); + } + + #[test] + fn test_cert_hash_deterministic() { + let (certs, _key) = generate_self_signed_cert().unwrap(); + let hash1 = cert_hash(&certs[0]); + let hash2 = cert_hash(&certs[0]); + assert_eq!(hash1, hash2); + } + + /// Helper: create QUIC server and client endpoints. + fn create_quic_endpoints() -> (quinn::Endpoint, quinn::Endpoint, String) { + let (certs, key) = generate_self_signed_cert().unwrap(); + let hash = cert_hash(&certs[0]); + let provider = Arc::new(rustls::crypto::ring::default_provider()); + + let mut server_tls = rustls::ServerConfig::builder_with_provider(provider.clone()) + .with_safe_default_protocol_versions().unwrap() + .with_no_client_auth() + .with_single_cert(certs, key).unwrap(); + server_tls.alpn_protocols = vec![b"smartvpn".to_vec()]; + let server_qcfg = quinn::ServerConfig::with_crypto(Arc::new( + quinn::crypto::rustls::QuicServerConfig::try_from(server_tls).unwrap(), + )); + let server_ep = quinn::Endpoint::server(server_qcfg, "127.0.0.1:0".parse().unwrap()).unwrap(); + + let mut client_tls = rustls::ClientConfig::builder_with_provider(provider) + .with_safe_default_protocol_versions().unwrap() + .dangerous() + .with_custom_certificate_verifier(Arc::new(CertHashVerifier { + expected_hash: hash, + })) + .with_no_client_auth(); + client_tls.alpn_protocols = vec![b"smartvpn".to_vec()]; + let client_config = quinn::ClientConfig::new(Arc::new( + QuicClientConfig::try_from(client_tls).unwrap(), + )); + let mut client_ep = quinn::Endpoint::client("0.0.0.0:0".parse().unwrap()).unwrap(); + client_ep.set_default_client_config(client_config); + + let server_addr = server_ep.local_addr().unwrap().to_string(); + (server_ep, client_ep, server_addr) + } + + #[tokio::test(flavor = "multi_thread", worker_threads = 4)] + async fn test_quic_server_client_roundtrip() { + let (server_ep, client_ep, server_addr) = create_quic_endpoints(); + let addr: std::net::SocketAddr = server_addr.parse().unwrap(); + + // Server: accept, accept_bi, read, echo, finish + let server_task = tokio::spawn(async move { + let conn = server_ep.accept().await.unwrap().await.unwrap(); + let (mut s_send, mut s_recv) = conn.accept_bi().await.unwrap(); + let data = s_recv.read_to_end(1024).await.unwrap(); + s_send.write_all(&data).await.unwrap(); + s_send.finish().unwrap(); + tokio::time::sleep(Duration::from_secs(1)).await; + server_ep + }); + + // Client: connect, open_bi, write, finish, read + let conn = client_ep.connect(addr, "smartvpn").unwrap().await.unwrap(); + let (mut c_send, mut c_recv) = conn.open_bi().await.unwrap(); + c_send.write_all(b"hello quinn").await.unwrap(); + c_send.finish().unwrap(); + let data = c_recv.read_to_end(1024).await.unwrap(); + assert_eq!(&data[..], b"hello quinn"); + + let _ = server_task.await; + drop(client_ep); + } + + /// Test transport trait wrappers over QUIC. + /// Key: client must send data first (QUIC streams are opened implicitly by data). + /// The server accept_bi runs concurrently with the client's first send_reliable. + #[tokio::test(flavor = "multi_thread", worker_threads = 4)] + async fn test_quic_transport_trait_roundtrip() { + let (server_ep, client_ep, server_addr) = create_quic_endpoints(); + let addr: std::net::SocketAddr = server_addr.parse().unwrap(); + + // Server task: accept connection, then accept_bi (blocks until client sends data) + let server_task = tokio::spawn(async move { + let conn = server_ep.accept().await.unwrap().await.unwrap(); + let (s_sink, s_stream) = accept_quic_connection(conn).await.unwrap(); + (s_sink, s_stream, server_ep) + }); + + // Client: connect, open_bi via wrapper + let conn = client_ep.connect(addr, "smartvpn").unwrap().await.unwrap(); + let (mut c_sink, mut c_stream) = open_quic_streams(conn).await.unwrap(); + + // Client sends first — this triggers the QUIC stream to become visible to the server + c_sink.send_reliable(b"hello-from-client".to_vec()).await.unwrap(); + + // Now server's accept_bi unblocks + let (mut s_sink, mut s_stream, _sep) = server_task.await.unwrap(); + + // Server reads the message + let msg = s_stream.recv_reliable().await.unwrap().unwrap(); + assert_eq!(msg, b"hello-from-client"); + + // Server -> Client + s_sink.send_reliable(b"hello-from-server".to_vec()).await.unwrap(); + let msg = c_stream.recv_reliable().await.unwrap().unwrap(); + assert_eq!(msg, b"hello-from-server"); + + drop(client_ep); + } + + /// Test QUIC datagram support. + #[tokio::test(flavor = "multi_thread", worker_threads = 4)] + async fn test_quic_datagram_exchange() { + let (server_ep, client_ep, server_addr) = create_quic_endpoints(); + let addr: std::net::SocketAddr = server_addr.parse().unwrap(); + + // Server: accept, accept_bi (opens control stream), then read datagram + let server_task = tokio::spawn(async move { + let conn = server_ep.accept().await.unwrap().await.unwrap(); + // Accept bi stream (control channel) + let (_s_sink, _s_stream) = accept_quic_connection(conn.clone()).await.unwrap(); + // Read datagram + let dgram = conn.read_datagram().await.unwrap(); + assert_eq!(&dgram[..], b"dgram-payload"); + server_ep + }); + + // Client: connect, open bi stream (triggers server accept_bi), then send datagram + let conn = client_ep.connect(addr, "smartvpn").unwrap().await.unwrap(); + let (mut c_sink, _c_stream) = open_quic_streams(conn.clone()).await.unwrap(); + + // Send initial data to open the stream (required for QUIC) + c_sink.send_reliable(b"init".to_vec()).await.unwrap(); + + // Small yield to let the server process the bi stream + tokio::task::yield_now().await; + + // Send datagram + assert!(conn.max_datagram_size().is_some()); + conn.send_datagram(bytes::Bytes::from_static(b"dgram-payload")).unwrap(); + + let _ = server_task.await.unwrap(); + drop(client_ep); + } + + /// Test that supports_datagrams returns true for QUIC transports. + #[tokio::test(flavor = "multi_thread", worker_threads = 4)] + async fn test_quic_supports_datagrams() { + let (server_ep, client_ep, server_addr) = create_quic_endpoints(); + let addr: std::net::SocketAddr = server_addr.parse().unwrap(); + + let server_task = tokio::spawn(async move { + let conn = server_ep.accept().await.unwrap().await.unwrap(); + let (_s_sink, s_stream) = accept_quic_connection(conn).await.unwrap(); + assert!(s_stream.supports_datagrams()); + server_ep + }); + + let conn = client_ep.connect(addr, "smartvpn").unwrap().await.unwrap(); + let (mut c_sink, c_stream) = open_quic_streams(conn).await.unwrap(); + assert!(c_stream.supports_datagrams()); + + // Send data to trigger server's accept_bi + c_sink.send_reliable(b"ping".to_vec()).await.unwrap(); + + let _ = server_task.await.unwrap(); + drop(client_ep); + } +} diff --git a/rust/src/ratelimit.rs b/rust/src/ratelimit.rs index 0720eb4..816a8b8 100644 --- a/rust/src/ratelimit.rs +++ b/rust/src/ratelimit.rs @@ -130,10 +130,12 @@ mod tests { #[test] fn tokens_do_not_exceed_burst() { - let mut tb = TokenBucket::new(1_000_000, 1_000); + // Use a low rate so refill between consecutive calls is negligible + let mut tb = TokenBucket::new(100, 1_000); // Wait to accumulate — but should cap at burst std::thread::sleep(Duration::from_millis(50)); assert!(tb.try_consume(1_000)); + // At 100 bytes/sec, the few μs between calls add ~0 tokens assert!(!tb.try_consume(1)); } } diff --git a/rust/src/server.rs b/rust/src/server.rs index 580b689..3bb72e3 100644 --- a/rust/src/server.rs +++ b/rust/src/server.rs @@ -1,6 +1,5 @@ use anyhow::Result; use bytes::BytesMut; -use futures_util::{SinkExt, StreamExt}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::net::Ipv4Addr; @@ -8,7 +7,6 @@ use std::sync::Arc; use std::time::Duration; use tokio::net::TcpListener; use tokio::sync::{mpsc, Mutex, RwLock}; -use tokio_tungstenite::tungstenite::Message; use tracing::{info, error, warn}; use crate::codec::{Frame, FrameCodec, PacketType}; @@ -17,6 +15,8 @@ use crate::mtu::{MtuConfig, TunnelOverhead}; use crate::network::IpPool; use crate::ratelimit::TokenBucket; use crate::transport; +use crate::transport_trait::{self, TransportSink, TransportStream}; +use crate::quic_transport; /// Dead-peer timeout: 3x max keepalive interval (Healthy=60s). const DEAD_PEER_TIMEOUT: Duration = Duration::from_secs(180); @@ -39,6 +39,12 @@ pub struct ServerConfig { pub default_rate_limit_bytes_per_sec: Option, /// Default burst size for new clients (bytes). None = unlimited. pub default_burst_bytes: Option, + /// Transport mode: "websocket" (default), "quic", or "both". + pub transport_mode: Option, + /// QUIC listen address (host:port). Defaults to listen_addr. + pub quic_listen_addr: Option, + /// QUIC idle timeout in seconds (default: 30). + pub quic_idle_timeout_secs: Option, } /// Information about a connected client. @@ -135,14 +141,58 @@ impl VpnServer { self.state = Some(state.clone()); self.shutdown_tx = Some(shutdown_tx); + let transport_mode = config.transport_mode.as_deref().unwrap_or("both"); let listen_addr = config.listen_addr.clone(); - tokio::spawn(async move { - if let Err(e) = run_listener(state, listen_addr, &mut shutdown_rx).await { - error!("Server listener error: {}", e); - } - }); - info!("VPN server started"); + match transport_mode { + "quic" => { + let quic_addr = config.quic_listen_addr.clone().unwrap_or_else(|| listen_addr.clone()); + let idle_timeout = config.quic_idle_timeout_secs.unwrap_or(30); + tokio::spawn(async move { + if let Err(e) = run_quic_listener(state, quic_addr, idle_timeout, &mut shutdown_rx).await { + error!("QUIC listener error: {}", e); + } + }); + } + "both" => { + let quic_addr = config.quic_listen_addr.clone().unwrap_or_else(|| listen_addr.clone()); + let idle_timeout = config.quic_idle_timeout_secs.unwrap_or(30); + let state2 = state.clone(); + let (shutdown_tx2, mut shutdown_rx2) = mpsc::channel::<()>(1); + // Store second shutdown sender so both listeners stop + let shutdown_tx_orig = self.shutdown_tx.take().unwrap(); + let (combined_tx, mut combined_rx) = mpsc::channel::<()>(1); + self.shutdown_tx = Some(combined_tx); + + // Forward combined shutdown to both listeners + tokio::spawn(async move { + combined_rx.recv().await; + let _ = shutdown_tx_orig.send(()).await; + let _ = shutdown_tx2.send(()).await; + }); + + tokio::spawn(async move { + if let Err(e) = run_ws_listener(state, listen_addr, &mut shutdown_rx).await { + error!("WebSocket listener error: {}", e); + } + }); + tokio::spawn(async move { + if let Err(e) = run_quic_listener(state2, quic_addr, idle_timeout, &mut shutdown_rx2).await { + error!("QUIC listener error: {}", e); + } + }); + } + _ => { + // "websocket" (default) + tokio::spawn(async move { + if let Err(e) = run_ws_listener(state, listen_addr, &mut shutdown_rx).await { + error!("Server listener error: {}", e); + } + }); + } + } + + info!("VPN server started (transport: {})", transport_mode); Ok(()) } @@ -239,7 +289,9 @@ impl VpnServer { } } -async fn run_listener( +/// WebSocket listener — accepts TCP connections, upgrades to WS, then hands off +/// to the transport-agnostic `handle_client_connection`. +async fn run_ws_listener( state: Arc, listen_addr: String, shutdown_rx: &mut mpsc::Receiver<()>, @@ -255,8 +307,20 @@ async fn run_listener( info!("New connection from {}", addr); let state = state.clone(); tokio::spawn(async move { - if let Err(e) = handle_client_connection(state, stream).await { - warn!("Client connection error: {}", e); + match transport::accept_connection(stream).await { + Ok(ws) => { + let (sink, stream) = transport_trait::split_ws(ws); + if let Err(e) = handle_client_connection( + state, + Box::new(sink), + Box::new(stream), + ).await { + warn!("Client connection error: {}", e); + } + } + Err(e) => { + warn!("WebSocket upgrade failed: {}", e); + } } }); } @@ -275,13 +339,95 @@ async fn run_listener( Ok(()) } +/// QUIC listener — accepts QUIC connections and hands off to the transport-agnostic +/// `handle_client_connection`. +async fn run_quic_listener( + state: Arc, + listen_addr: String, + idle_timeout_secs: u64, + shutdown_rx: &mut mpsc::Receiver<()>, +) -> Result<()> { + // Generate or use configured TLS certificate for QUIC + let (cert_chain, private_key) = if let (Some(ref cert_pem), Some(ref key_pem)) = + (&state.config.tls_cert, &state.config.tls_key) + { + // Parse PEM certificates + let certs: Vec> = + rustls_pemfile::certs(&mut cert_pem.as_bytes()) + .collect::, _>>()?; + let key = rustls_pemfile::private_key(&mut key_pem.as_bytes())? + .ok_or_else(|| anyhow::anyhow!("No private key found in PEM"))?; + (certs, key) + } else { + // Generate self-signed certificate + let (certs, key) = quic_transport::generate_self_signed_cert()?; + info!("QUIC using self-signed certificate (hash: {})", quic_transport::cert_hash(&certs[0])); + (certs, key) + }; + + let endpoint = quic_transport::create_quic_server(quic_transport::QuicServerConfig { + listen_addr, + cert_chain, + private_key, + idle_timeout_secs, + })?; + + loop { + tokio::select! { + incoming = endpoint.accept() => { + match incoming { + Some(incoming) => { + let state = state.clone(); + tokio::spawn(async move { + match incoming.await { + Ok(conn) => { + let remote = conn.remote_address(); + info!("New QUIC connection from {}", remote); + match quic_transport::accept_quic_connection(conn).await { + Ok((sink, stream)) => { + if let Err(e) = handle_client_connection( + state, + Box::new(sink), + Box::new(stream), + ).await { + warn!("QUIC client error: {}", e); + } + } + Err(e) => { + warn!("QUIC stream accept failed: {}", e); + } + } + } + Err(e) => { + warn!("QUIC handshake failed: {}", e); + } + } + }); + } + None => { + info!("QUIC endpoint closed"); + break; + } + } + } + _ = shutdown_rx.recv() => { + info!("QUIC shutdown signal received"); + endpoint.close(0u32.into(), b"shutdown"); + break; + } + } + } + + Ok(()) +} + +/// Transport-agnostic client handler. Performs the Noise NK handshake, registers +/// the client, and runs the main packet forwarding loop. async fn handle_client_connection( state: Arc, - stream: tokio::net::TcpStream, + mut sink: Box, + mut stream: Box, ) -> Result<()> { - let ws = transport::accept_connection(stream).await?; - let (mut ws_sink, mut ws_stream) = ws.split(); - let client_id = uuid_v4(); let assigned_ip = state.ip_pool.lock().await.allocate(&client_id)?; @@ -295,9 +441,9 @@ async fn handle_client_connection( let mut buf = vec![0u8; 65535]; // Receive handshake init - let init_msg = match ws_stream.next().await { - Some(Ok(Message::Binary(data))) => data.to_vec(), - _ => anyhow::bail!("Expected handshake init message"), + let init_msg = match stream.recv_reliable().await? { + Some(data) => data, + None => anyhow::bail!("Connection closed before handshake"), }; let mut frame_buf = BytesMut::from(&init_msg[..]); @@ -318,7 +464,7 @@ async fn handle_client_connection( }; let mut frame_bytes = BytesMut::new(); >::encode(&mut FrameCodec, response_frame, &mut frame_bytes)?; - ws_sink.send(Message::Binary(frame_bytes.to_vec().into())).await?; + sink.send_reliable(frame_bytes.to_vec()).await?; let mut noise_transport = responder.into_transport_mode()?; @@ -369,7 +515,7 @@ async fn handle_client_connection( }; let mut frame_bytes = BytesMut::new(); >::encode(&mut FrameCodec, encrypted_info, &mut frame_bytes)?; - ws_sink.send(Message::Binary(frame_bytes.to_vec().into())).await?; + sink.send_reliable(frame_bytes.to_vec()).await?; info!("Client {} connected with IP {}", client_id, assigned_ip); @@ -378,11 +524,11 @@ async fn handle_client_connection( loop { tokio::select! { - msg = ws_stream.next() => { + msg = stream.recv_reliable() => { match msg { - Some(Ok(Message::Binary(data))) => { + Ok(Some(data)) => { last_activity = tokio::time::Instant::now(); - let mut frame_buf = BytesMut::from(&data[..][..]); + let mut frame_buf = BytesMut::from(&data[..]); match ::decode(&mut FrameCodec, &mut frame_buf) { Ok(Some(frame)) => match frame.packet_type { PacketType::IpPacket => { @@ -432,7 +578,7 @@ async fn handle_client_connection( }; let mut frame_bytes = BytesMut::new(); >::encode(&mut FrameCodec, ack_frame, &mut frame_bytes)?; - ws_sink.send(Message::Binary(frame_bytes.to_vec().into())).await?; + sink.send_reliable(frame_bytes.to_vec()).await?; let mut stats = state.stats.write().await; stats.keepalives_received += 1; @@ -463,20 +609,12 @@ async fn handle_client_connection( } } } - Some(Ok(Message::Close(_))) | None => { + Ok(None) => { info!("Client {} connection closed", client_id); break; } - Some(Ok(Message::Ping(data))) => { - last_activity = tokio::time::Instant::now(); - ws_sink.send(Message::Pong(data)).await?; - } - Some(Ok(_)) => { - last_activity = tokio::time::Instant::now(); - continue; - } - Some(Err(e)) => { - warn!("WebSocket error from {}: {}", client_id, e); + Err(e) => { + warn!("Transport error from {}: {}", client_id, e); break; } } diff --git a/rust/src/transport_trait.rs b/rust/src/transport_trait.rs new file mode 100644 index 0000000..d7198b9 --- /dev/null +++ b/rust/src/transport_trait.rs @@ -0,0 +1,116 @@ +use anyhow::Result; +use async_trait::async_trait; +use futures_util::{SinkExt, StreamExt}; +use tokio_tungstenite::tungstenite::Message; + +use crate::transport::WsStream; + +// ============================================================================ +// Transport trait abstraction +// ============================================================================ + +/// Outbound half of a VPN transport connection. +#[async_trait] +pub trait TransportSink: Send + 'static { + /// Send a framed binary message on the reliable channel. + async fn send_reliable(&mut self, data: Vec) -> Result<()>; + + /// Send a datagram (unreliable, best-effort). + /// Falls back to reliable if the transport does not support datagrams. + async fn send_datagram(&mut self, data: Vec) -> Result<()>; + + /// Gracefully close the transport. + async fn close(&mut self) -> Result<()>; +} + +/// Inbound half of a VPN transport connection. +#[async_trait] +pub trait TransportStream: Send + 'static { + /// Receive the next reliable binary message. Returns `None` on close. + async fn recv_reliable(&mut self) -> Result>>; + + /// Receive the next datagram. Returns `None` if datagrams are unsupported + /// or the connection is closed. + async fn recv_datagram(&mut self) -> Result>>; + + /// Whether this transport supports unreliable datagrams. + fn supports_datagrams(&self) -> bool; +} + +// ============================================================================ +// WebSocket implementation +// ============================================================================ + +/// WebSocket transport sink (wraps the write half of a split WsStream). +pub struct WsTransportSink { + inner: futures_util::stream::SplitSink, +} + +impl WsTransportSink { + pub fn new(inner: futures_util::stream::SplitSink) -> Self { + Self { inner } + } +} + +#[async_trait] +impl TransportSink for WsTransportSink { + async fn send_reliable(&mut self, data: Vec) -> Result<()> { + self.inner.send(Message::Binary(data.into())).await?; + Ok(()) + } + + async fn send_datagram(&mut self, data: Vec) -> Result<()> { + // WebSocket has no datagram support — fall back to reliable. + self.send_reliable(data).await + } + + async fn close(&mut self) -> Result<()> { + self.inner.close().await?; + Ok(()) + } +} + +/// WebSocket transport stream (wraps the read half of a split WsStream). +pub struct WsTransportStream { + inner: futures_util::stream::SplitStream, +} + +impl WsTransportStream { + pub fn new(inner: futures_util::stream::SplitStream) -> Self { + Self { inner } + } +} + +#[async_trait] +impl TransportStream for WsTransportStream { + async fn recv_reliable(&mut self) -> Result>> { + loop { + match self.inner.next().await { + Some(Ok(Message::Binary(data))) => return Ok(Some(data.to_vec())), + Some(Ok(Message::Close(_))) | None => return Ok(None), + Some(Ok(Message::Ping(_))) => { + // Ping handling is done at the tungstenite layer automatically + // when the sink side is alive. Just skip here. + continue; + } + Some(Ok(_)) => continue, + Some(Err(e)) => return Err(anyhow::anyhow!("WebSocket error: {}", e)), + } + } + } + + async fn recv_datagram(&mut self) -> Result>> { + // WebSocket does not support datagrams. + Ok(None) + } + + fn supports_datagrams(&self) -> bool { + false + } +} + +/// Split a WebSocket stream into transport sink and stream halves. +pub fn split_ws(ws: WsStream) -> (WsTransportSink, WsTransportStream) { + let (sink, stream) = ws.split(); + (WsTransportSink::new(sink), WsTransportStream::new(stream)) +} diff --git a/test/test.quic.node.ts b/test/test.quic.node.ts new file mode 100644 index 0000000..6ffc9ae --- /dev/null +++ b/test/test.quic.node.ts @@ -0,0 +1,242 @@ +import { tap, expect } from '@git.zone/tstest/tapbundle'; +import * as net from 'net'; +import * as dgram from 'dgram'; +import { VpnClient, VpnServer } from '../ts/index.js'; +import type { IVpnClientOptions, IVpnServerOptions, IVpnKeypair, IVpnServerConfig } from '../ts/index.js'; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +async function findFreePort(): Promise { + const server = net.createServer(); + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)); + const port = (server.address() as net.AddressInfo).port; + await new Promise((resolve) => server.close(() => resolve())); + return port; +} + +async function findFreeUdpPort(): Promise { + const sock = dgram.createSocket('udp4'); + await new Promise((resolve) => sock.bind(0, '127.0.0.1', resolve)); + const port = (sock.address() as net.AddressInfo).port; + await new Promise((resolve) => sock.close(resolve)); + return port; +} + +function delay(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +async function waitFor( + fn: () => Promise, + timeoutMs: number = 10000, + pollMs: number = 500, +): Promise { + const deadline = Date.now() + timeoutMs; + while (Date.now() < deadline) { + if (await fn()) return; + await delay(pollMs); + } + throw new Error(`waitFor timed out after ${timeoutMs}ms`); +} + +// --------------------------------------------------------------------------- +// Test state +// --------------------------------------------------------------------------- + +let server: VpnServer; +let wsPort: number; +let quicPort: number; +let keypair: IVpnKeypair; + +// --------------------------------------------------------------------------- +// Tests: QUIC-only server + QUIC client +// --------------------------------------------------------------------------- + +tap.test('setup: start VPN server in QUIC mode', async () => { + quicPort = await findFreeUdpPort(); + + const options: IVpnServerOptions = { + transport: { transport: 'stdio' }, + }; + server = new VpnServer(options); + + const started = await server['bridge'].start(); + expect(started).toBeTrue(); + + keypair = await server.generateKeypair(); + + const serverConfig: IVpnServerConfig = { + listenAddr: `127.0.0.1:${quicPort}`, + privateKey: keypair.privateKey, + publicKey: keypair.publicKey, + subnet: '10.9.0.0/24', + transportMode: 'quic', + keepaliveIntervalSecs: 3, + }; + await server['bridge'].sendCommand('start', { config: serverConfig }); + + const status = await server.getStatus(); + expect(status.state).toEqual('connected'); +}); + +tap.test('QUIC client connects and gets IP', async () => { + const options: IVpnClientOptions = { + transport: { transport: 'stdio' }, + }; + const client = new VpnClient(options); + const started = await client.start(); + expect(started).toBeTrue(); + + const result = await client.connect({ + serverUrl: `127.0.0.1:${quicPort}`, + serverPublicKey: keypair.publicKey, + transport: 'quic', + keepaliveIntervalSecs: 3, + }); + + expect(result.assignedIp).toBeTypeofString(); + expect(result.assignedIp).toStartWith('10.9.0.'); + + const clientStatus = await client.getStatus(); + expect(clientStatus.state).toEqual('connected'); + + // Verify server sees the client + await waitFor(async () => { + const clients = await server.listClients(); + return clients.length >= 1; + }); + + await client.stop(); +}); + +tap.test('teardown: stop QUIC server', async () => { + await server.stop(); + await delay(500); +}); + +// --------------------------------------------------------------------------- +// Tests: dual-mode server (both) + auto client +// --------------------------------------------------------------------------- + +let dualServer: VpnServer; +let dualWsPort: number; +let dualQuicPort: number; +let dualKeypair: IVpnKeypair; + +tap.test('setup: start VPN server in both mode', async () => { + dualWsPort = await findFreePort(); + dualQuicPort = await findFreeUdpPort(); + + const options: IVpnServerOptions = { + transport: { transport: 'stdio' }, + }; + dualServer = new VpnServer(options); + + const started = await dualServer['bridge'].start(); + expect(started).toBeTrue(); + + dualKeypair = await dualServer.generateKeypair(); + + const serverConfig: IVpnServerConfig = { + listenAddr: `127.0.0.1:${dualWsPort}`, + privateKey: dualKeypair.privateKey, + publicKey: dualKeypair.publicKey, + subnet: '10.10.0.0/24', + transportMode: 'both', + quicListenAddr: `127.0.0.1:${dualQuicPort}`, + keepaliveIntervalSecs: 3, + }; + await dualServer['bridge'].sendCommand('start', { config: serverConfig }); + + const status = await dualServer.getStatus(); + expect(status.state).toEqual('connected'); +}); + +tap.test('auto client connects to dual-mode server (QUIC preferred)', async () => { + const options: IVpnClientOptions = { + transport: { transport: 'stdio' }, + }; + const client = new VpnClient(options); + const started = await client.start(); + expect(started).toBeTrue(); + + // "auto" mode (default): tries QUIC first at same host:port, falls back to WS + // Since the WS port and QUIC port differ, auto will try QUIC on WS port (fail), + // then fall back to WebSocket + const result = await client.connect({ + serverUrl: `ws://127.0.0.1:${dualWsPort}`, + serverPublicKey: dualKeypair.publicKey, + // transport defaults to 'auto' + keepaliveIntervalSecs: 3, + }); + + expect(result.assignedIp).toBeTypeofString(); + expect(result.assignedIp).toStartWith('10.10.0.'); + + const clientStatus = await client.getStatus(); + expect(clientStatus.state).toEqual('connected'); + + await waitFor(async () => { + const clients = await dualServer.listClients(); + return clients.length >= 1; + }); + + await client.stop(); +}); + +tap.test('explicit QUIC client connects to dual-mode server', async () => { + const options: IVpnClientOptions = { + transport: { transport: 'stdio' }, + }; + const client = new VpnClient(options); + const started = await client.start(); + expect(started).toBeTrue(); + + const result = await client.connect({ + serverUrl: `127.0.0.1:${dualQuicPort}`, + serverPublicKey: dualKeypair.publicKey, + transport: 'quic', + keepaliveIntervalSecs: 3, + }); + + expect(result.assignedIp).toBeTypeofString(); + expect(result.assignedIp).toStartWith('10.10.0.'); + + const clientStatus = await client.getStatus(); + expect(clientStatus.state).toEqual('connected'); + + await client.stop(); +}); + +tap.test('keepalive exchange over QUIC', async () => { + const options: IVpnClientOptions = { + transport: { transport: 'stdio' }, + }; + const client = new VpnClient(options); + await client.start(); + + await client.connect({ + serverUrl: `127.0.0.1:${dualQuicPort}`, + serverPublicKey: dualKeypair.publicKey, + transport: 'quic', + keepaliveIntervalSecs: 3, + }); + + // Wait for keepalive exchange + await delay(8000); + + const clientStats = await client.getStatistics(); + expect(clientStats.keepalivesSent).toBeGreaterThanOrEqual(1); + expect(clientStats.keepalivesReceived).toBeGreaterThanOrEqual(1); + + await client.stop(); +}); + +tap.test('teardown: stop dual-mode server', async () => { + await dualServer.stop(); + await delay(500); +}); + +export default tap.start(); diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 4d78117..3d585d9 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartvpn', - version: '1.3.0', + version: '1.4.0', description: 'A VPN solution with TypeScript control plane and Rust data plane daemon' } diff --git a/ts/smartvpn.classes.vpnconfig.ts b/ts/smartvpn.classes.vpnconfig.ts index 567344a..6740ef3 100644 --- a/ts/smartvpn.classes.vpnconfig.ts +++ b/ts/smartvpn.classes.vpnconfig.ts @@ -15,8 +15,11 @@ export class VpnConfig { if (!config.serverUrl) { throw new Error('VpnConfig: serverUrl is required'); } - if (!config.serverUrl.startsWith('wss://') && !config.serverUrl.startsWith('ws://')) { - throw new Error('VpnConfig: serverUrl must start with wss:// or ws://'); + // For QUIC-only transport, serverUrl is a host:port address; for WebSocket/auto it must be ws:// or wss:// + if (config.transport !== 'quic') { + if (!config.serverUrl.startsWith('wss://') && !config.serverUrl.startsWith('ws://')) { + throw new Error('VpnConfig: serverUrl must start with wss:// or ws:// (for WebSocket transport)'); + } } if (!config.serverPublicKey) { throw new Error('VpnConfig: serverPublicKey is required'); diff --git a/ts/smartvpn.interfaces.ts b/ts/smartvpn.interfaces.ts index 0da6e48..0b58496 100644 --- a/ts/smartvpn.interfaces.ts +++ b/ts/smartvpn.interfaces.ts @@ -32,6 +32,10 @@ export interface IVpnClientConfig { mtu?: number; /** Keepalive interval in seconds (default: 30) */ keepaliveIntervalSecs?: number; + /** Transport protocol: 'auto' (default, tries QUIC then WS), 'websocket', or 'quic' */ + transport?: 'auto' | 'websocket' | 'quic'; + /** For QUIC: SHA-256 hash of server certificate (base64) for cert pinning */ + serverCertHash?: string; } export interface IVpnClientOptions { @@ -68,6 +72,12 @@ export interface IVpnServerConfig { defaultRateLimitBytesPerSec?: number; /** Default burst size for new clients (bytes). Omit for unlimited. */ defaultBurstBytes?: number; + /** Transport mode: 'both' (default, WS+QUIC), 'websocket', or 'quic' */ + transportMode?: 'websocket' | 'quic' | 'both'; + /** QUIC listen address (host:port). Defaults to listenAddr. */ + quicListenAddr?: string; + /** QUIC idle timeout in seconds (default: 30) */ + quicIdleTimeoutSecs?: number; } export interface IVpnServerOptions {