Compare commits

..

8 Commits

Author SHA1 Message Date
c0e432fd9b v26.2.4
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-26 07:05:57 +00:00
a3d8a3a388 fix(rustproxy-http): improve HTTP/3 connection reuse and clean up stale proxy state 2026-03-26 07:05:57 +00:00
437d1a3329 v26.2.3
Some checks failed
Default (tags) / security (push) Failing after 3s
Default (tags) / test (push) Failing after 3s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-25 07:26:47 +00:00
746d93663d fix(repo): no changes to commit 2026-03-25 07:26:47 +00:00
a3f3fee253 v26.2.2
Some checks failed
Default (tags) / security (push) Failing after 4s
Default (tags) / test (push) Failing after 5s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-25 07:22:17 +00:00
53dee1fffc fix(proxy): improve connection cleanup and route validation handling 2026-03-25 07:22:17 +00:00
34dc0cb9b6 v26.2.1
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-23 11:11:55 +00:00
c83c43194b fix(rustproxy-http): include the upstream request URL when caching H3 Alt-Svc discoveries 2026-03-23 11:11:55 +00:00
23 changed files with 893 additions and 851 deletions

View File

@@ -1,5 +1,33 @@
# Changelog
## 2026-03-26 - 26.2.4 - fix(rustproxy-http)
improve HTTP/3 connection reuse and clean up stale proxy state
- Reuse pooled HTTP/3 SendRequest handles to skip repeated SETTINGS handshakes and reduce request overhead on QUIC pool hits
- Add periodic cleanup for per-route rate limiters and orphaned backend metrics to prevent unbounded memory growth after traffic or backend errors stop
- Enforce HTTP max connection lifetime alongside idle timeouts and apply configured lifetime values from the TCP listener
- Reduce HTTP/3 body copying by using owned Bytes paths for request and response streaming, and replace the custom response body adapter with a stream-based implementation
- Harden auxiliary proxy components by capping datagram handler buffer growth and removing duplicate RustProxy exit listeners
## 2026-03-25 - 26.2.3 - fix(repo)
no changes to commit
## 2026-03-25 - 26.2.2 - fix(proxy)
improve connection cleanup and route validation handling
- add timeouts for HTTP/1 upstream connection drivers to prevent lingering tasks
- ensure QUIC relay sessions cancel and abort background tasks on drop
- avoid registering unnamed routes as duplicates and label unnamed catch-all conflicts clearly
- fix offset mapping route helper to forward only remaining route options without overriding derived values
- update project config filename and toolchain versions for the current build setup
## 2026-03-23 - 26.2.1 - fix(rustproxy-http)
include the upstream request URL when caching H3 Alt-Svc discoveries
- Tracks the request path that triggered Alt-Svc discovery in connection activity state
- Adds request URL context to Alt-Svc debug logging and protocol cache insertion reasons for better traceability
## 2026-03-23 - 26.2.0 - feat(protocol-cache)
add sliding TTL re-probing and eviction for backend protocol detection

770
deno.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
Copyright (c) 2019 Lossless GmbH (hello@lossless.com)
Copyright (c) 2019 Task Venture Capital GmbH (hello@task.vc)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,6 +1,6 @@
{
"name": "@push.rocks/smartproxy",
"version": "26.2.0",
"version": "26.2.4",
"private": false,
"description": "A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.",
"main": "dist_ts/index.js",
@@ -16,13 +16,13 @@
"buildDocs": "tsdoc"
},
"devDependencies": {
"@git.zone/tsbuild": "^4.3.0",
"@git.zone/tsrun": "^2.0.1",
"@git.zone/tsrust": "^1.3.0",
"@git.zone/tstest": "^3.5.0",
"@push.rocks/smartserve": "^2.0.1",
"@git.zone/tsbuild": "^4.4.0",
"@git.zone/tsrun": "^2.0.2",
"@git.zone/tsrust": "^1.3.2",
"@git.zone/tstest": "^3.6.0",
"@push.rocks/smartserve": "^2.0.3",
"@types/node": "^25.5.0",
"typescript": "^5.9.3",
"typescript": "^6.0.2",
"why-is-node-running": "^3.2.2"
},
"dependencies": {
@@ -41,7 +41,7 @@
"dist_ts_web/**/*",
"assets/**/*",
"cli.js",
"npmextra.json",
".smartconfig.json",
"readme.md",
"changelog.md"
],

469
pnpm-lock.yaml generated
View File

@@ -25,26 +25,26 @@ importers:
version: 10.2.4
devDependencies:
'@git.zone/tsbuild':
specifier: ^4.3.0
version: 4.3.0
specifier: ^4.4.0
version: 4.4.0
'@git.zone/tsrun':
specifier: ^2.0.1
version: 2.0.1
specifier: ^2.0.2
version: 2.0.2
'@git.zone/tsrust':
specifier: ^1.3.0
version: 1.3.0
specifier: ^1.3.2
version: 1.3.2
'@git.zone/tstest':
specifier: ^3.5.0
version: 3.5.0(socks@2.8.7)(typescript@5.9.3)
specifier: ^3.6.0
version: 3.6.0(socks@2.8.7)(typescript@6.0.2)
'@push.rocks/smartserve':
specifier: ^2.0.1
version: 2.0.1
specifier: ^2.0.3
version: 2.0.3
'@types/node':
specifier: ^25.5.0
version: 25.5.0
typescript:
specifier: ^5.9.3
version: 5.9.3
specifier: ^6.0.2
version: 6.0.2
why-is-node-running:
specifier: ^3.2.2
version: 3.2.2
@@ -414,28 +414,28 @@ packages:
cpu: [x64]
os: [win32]
'@git.zone/tsbuild@4.3.0':
resolution: {integrity: sha512-lb6eMQ8RQPaJqAB4kC++GIElOiTAH1pClmoND/q7XHuiMZxv6cXz2/U/sZt339mon2c40dXRG2tkLF2jRsP0pQ==}
'@git.zone/tsbuild@4.4.0':
resolution: {integrity: sha512-98igHfppi6blFYDyzNukNkj4FUO5ZlyXEaSyJh8vCkkZM8SyAgfZj+NUWA1D1iaPXE58UvK1Pt/o8p8iI9UHHw==}
hasBin: true
'@git.zone/tsbundle@2.9.1':
resolution: {integrity: sha512-JW1xjSv7UjAm2lwAQPxhCWs14wqs+UIq5FqIGUPuI6rrDBWIMT2d0gpP6iP6TqXqgm6XpBlfU4rHcHheUXzXbQ==}
'@git.zone/tsbundle@2.10.0':
resolution: {integrity: sha512-dw2VFlgKssDlCxg92wSPiiAKwfCjJBOEOYXq1xO91OpjQLOkyogCxSLy0jzQ2BYnt4qmBnapjamzYzVjCr4CWg==}
hasBin: true
'@git.zone/tspublish@1.11.2':
resolution: {integrity: sha512-BcGap1OzXDgXpfQXMh9W17r/CkWNhPsJ3WzjG2wrGE+ePUJCJAm9w6+J8G5WdZZcZKPqTB07cp707LbSiksc5A==}
'@git.zone/tspublish@1.11.5':
resolution: {integrity: sha512-3tCGhVbH6S/17n3A6Tc6H+ncRdxxbTT0ABcj8S1wRLA8YuBSj9bY7k6uj/iFRy/B/OepB94m1goCiaWESdcZYg==}
hasBin: true
'@git.zone/tsrun@2.0.1':
resolution: {integrity: sha512-NEcnsjvlC1o3Z6SS3VhKCf6Ev+Sh4EAinmggslrIR/ppMrvjDbXNFXoyr3PB+GLeSAR0JRZ1fGvVYjpEzjBdIg==}
'@git.zone/tsrun@2.0.2':
resolution: {integrity: sha512-Rnp/wYHzI8A1pVBKOOePRJgQiBZdW+GEjpQk2uhvXz6A+ljUV2SXKc7NpQVVDsjEZaNFeAI9jMYOdk3lm3yMDA==}
hasBin: true
'@git.zone/tsrust@1.3.0':
resolution: {integrity: sha512-dvmTAiM04Pkd7J1Gail3fu7aasmILQhC5vKL71/g6HYhpvl16/c+Dj3We5G4HsFr0jvAr+Xu570ZGEuZrtRcCg==}
'@git.zone/tsrust@1.3.2':
resolution: {integrity: sha512-bUGomPk++He47Q6rnd9bihX6qoYtXgp9BtroBnNADk3q8WGyHivAcPwqIe4Bk32eByzW1Acc37u/h5gb/V8ekA==}
hasBin: true
'@git.zone/tstest@3.5.0':
resolution: {integrity: sha512-ugIJzdVkbgqSSw08SZajE7TB01GIYjEAmIy67O5skhvOyszGifwzJdR+8dS1VbQGlUUWQZMGQ2IowllHbAZYJQ==}
'@git.zone/tstest@3.6.0':
resolution: {integrity: sha512-5D6COywCXmCqeUB8v6/kOzjEWCTKTUTI3ZB99ebwEibENFXnFBoVxNSRKN0pSmBYlgBEkT7DLNfTfp5tclSg8A==}
hasBin: true
'@img/colour@1.1.0':
@@ -783,8 +783,8 @@ packages:
'@napi-rs/wasm-runtime@1.1.1':
resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==}
'@oxc-project/types@0.99.0':
resolution: {integrity: sha512-LLDEhXB7g1m5J+woRSgfKsFPS3LhR9xRhTeIoEBm5WrkwMxn6eZ0Ld0c0K5eHB57ChZX6I3uSmmLjZ8pcjlRcw==}
'@oxc-project/types@0.122.0':
resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==}
'@pdf-lib/standard-fonts@1.0.0':
resolution: {integrity: sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==}
@@ -858,12 +858,12 @@ packages:
'@push.rocks/lik@6.3.1':
resolution: {integrity: sha512-UWDwGBaVx5yPtAFXqDDBtQZCzETUOA/7myQIXb+YBsuiIw4yQuhNZ23uY2ChQH2Zn6DLqdNSgQcYC0WywMZBNQ==}
'@push.rocks/lik@6.4.0':
resolution: {integrity: sha512-GCdXyF2a6NP+i0W6Mib1PjtA6JGrl6Ae17SbaQwqTscn4JHNta6xm9r+D8/b83XGZsoU903FlJZli3YqJCxT9Q==}
'@push.rocks/mongodump@1.1.0':
resolution: {integrity: sha512-kW0ZUGyf1e4nwloVwBQjNId+MzgTcNS834C+RxH21i1NqyOubbpWZtJtPP+K+s35nSJRyCTy3ICfBMdDBTAm2w==}
'@push.rocks/npmextra@5.3.3':
resolution: {integrity: sha512-snLpSHwaQ5OXlZzF1KX/FY71W5LwajjBzor82Vue0smjEPnSeUPY5/JcVdMwtdprdJe13pc/EQQuIiL/zw4/yg==}
'@push.rocks/qenv@6.1.3':
resolution: {integrity: sha512-+z2hsAU/7CIgpYLFqvda8cn9rUBMHqLdQLjsFfRn5jPoD7dJ5rFlpkbhfM4Ws8mHMniwWaxGKo+q/YBhtzRBLg==}
@@ -888,6 +888,9 @@ packages:
'@push.rocks/smartclickhouse@2.2.0':
resolution: {integrity: sha512-eTzKiREIPSzL1kPkVyD6vEbn+WV/DvQqDjP67VlhNlQGbRcemnJG/eLrUUR1ytmdIqnsZGEK6UYBgyj5nhzLNQ==}
'@push.rocks/smartconfig@6.1.0':
resolution: {integrity: sha512-B+xh63PhGAsSwuRyCKXr4PAjJ4HoVKhNysi67OGY6gGqGm6uopgEW1cvrUZ7T5ZSck9KlVx7ZTugbqm6dqBK1Q==}
'@push.rocks/smartcrypto@2.0.4':
resolution: {integrity: sha512-1+/5bsjyataf5uUkUNnnVXGRAt+gHVk1KDzozjTqgqJxHvQk1d9fVDohL6CxUhUucTPtu5VR5xNBiV8YCDuGyw==}
@@ -1017,8 +1020,8 @@ packages:
'@push.rocks/smartrx@3.0.10':
resolution: {integrity: sha512-USjIYcsSfzn14cwOsxgq/bBmWDTTzy3ouWAnW5NdMyRRzEbmeNrvmy6TRqNeDlJ2PsYNTt1rr/zGUqvIy72ITg==}
'@push.rocks/smartserve@2.0.1':
resolution: {integrity: sha512-YQb2qexfCzCqOlLWBBXKMg6xG4zahCPAxomz/KEKAwHtW6wMTtuHKSTSkRTQ0vl9jssLMAmRz2OyafiL9XGJXQ==}
'@push.rocks/smartserve@2.0.3':
resolution: {integrity: sha512-PttdFlh61lsDNSRvRCSlKjRzuxgD3WP2XLuBNXu1hLfqLpQXDESj0ZCRPDZslLZsyFT5aHP9godb4D4L3bzHWA==}
'@push.rocks/smartshell@3.3.8':
resolution: {integrity: sha512-t9J/py0vnea4ZtOs7Anc9dc6lcvg6EDvYBw5eE1mB+KUWxMQf/ROIQwWMo6B9SMNY4JS2UwvfuJQJ8makP/7Tg==}
@@ -1029,8 +1032,8 @@ packages:
'@push.rocks/smartstate@2.0.27':
resolution: {integrity: sha512-q4UKir7GV3hakJWXQR4DoA4tUVwT5GRkJ/MtanHYF0wZLHfS19+nGmyO9y974zk3eT9hmy3+Lq5cKtU2W6+Y3w==}
'@push.rocks/smartstorage@6.0.1':
resolution: {integrity: sha512-W5PEVwO0J2K9YUZRTbKXadC11h6/IBzzqU+P0TIE/xpJZC4K1duEXwEhxGWcbfhCkPRRa51xH8Z5mAmzzm8qxA==}
'@push.rocks/smartstorage@6.3.2':
resolution: {integrity: sha512-g8rXlVZ+6iKmzNoybtwQntdb7EWA6WnVmbXNOdwDKWR8w4o/7UMErj+H5mt57iqYIy1pzQAoTb8IWJNsti7XQw==}
'@push.rocks/smartstream@3.4.0':
resolution: {integrity: sha512-kePb44W9n5K96zj2Ms3K4xnYbNXP5AfxDd86zZMDQ1/T10nvkIpL9m5w4lG/VJ4KAsWFs81S87BkkcjhhrY5Kw==}
@@ -1050,8 +1053,8 @@ packages:
'@push.rocks/smartversion@3.0.5':
resolution: {integrity: sha512-8MZSo1yqyaKxKq0Q5N188l4un++9GFWVbhCAX5mXJwewZHn97ujffTeL+eOQYpWFTEpUhaq1QhL4NhqObBCt1Q==}
'@push.rocks/smartwatch@6.3.0':
resolution: {integrity: sha512-TeZ1PGBoBMpC4/CK8StIj5InEiFfKp7xWJSm3aYMjB/uaoeRP0vXqv1ORIC/TKYGJuEDuAXUsit8tZVjn0qT1Q==}
'@push.rocks/smartwatch@6.4.0':
resolution: {integrity: sha512-KDswRgE/siBmZRCsRA07MtW5oF4c9uQEBkwTGPIWneHzksbCDsvs/7agKFEL7WnNifLNwo8w1K1qoiVWkX1fvw==}
engines: {node: '>=20.0.0'}
'@push.rocks/smartyaml@2.0.5':
@@ -1083,146 +1086,152 @@ packages:
resolution: {integrity: sha512-bqorOaGXPOuiOSV81luTKrTghg4O4NBRD0zyv7TIqmrMGf4a0uoozaUMp1X8vQdZW+y0gTzUJP9wkzAE6Cci0g==}
deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartpromise
'@rolldown/binding-android-arm64@1.0.0-beta.52':
resolution: {integrity: sha512-MBGIgysimZPqTDcLXI+i9VveijkP5C3EAncEogXhqfax6YXj1Tr2LY3DVuEOMIjWfMPMhtQSPup4fSTAmgjqIw==}
'@rolldown/binding-android-arm64@1.0.0-rc.11':
resolution: {integrity: sha512-SJ+/g+xNnOh6NqYxD0V3uVN4W3VfnrGsC9/hoglicgTNfABFG9JjISvkkU0dNY84MNHLWyOgxP9v9Y9pX4S7+A==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [android]
'@rolldown/binding-darwin-arm64@1.0.0-beta.52':
resolution: {integrity: sha512-MmKeoLnKu1d9j6r19K8B+prJnIZ7u+zQ+zGQ3YHXGnr41rzE3eqQLovlkvoZnRoxDGPA4ps0pGiwXy6YE3lJyg==}
'@rolldown/binding-darwin-arm64@1.0.0-rc.11':
resolution: {integrity: sha512-7WQgR8SfOPwmDZGFkThUvsmd/nwAWv91oCO4I5LS7RKrssPZmOt7jONN0cW17ydGC1n/+puol1IpoieKqQidmg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [darwin]
'@rolldown/binding-darwin-x64@1.0.0-beta.52':
resolution: {integrity: sha512-qpHedvQBmIjT8zdnjN3nWPR2qjQyJttbXniCEKKdHeAbZG9HyNPBUzQF7AZZGwmS9coQKL+hWg9FhWzh2dZ2IA==}
'@rolldown/binding-darwin-x64@1.0.0-rc.11':
resolution: {integrity: sha512-39Ks6UvIHq4rEogIfQBoBRusj0Q0nPVWIvqmwBLaT6aqQGIakHdESBVOPRRLacy4WwUPIx4ZKzfZ9PMW+IeyUQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [darwin]
'@rolldown/binding-freebsd-x64@1.0.0-beta.52':
resolution: {integrity: sha512-dDp7WbPapj/NVW0LSiH/CLwMhmLwwKb3R7mh2kWX+QW85X1DGVnIEyKh9PmNJjB/+suG1dJygdtdNPVXK1hylg==}
'@rolldown/binding-freebsd-x64@1.0.0-rc.11':
resolution: {integrity: sha512-jfsm0ZHfhiqrvWjJAmzsqiIFPz5e7mAoCOPBNTcNgkiid/LaFKiq92+0ojH+nmJmKYkre4t71BWXUZDNp7vsag==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [freebsd]
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.52':
resolution: {integrity: sha512-9e4l6vy5qNSliDPqNfR6CkBOAx6PH7iDV4OJiEJzajajGrVy8gc/IKKJUsoE52G8ud8MX6r3PMl97NfwgOzB7g==}
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.11':
resolution: {integrity: sha512-zjQaUtSyq1nVe3nxmlSCuR96T1LPlpvmJ0SZy0WJFEsV4kFbXcq2u68L4E6O0XeFj4aex9bEauqjW8UQBeAvfQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
'@rolldown/binding-linux-arm64-gnu@1.0.0-beta.52':
resolution: {integrity: sha512-V48oDR84feRU2KRuzpALp594Uqlx27+zFsT6+BgTcXOtu7dWy350J1G28ydoCwKB+oxwsRPx2e7aeQnmd3YJbQ==}
'@rolldown/binding-linux-arm64-gnu@1.0.0-rc.11':
resolution: {integrity: sha512-WMW1yE6IOnehTcFE9eipFkm3XN63zypWlrJQ2iF7NrQ9b2LDRjumFoOGJE8RJJTJCTBAdmLMnJ8uVitACUUo1Q==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
'@rolldown/binding-linux-arm64-musl@1.0.0-beta.52':
resolution: {integrity: sha512-ENLmSQCWqSA/+YN45V2FqTIemg7QspaiTjlm327eUAMeOLdqmSOVVyrQexJGNTQ5M8sDYCgVAig2Kk01Ggmqaw==}
'@rolldown/binding-linux-arm64-musl@1.0.0-rc.11':
resolution: {integrity: sha512-jfndI9tsfm4APzjNt6QdBkYwre5lRPUgHeDHoI7ydKUuJvz3lZeCfMsI56BZj+7BYqiKsJm7cfd/6KYV7ubrBg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
'@rolldown/binding-linux-x64-gnu@1.0.0-beta.52':
resolution: {integrity: sha512-klahlb2EIFltSUubn/VLjuc3qxp1E7th8ukayPfdkcKvvYcQ5rJztgx8JsJSuAKVzKtNTqUGOhy4On71BuyV8g==}
'@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.11':
resolution: {integrity: sha512-ZlFgw46NOAGMgcdvdYwAGu2Q+SLFA9LzbJLW+iyMOJyhj5wk6P3KEE9Gct4xWwSzFoPI7JCdYmYMzVtlgQ+zfw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
'@rolldown/binding-linux-s390x-gnu@1.0.0-rc.11':
resolution: {integrity: sha512-hIOYmuT6ofM4K04XAZd3OzMySEO4K0/nc9+jmNcxNAxRi6c5UWpqfw3KMFV4MVFWL+jQsSh+bGw2VqmaPMTLyw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
'@rolldown/binding-linux-x64-gnu@1.0.0-rc.11':
resolution: {integrity: sha512-qXBQQO9OvkjjQPLdUVr7Nr2t3QTZI7s4KZtfw7HzBgjbmAPSFwSv4rmET9lLSgq3rH/ndA3ngv3Qb8l2njoPNA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
'@rolldown/binding-linux-x64-musl@1.0.0-beta.52':
resolution: {integrity: sha512-UuA+JqQIgqtkgGN2c/AQ5wi8M6mJHrahz/wciENPTeI6zEIbbLGoth5XN+sQe2pJDejEVofN9aOAp0kaazwnVg==}
'@rolldown/binding-linux-x64-musl@1.0.0-rc.11':
resolution: {integrity: sha512-/tpFfoSTzUkH9LPY+cYbqZBDyyX62w5fICq9qzsHLL8uTI6BHip3Q9Uzft0wylk/i8OOwKik8OxW+QAhDmzwmg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
'@rolldown/binding-openharmony-arm64@1.0.0-beta.52':
resolution: {integrity: sha512-1BNQW8u4ro8bsN1+tgKENJiqmvc+WfuaUhXzMImOVSMw28pkBKdfZtX2qJPADV3terx+vNJtlsgSGeb3+W6Jiw==}
'@rolldown/binding-openharmony-arm64@1.0.0-rc.11':
resolution: {integrity: sha512-mcp3Rio2w72IvdZG0oQ4bM2c2oumtwHfUfKncUM6zGgz0KgPz4YmDPQfnXEiY5t3+KD/i8HG2rOB/LxdmieK2g==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [openharmony]
'@rolldown/binding-wasm32-wasi@1.0.0-beta.52':
resolution: {integrity: sha512-K/p7clhCqJOQpXGykrFaBX2Dp9AUVIDHGc+PtFGBwg7V+mvBTv/tsm3LC3aUmH02H2y3gz4y+nUTQ0MLpofEEg==}
'@rolldown/binding-wasm32-wasi@1.0.0-rc.11':
resolution: {integrity: sha512-LXk5Hii1Ph9asuGRjBuz8TUxdc1lWzB7nyfdoRgI0WGPZKmCxvlKk8KfYysqtr4MfGElu/f/pEQRh8fcEgkrWw==}
engines: {node: '>=14.0.0'}
cpu: [wasm32]
'@rolldown/binding-win32-arm64-msvc@1.0.0-beta.52':
resolution: {integrity: sha512-a4EkXBtnYYsKipjS7QOhEBM4bU5IlR9N1hU+JcVEVeuTiaslIyhWVKsvf7K2YkQHyVAJ+7/A9BtrGqORFcTgng==}
'@rolldown/binding-win32-arm64-msvc@1.0.0-rc.11':
resolution: {integrity: sha512-dDwf5otnx0XgRY1yqxOC4ITizcdzS/8cQ3goOWv3jFAo4F+xQYni+hnMuO6+LssHHdJW7+OCVL3CoU4ycnh35Q==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [win32]
'@rolldown/binding-win32-ia32-msvc@1.0.0-beta.52':
resolution: {integrity: sha512-5ZXcYyd4GxPA6QfbGrNcQjmjbuLGvfz6728pZMsQvGHI+06LT06M6TPtXvFvLgXtexc+OqvFe1yAIXJU1gob/w==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ia32]
os: [win32]
'@rolldown/binding-win32-x64-msvc@1.0.0-beta.52':
resolution: {integrity: sha512-tzpnRQXJrSzb8Z9sm97UD3cY0toKOImx+xRKsDLX4zHaAlRXWh7jbaKBePJXEN7gNw7Nm03PBNwphdtA8KSUYQ==}
'@rolldown/binding-win32-x64-msvc@1.0.0-rc.11':
resolution: {integrity: sha512-LN4/skhSggybX71ews7dAj6r2geaMJfm3kMbK2KhFMg9B10AZXnKoLCVVgzhMHL0S+aKtr4p8QbAW8k+w95bAA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [win32]
'@rolldown/pluginutils@1.0.0-beta.52':
resolution: {integrity: sha512-/L0htLJZbaZFL1g9OHOblTxbCYIGefErJjtYOwgl9ZqNx27P3L0SDfjhhHIss32gu5NWgnxuT2a2Hnnv6QGHKA==}
'@rolldown/pluginutils@1.0.0-rc.11':
resolution: {integrity: sha512-xQO9vbwBecJRv9EUcQ/y0dzSTJgA7Q6UVN7xp6B81+tBGSLVAK03yJ9NkJaUA7JFD91kbjxRSC/mDnmvXzbHoQ==}
'@rspack/binding-darwin-arm64@1.7.9':
resolution: {integrity: sha512-64dgstte0If5czi9bA/cpOe0ryY6wC9AIQRtyJ3DlOF6Tt+y9cKkmUoGu3V+WYaYIZRT7HNk8V7kL8amVjFTYw==}
'@rspack/binding-darwin-arm64@1.7.10':
resolution: {integrity: sha512-bsXi7I6TpH+a4L6okIUh1JDvwT+XcK/L7Yvhu5G2t5YYyd2fl5vMM5O9cePRpEb0RdqJZ3Z8i9WIWHap9aQ8Gw==}
cpu: [arm64]
os: [darwin]
'@rspack/binding-darwin-x64@1.7.9':
resolution: {integrity: sha512-2QSLs3w4rLy4UUGVnIlkt6IlIKOzR1e0RPsq2FYQW6s3p9JrwRCtOeHohyh7EJSqF54dtfhe9UZSAwba3LqH1Q==}
'@rspack/binding-darwin-x64@1.7.10':
resolution: {integrity: sha512-h/kOGL1bUflDDYnbiUjaRE9kagJpour4FatGihueV03+cRGQ6jpde+BjUakqzMx65CeDbeYI6jAiPhElnlAtRw==}
cpu: [x64]
os: [darwin]
'@rspack/binding-linux-arm64-gnu@1.7.9':
resolution: {integrity: sha512-qhUGI/uVfvLmKWts4QkVHGL8yfUyJkblZs+OFD5Upa2y676EOsbQgWsCwX4xGB6Tv+TOzFP0SLh/UfO8ZfdE+w==}
'@rspack/binding-linux-arm64-gnu@1.7.10':
resolution: {integrity: sha512-Z4reus7UxGM4+JuhiIht8KuGP1KgM7nNhOlXUHcQCMswP/Rymj5oJQN3TDWgijFUZs09ULl8t3T+AQAVTd/WvA==}
cpu: [arm64]
os: [linux]
'@rspack/binding-linux-arm64-musl@1.7.9':
resolution: {integrity: sha512-VjfmR1hgO9n3L6MaE5KG+DXSrrLVqHHOkVcOtS2LMq3bjMTwbBywY7ycymcLnX5KJsol8d3ZGYep6IfSOt3lFA==}
'@rspack/binding-linux-arm64-musl@1.7.10':
resolution: {integrity: sha512-LYaoVmWizG4oQ3g+St3eM5qxsyfH07kLirP7NJcDMgvu3eQ29MeyTZ3ugkgW6LvlmJue7eTQyf6CZlanoF5SSg==}
cpu: [arm64]
os: [linux]
'@rspack/binding-linux-x64-gnu@1.7.9':
resolution: {integrity: sha512-0kldV+3WTs/VYDWzxJ7K40hCW26IHtnk8xPK3whKoo1649rgeXXa0EdsU5P7hG8Ef5SWQjHHHZ/fuHYSO3Y6HA==}
'@rspack/binding-linux-x64-gnu@1.7.10':
resolution: {integrity: sha512-aIm2G4Kcm3qxDTNqKarK0oaLY2iXnCmpRQQhAcMlR0aS2LmxL89XzVeRr9GFA1MzGrAsZONWCLkxQvn3WUbm4Q==}
cpu: [x64]
os: [linux]
'@rspack/binding-linux-x64-musl@1.7.9':
resolution: {integrity: sha512-Gi4872cFtc2d83FKATR6Qcf2VBa/tFCqffI/IwRRl6Hx5FulEBqx+tH7gAuRVF693vrbXNxK+FQ+k4iEsEJxrw==}
'@rspack/binding-linux-x64-musl@1.7.10':
resolution: {integrity: sha512-SIHQbAgB9IPH0H3H+i5rN5jo9yA/yTMq8b7XfRkTMvZ7P7MXxJ0dE8EJu3BmCLM19sqnTc2eX+SVfE8ZMDzghA==}
cpu: [x64]
os: [linux]
'@rspack/binding-wasm32-wasi@1.7.9':
resolution: {integrity: sha512-5QEzqo6EaolpuZmK6w/mgSueorgGnnzp7dJaAvBj6ECFIg/aLXhXXmWCWbxt7Ws2gKvG5/PgaxDqbUxYL51juA==}
'@rspack/binding-wasm32-wasi@1.7.10':
resolution: {integrity: sha512-J9HDXHD1tj+9FmX4+K3CTkO7dCE2bootlR37YuC2Owc0Lwl1/i2oGT71KHnMqI9faF/hipAaQM5OywkiiuNB7w==}
cpu: [wasm32]
'@rspack/binding-win32-arm64-msvc@1.7.9':
resolution: {integrity: sha512-MMqvcrIc8aOqTuHjWkjdzilvoZ3Hv07Od0Foogiyq3JMudsS3Wcmh7T1dFerGg19MOJcRUeEkrg2NQOMOQ6xDA==}
'@rspack/binding-win32-arm64-msvc@1.7.10':
resolution: {integrity: sha512-FaQGSCXH89nMOYW0bVp0bKQDQbrOEFFm7yedla7g6mkWlFVQo5UyBxid5wJUCqGJBtJepRxeRfByWiaI5nVGvg==}
cpu: [arm64]
os: [win32]
'@rspack/binding-win32-ia32-msvc@1.7.9':
resolution: {integrity: sha512-4kYYS+NZ2CuNbKjq40yB/UEyB51o1PHj5wpr+Y943oOJXpEKWU2Q4vkF8VEohPEcnA9cKVotYCnqStme+02suA==}
'@rspack/binding-win32-ia32-msvc@1.7.10':
resolution: {integrity: sha512-/66TNLOeM4R5dHhRWRVbMTgWghgxz+32ym0c/zGGXQRoMbz7210EoL40ALUgdBdeeREO8LoV+Mn7v8/QZCwHzw==}
cpu: [ia32]
os: [win32]
'@rspack/binding-win32-x64-msvc@1.7.9':
resolution: {integrity: sha512-1g+QyXXvs+838Un/4GaUvJfARDGHMCs15eXDYWBl5m/Skubyng8djWAgr6ag1+cVoJZXCPOvybTItcblWF3gbQ==}
'@rspack/binding-win32-x64-msvc@1.7.10':
resolution: {integrity: sha512-SUa3v1W7PGFCy6AHRmDsm43/tkfaZFi1TN2oIk5aCdT9T51baDVBjAbehRDu9xFbK4piL3k7uqIVSIrKgVqk1g==}
cpu: [x64]
os: [win32]
'@rspack/binding@1.7.9':
resolution: {integrity: sha512-A56e0NdfNwbOSJoilMkxzaPuVYaKCNn1shuiwWnCIBmhV9ix1n9S1XvquDjkGyv+gCdR1+zfJBOa5DMB7htLHw==}
'@rspack/binding@1.7.10':
resolution: {integrity: sha512-j+DPEaSJLRgasxXNpYQpvC7wUkQF5WoWPiTfm4fLczwlAmYwGSVkJiyWDrOlvVPiGGYiXIaXEjVWTw6fT6/vnA==}
'@rspack/core@1.7.9':
resolution: {integrity: sha512-VHuSKvRkuv42Ya+TxEGO0LE0r9+8P4tKGokmomj4R1f/Nu2vtS3yoaIMfC4fR6VuHGd3MZ+KTI0cNNwHfFcskw==}
'@rspack/core@1.7.10':
resolution: {integrity: sha512-dO7J0aHSa9Fg2kGT0+ZsM500lMdlNIyCHavIaz7dTDn6KXvFz1qbWQ/48x3OlNFw1mA0jxAjjw9e7h3sWQZUNg==}
engines: {node: '>=18.12.0'}
peerDependencies:
'@swc/helpers': '>=0.5.1'
@@ -2933,8 +2942,8 @@ packages:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
hasBin: true
rolldown@1.0.0-beta.52:
resolution: {integrity: sha512-Hbnpljue+JhMJrlOjQ1ixp9me7sUec7OjFvS+A1Qm8k8Xyxmw3ZhxFu7LlSXW1s9AX3POE9W9o2oqCEeR5uDmg==}
rolldown@1.0.0-rc.11:
resolution: {integrity: sha512-NRjoKMusSjfRbSYiH3VSumlkgFe7kYAa3pzVOsVYVFY3zb5d7nS+a3KGQ7hJKXuYWbzJKPVQ9Wxq2UvyK+ENpw==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
@@ -2995,8 +3004,8 @@ packages:
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
smol-toml@1.6.0:
resolution: {integrity: sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==}
smol-toml@1.6.1:
resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==}
engines: {node: '>= 18'}
socks-proxy-agent@8.0.5:
@@ -3162,8 +3171,8 @@ packages:
typed-query-selector@2.12.1:
resolution: {integrity: sha512-uzR+FzI8qrUEIu96oaeBJmd9E7CFEiQ3goA5qCVgc4s5llSubcfGHq9yUstZx/k4s9dXHVKsE35YWoFyvEqEHA==}
typescript@5.9.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
typescript@6.0.2:
resolution: {integrity: sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==}
engines: {node: '>=14.17'}
hasBin: true
@@ -3275,6 +3284,18 @@ packages:
utf-8-validate:
optional: true
ws@8.20.0:
resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==}
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
xml-parse-from-string@1.0.1:
resolution: {integrity: sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==}
@@ -3938,9 +3959,9 @@ snapshots:
'@esbuild/win32-x64@0.27.4':
optional: true
'@git.zone/tsbuild@4.3.0':
'@git.zone/tsbuild@4.4.0':
dependencies:
'@git.zone/tspublish': 1.11.2
'@git.zone/tspublish': 1.11.5
'@push.rocks/early': 4.0.4
'@push.rocks/smartcli': 4.0.20
'@push.rocks/smartdelay': 3.0.5
@@ -3949,7 +3970,7 @@ snapshots:
'@push.rocks/smartlog': 3.2.1
'@push.rocks/smartpath': 6.0.0
'@push.rocks/smartpromise': 4.2.3
typescript: 5.9.3
typescript: 6.0.2
transitivePeerDependencies:
- '@nuxt/kit'
- aws-crt
@@ -3960,11 +3981,11 @@ snapshots:
- supports-color
- vue
'@git.zone/tsbundle@2.9.1':
'@git.zone/tsbundle@2.10.0':
dependencies:
'@push.rocks/early': 4.0.4
'@push.rocks/npmextra': 5.3.3
'@push.rocks/smartcli': 4.0.20
'@push.rocks/smartconfig': 6.1.0
'@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartfs': 1.5.0
'@push.rocks/smartinteract': 2.0.16
@@ -3973,12 +3994,12 @@ snapshots:
'@push.rocks/smartpath': 6.0.0
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartspawn': 3.0.3
'@rspack/core': 1.7.9
'@rspack/core': 1.7.10
'@types/html-minifier': 4.0.6
esbuild: 0.27.4
html-minifier: 4.0.0
rolldown: 1.0.0-beta.52
typescript: 5.9.3
rolldown: 1.0.0-rc.11
typescript: 6.0.2
transitivePeerDependencies:
- '@nuxt/kit'
- '@swc/helpers'
@@ -3986,11 +4007,11 @@ snapshots:
- supports-color
- vue
'@git.zone/tspublish@1.11.2':
'@git.zone/tspublish@1.11.5':
dependencies:
'@push.rocks/consolecolor': 2.0.3
'@push.rocks/npmextra': 5.3.3
'@push.rocks/smartcli': 4.0.20
'@push.rocks/smartconfig': 6.1.0
'@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartfile': 13.1.2
'@push.rocks/smartfs': 1.5.0
@@ -4009,34 +4030,34 @@ snapshots:
- supports-color
- vue
'@git.zone/tsrun@2.0.1':
'@git.zone/tsrun@2.0.2':
dependencies:
'@push.rocks/smartfile': 13.1.2
'@push.rocks/smartshell': 3.3.8
tsx: 4.21.0
'@git.zone/tsrust@1.3.0':
'@git.zone/tsrust@1.3.2':
dependencies:
'@push.rocks/early': 4.0.4
'@push.rocks/npmextra': 5.3.3
'@push.rocks/smartcli': 4.0.20
'@push.rocks/smartconfig': 6.1.0
'@push.rocks/smartfile': 13.1.2
'@push.rocks/smartpath': 6.0.0
'@push.rocks/smartshell': 3.3.8
smol-toml: 1.6.0
smol-toml: 1.6.1
transitivePeerDependencies:
- '@nuxt/kit'
- react
- supports-color
- vue
'@git.zone/tstest@3.5.0(socks@2.8.7)(typescript@5.9.3)':
'@git.zone/tstest@3.6.0(socks@2.8.7)(typescript@6.0.2)':
dependencies:
'@git.zone/tsbundle': 2.9.1
'@git.zone/tsrun': 2.0.1
'@git.zone/tsbundle': 2.10.0
'@git.zone/tsrun': 2.0.2
'@push.rocks/consolecolor': 2.0.3
'@push.rocks/qenv': 6.1.3
'@push.rocks/smartbrowser': 2.0.11(typescript@5.9.3)
'@push.rocks/smartbrowser': 2.0.11(typescript@6.0.2)
'@push.rocks/smartcrypto': 2.0.4
'@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartenv': 6.0.0
@@ -4050,14 +4071,14 @@ snapshots:
'@push.rocks/smartpath': 6.0.0
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartrequest': 5.0.1
'@push.rocks/smartserve': 2.0.1
'@push.rocks/smartserve': 2.0.3
'@push.rocks/smartshell': 3.3.8
'@push.rocks/smartstorage': 6.0.1
'@push.rocks/smartstorage': 6.3.2
'@push.rocks/smarttime': 4.2.3
'@push.rocks/smartwatch': 6.3.0
'@push.rocks/smartwatch': 6.4.0
'@types/ws': 8.18.1
figures: 6.1.0
ws: 8.19.0
ws: 8.20.0
transitivePeerDependencies:
- '@aws-sdk/credential-providers'
- '@mongodb-js/zstd'
@@ -4513,7 +4534,7 @@ snapshots:
'@tybys/wasm-util': 0.10.1
optional: true
'@oxc-project/types@0.99.0': {}
'@oxc-project/types@0.122.0': {}
'@pdf-lib/standard-fonts@1.0.0':
dependencies:
@@ -4684,6 +4705,15 @@ snapshots:
'@types/symbol-tree': 3.2.5
symbol-tree: 3.2.4
'@push.rocks/lik@6.4.0':
dependencies:
'@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartmatch': 2.0.0
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartrx': 3.0.10
'@push.rocks/smarttime': 4.2.3
symbol-tree: 3.2.4
'@push.rocks/mongodump@1.1.0(socks@2.8.7)':
dependencies:
'@push.rocks/lik': 6.3.1
@@ -4702,23 +4732,6 @@ snapshots:
- snappy
- socks
'@push.rocks/npmextra@5.3.3':
dependencies:
'@push.rocks/qenv': 6.1.3
'@push.rocks/smartfile': 11.2.7
'@push.rocks/smartjson': 5.2.0
'@push.rocks/smartlog': 3.2.1
'@push.rocks/smartpath': 6.0.0
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartrx': 3.0.10
'@push.rocks/taskbuffer': 3.5.0
'@tsclass/tsclass': 9.5.0
transitivePeerDependencies:
- '@nuxt/kit'
- react
- supports-color
- vue
'@push.rocks/qenv@6.1.3':
dependencies:
'@api.global/typedrequest': 3.2.5
@@ -4748,11 +4761,11 @@ snapshots:
- react-native-b4a
- supports-color
'@push.rocks/smartbrowser@2.0.11(typescript@5.9.3)':
'@push.rocks/smartbrowser@2.0.11(typescript@6.0.2)':
dependencies:
'@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartpdf': 4.2.0(typescript@5.9.3)
'@push.rocks/smartpuppeteer': 2.0.5(typescript@5.9.3)
'@push.rocks/smartpdf': 4.2.0(typescript@6.0.2)
'@push.rocks/smartpuppeteer': 2.0.5(typescript@6.0.2)
'@push.rocks/smartunique': 3.0.9
transitivePeerDependencies:
- '@nuxt/kit'
@@ -4811,6 +4824,23 @@ snapshots:
'@push.rocks/smarturl': 3.1.0
'@push.rocks/webrequest': 4.0.5
'@push.rocks/smartconfig@6.1.0':
dependencies:
'@push.rocks/qenv': 6.1.3
'@push.rocks/smartfile': 11.2.7
'@push.rocks/smartjson': 5.2.0
'@push.rocks/smartlog': 3.2.1
'@push.rocks/smartpath': 6.0.0
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartrx': 3.0.10
'@push.rocks/taskbuffer': 3.5.0
'@tsclass/tsclass': 9.5.0
transitivePeerDependencies:
- '@nuxt/kit'
- react
- supports-color
- vue
'@push.rocks/smartcrypto@2.0.4':
dependencies:
'@push.rocks/smartpromise': 4.2.3
@@ -5131,7 +5161,7 @@ snapshots:
'@push.rocks/smartpath@6.0.0': {}
'@push.rocks/smartpdf@4.2.0(typescript@5.9.3)':
'@push.rocks/smartpdf@4.2.0(typescript@6.0.2)':
dependencies:
'@push.rocks/smartbuffer': 3.0.5
'@push.rocks/smartdelay': 3.0.5
@@ -5140,8 +5170,8 @@ snapshots:
'@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/smartpuppeteer': 2.0.5(typescript@6.0.2)
'@push.rocks/smartserve': 2.0.3
'@push.rocks/smartunique': 3.0.9
'@tsclass/tsclass': 9.5.0
pdf-lib: 1.17.1
@@ -5166,11 +5196,11 @@ snapshots:
'@push.rocks/smartpromise@4.2.3': {}
'@push.rocks/smartpuppeteer@2.0.5(typescript@5.9.3)':
'@push.rocks/smartpuppeteer@2.0.5(typescript@6.0.2)':
dependencies:
'@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartshell': 3.3.8
puppeteer: 24.40.0(typescript@5.9.3)
puppeteer: 24.40.0(typescript@6.0.2)
tree-kill: 1.2.2
transitivePeerDependencies:
- bare-abort-controller
@@ -5221,7 +5251,7 @@ snapshots:
'@push.rocks/smartpromise': 4.2.3
rxjs: 7.8.2
'@push.rocks/smartserve@2.0.1':
'@push.rocks/smartserve@2.0.3':
dependencies:
'@api.global/typedrequest': 3.2.5
'@cfworker/json-schema': 4.1.1
@@ -5260,7 +5290,7 @@ snapshots:
'@push.rocks/smartrx': 3.0.10
'@push.rocks/webstore': 2.0.20
'@push.rocks/smartstorage@6.0.1':
'@push.rocks/smartstorage@6.3.2':
dependencies:
'@push.rocks/smartpath': 6.0.0
'@push.rocks/smartrust': 1.3.2
@@ -5301,11 +5331,12 @@ snapshots:
'@types/semver': 7.7.1
semver: 7.7.4
'@push.rocks/smartwatch@6.3.0':
'@push.rocks/smartwatch@6.4.0':
dependencies:
'@push.rocks/lik': 6.3.1
'@push.rocks/lik': 6.4.0
'@push.rocks/smartenv': 6.0.0
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartrust': 1.3.2
'@push.rocks/smartrx': 3.0.10
chokidar: 5.0.0
picomatch: 4.0.3
@@ -5374,101 +5405,104 @@ snapshots:
'@pushrocks/smartpromise@4.0.2': {}
'@rolldown/binding-android-arm64@1.0.0-beta.52':
'@rolldown/binding-android-arm64@1.0.0-rc.11':
optional: true
'@rolldown/binding-darwin-arm64@1.0.0-beta.52':
'@rolldown/binding-darwin-arm64@1.0.0-rc.11':
optional: true
'@rolldown/binding-darwin-x64@1.0.0-beta.52':
'@rolldown/binding-darwin-x64@1.0.0-rc.11':
optional: true
'@rolldown/binding-freebsd-x64@1.0.0-beta.52':
'@rolldown/binding-freebsd-x64@1.0.0-rc.11':
optional: true
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.52':
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.11':
optional: true
'@rolldown/binding-linux-arm64-gnu@1.0.0-beta.52':
'@rolldown/binding-linux-arm64-gnu@1.0.0-rc.11':
optional: true
'@rolldown/binding-linux-arm64-musl@1.0.0-beta.52':
'@rolldown/binding-linux-arm64-musl@1.0.0-rc.11':
optional: true
'@rolldown/binding-linux-x64-gnu@1.0.0-beta.52':
'@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.11':
optional: true
'@rolldown/binding-linux-x64-musl@1.0.0-beta.52':
'@rolldown/binding-linux-s390x-gnu@1.0.0-rc.11':
optional: true
'@rolldown/binding-openharmony-arm64@1.0.0-beta.52':
'@rolldown/binding-linux-x64-gnu@1.0.0-rc.11':
optional: true
'@rolldown/binding-wasm32-wasi@1.0.0-beta.52':
'@rolldown/binding-linux-x64-musl@1.0.0-rc.11':
optional: true
'@rolldown/binding-openharmony-arm64@1.0.0-rc.11':
optional: true
'@rolldown/binding-wasm32-wasi@1.0.0-rc.11':
dependencies:
'@napi-rs/wasm-runtime': 1.1.1
optional: true
'@rolldown/binding-win32-arm64-msvc@1.0.0-beta.52':
'@rolldown/binding-win32-arm64-msvc@1.0.0-rc.11':
optional: true
'@rolldown/binding-win32-ia32-msvc@1.0.0-beta.52':
'@rolldown/binding-win32-x64-msvc@1.0.0-rc.11':
optional: true
'@rolldown/binding-win32-x64-msvc@1.0.0-beta.52':
'@rolldown/pluginutils@1.0.0-rc.11': {}
'@rspack/binding-darwin-arm64@1.7.10':
optional: true
'@rolldown/pluginutils@1.0.0-beta.52': {}
'@rspack/binding-darwin-arm64@1.7.9':
'@rspack/binding-darwin-x64@1.7.10':
optional: true
'@rspack/binding-darwin-x64@1.7.9':
'@rspack/binding-linux-arm64-gnu@1.7.10':
optional: true
'@rspack/binding-linux-arm64-gnu@1.7.9':
'@rspack/binding-linux-arm64-musl@1.7.10':
optional: true
'@rspack/binding-linux-arm64-musl@1.7.9':
'@rspack/binding-linux-x64-gnu@1.7.10':
optional: true
'@rspack/binding-linux-x64-gnu@1.7.9':
'@rspack/binding-linux-x64-musl@1.7.10':
optional: true
'@rspack/binding-linux-x64-musl@1.7.9':
optional: true
'@rspack/binding-wasm32-wasi@1.7.9':
'@rspack/binding-wasm32-wasi@1.7.10':
dependencies:
'@napi-rs/wasm-runtime': 1.0.7
optional: true
'@rspack/binding-win32-arm64-msvc@1.7.9':
'@rspack/binding-win32-arm64-msvc@1.7.10':
optional: true
'@rspack/binding-win32-ia32-msvc@1.7.9':
'@rspack/binding-win32-ia32-msvc@1.7.10':
optional: true
'@rspack/binding-win32-x64-msvc@1.7.9':
'@rspack/binding-win32-x64-msvc@1.7.10':
optional: true
'@rspack/binding@1.7.9':
'@rspack/binding@1.7.10':
optionalDependencies:
'@rspack/binding-darwin-arm64': 1.7.9
'@rspack/binding-darwin-x64': 1.7.9
'@rspack/binding-linux-arm64-gnu': 1.7.9
'@rspack/binding-linux-arm64-musl': 1.7.9
'@rspack/binding-linux-x64-gnu': 1.7.9
'@rspack/binding-linux-x64-musl': 1.7.9
'@rspack/binding-wasm32-wasi': 1.7.9
'@rspack/binding-win32-arm64-msvc': 1.7.9
'@rspack/binding-win32-ia32-msvc': 1.7.9
'@rspack/binding-win32-x64-msvc': 1.7.9
'@rspack/binding-darwin-arm64': 1.7.10
'@rspack/binding-darwin-x64': 1.7.10
'@rspack/binding-linux-arm64-gnu': 1.7.10
'@rspack/binding-linux-arm64-musl': 1.7.10
'@rspack/binding-linux-x64-gnu': 1.7.10
'@rspack/binding-linux-x64-musl': 1.7.10
'@rspack/binding-wasm32-wasi': 1.7.10
'@rspack/binding-win32-arm64-msvc': 1.7.10
'@rspack/binding-win32-ia32-msvc': 1.7.10
'@rspack/binding-win32-x64-msvc': 1.7.10
'@rspack/core@1.7.9':
'@rspack/core@1.7.10':
dependencies:
'@module-federation/runtime-tools': 0.22.0
'@rspack/binding': 1.7.9
'@rspack/binding': 1.7.10
'@rspack/lite-tapable': 1.1.0
'@rspack/lite-tapable@1.1.0': {}
@@ -6191,14 +6225,14 @@ snapshots:
ini: 1.3.8
proto-list: 1.2.4
cosmiconfig@9.0.1(typescript@5.9.3):
cosmiconfig@9.0.1(typescript@6.0.2):
dependencies:
env-paths: 2.2.1
import-fresh: 3.3.1
js-yaml: 4.1.1
parse-json: 5.2.0
optionalDependencies:
typescript: 5.9.3
typescript: 6.0.2
croner@10.0.1: {}
@@ -7438,7 +7472,7 @@ snapshots:
devtools-protocol: 0.0.1581282
typed-query-selector: 2.12.1
webdriver-bidi-protocol: 0.4.1
ws: 8.19.0
ws: 8.20.0
transitivePeerDependencies:
- bare-abort-controller
- bare-buffer
@@ -7447,11 +7481,11 @@ snapshots:
- supports-color
- utf-8-validate
puppeteer@24.40.0(typescript@5.9.3):
puppeteer@24.40.0(typescript@6.0.2):
dependencies:
'@puppeteer/browsers': 2.13.0
chromium-bidi: 14.0.0(devtools-protocol@0.0.1581282)
cosmiconfig: 9.0.1(typescript@5.9.3)
cosmiconfig: 9.0.1(typescript@6.0.2)
devtools-protocol: 0.0.1581282
puppeteer-core: 24.40.0
typed-query-selector: 2.12.1
@@ -7570,25 +7604,26 @@ snapshots:
dependencies:
glob: 7.2.3
rolldown@1.0.0-beta.52:
rolldown@1.0.0-rc.11:
dependencies:
'@oxc-project/types': 0.99.0
'@rolldown/pluginutils': 1.0.0-beta.52
'@oxc-project/types': 0.122.0
'@rolldown/pluginutils': 1.0.0-rc.11
optionalDependencies:
'@rolldown/binding-android-arm64': 1.0.0-beta.52
'@rolldown/binding-darwin-arm64': 1.0.0-beta.52
'@rolldown/binding-darwin-x64': 1.0.0-beta.52
'@rolldown/binding-freebsd-x64': 1.0.0-beta.52
'@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.52
'@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.52
'@rolldown/binding-linux-arm64-musl': 1.0.0-beta.52
'@rolldown/binding-linux-x64-gnu': 1.0.0-beta.52
'@rolldown/binding-linux-x64-musl': 1.0.0-beta.52
'@rolldown/binding-openharmony-arm64': 1.0.0-beta.52
'@rolldown/binding-wasm32-wasi': 1.0.0-beta.52
'@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.52
'@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.52
'@rolldown/binding-win32-x64-msvc': 1.0.0-beta.52
'@rolldown/binding-android-arm64': 1.0.0-rc.11
'@rolldown/binding-darwin-arm64': 1.0.0-rc.11
'@rolldown/binding-darwin-x64': 1.0.0-rc.11
'@rolldown/binding-freebsd-x64': 1.0.0-rc.11
'@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.11
'@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.11
'@rolldown/binding-linux-arm64-musl': 1.0.0-rc.11
'@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.11
'@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.11
'@rolldown/binding-linux-x64-gnu': 1.0.0-rc.11
'@rolldown/binding-linux-x64-musl': 1.0.0-rc.11
'@rolldown/binding-openharmony-arm64': 1.0.0-rc.11
'@rolldown/binding-wasm32-wasi': 1.0.0-rc.11
'@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.11
'@rolldown/binding-win32-x64-msvc': 1.0.0-rc.11
run-async@3.0.0: {}
@@ -7660,7 +7695,7 @@ snapshots:
smart-buffer@4.2.0: {}
smol-toml@1.6.0: {}
smol-toml@1.6.1: {}
socks-proxy-agent@8.0.5:
dependencies:
@@ -7861,7 +7896,7 @@ snapshots:
typed-query-selector@2.12.1: {}
typescript@5.9.3: {}
typescript@6.0.2: {}
uglify-js@3.19.3: {}
@@ -7963,6 +7998,8 @@ snapshots:
ws@8.19.0: {}
ws@8.20.0: {}
xml-parse-from-string@1.0.1: {}
xml2js@0.5.0:

1
rust/Cargo.lock generated
View File

@@ -1270,6 +1270,7 @@ dependencies = [
"arc-swap",
"bytes",
"dashmap",
"futures",
"h3",
"h3-quinn",
"http-body",

View File

@@ -30,3 +30,4 @@ socket2 = { workspace = true }
quinn = { workspace = true }
h3 = { workspace = true }
h3-quinn = { workspace = true }
futures = { version = "0.3", default-features = false, features = ["std"] }

View File

@@ -56,7 +56,11 @@ struct PooledH2 {
}
/// A pooled QUIC/HTTP/3 connection (multiplexed like H2).
/// Stores the h3 `SendRequest` handle so pool hits skip the h3 SETTINGS handshake.
pub struct PooledH3 {
/// Multiplexed h3 request handle — clone to open a new stream.
pub send_request: h3::client::SendRequest<h3_quinn::OpenStreams, Bytes>,
/// Raw QUIC connection — kept for liveness probing (close_reason) only.
pub connection: quinn::Connection,
pub created_at: Instant,
pub generation: u64,
@@ -197,7 +201,10 @@ impl ConnectionPool {
/// Try to get a pooled QUIC connection for the given key.
/// QUIC connections are multiplexed — the connection is shared, not removed.
pub fn checkout_h3(&self, key: &PoolKey) -> Option<(quinn::Connection, Duration)> {
pub fn checkout_h3(
&self,
key: &PoolKey,
) -> Option<(h3::client::SendRequest<h3_quinn::OpenStreams, Bytes>, quinn::Connection, Duration)> {
let entry = self.h3_pool.get(key)?;
let pooled = entry.value();
let age = pooled.created_at.elapsed();
@@ -215,13 +222,20 @@ impl ConnectionPool {
return None;
}
Some((pooled.connection.clone(), age))
Some((pooled.send_request.clone(), pooled.connection.clone(), age))
}
/// Register a QUIC connection in the pool. Returns the generation ID.
pub fn register_h3(&self, key: PoolKey, connection: quinn::Connection) -> u64 {
/// Register a QUIC connection and its h3 SendRequest handle in the pool.
/// Returns the generation ID.
pub fn register_h3(
&self,
key: PoolKey,
connection: quinn::Connection,
send_request: h3::client::SendRequest<h3_quinn::OpenStreams, Bytes>,
) -> u64 {
let gen = self.h2_generation.fetch_add(1, Ordering::Relaxed);
self.h3_pool.insert(key, PooledH3 {
send_request,
connection,
created_at: Instant::now(),
generation: gen,

View File

@@ -116,7 +116,7 @@ async fn handle_h3_request(
cancel: CancellationToken,
) -> anyhow::Result<()> {
// Stream request body from H3 client via an mpsc channel.
let (body_tx, body_rx) = tokio::sync::mpsc::channel::<Bytes>(4);
let (body_tx, body_rx) = tokio::sync::mpsc::channel::<Bytes>(32);
// Spawn the H3 body reader task with cancellation
let body_cancel = cancel.clone();
@@ -132,8 +132,7 @@ async fn handle_h3_request(
}
};
let mut chunk = chunk;
let data = Bytes::copy_from_slice(chunk.chunk());
chunk.advance(chunk.remaining());
let data = chunk.copy_to_bytes(chunk.remaining());
if body_tx.send(data).await.is_err() {
break;
}
@@ -179,8 +178,8 @@ async fn handle_h3_request(
while let Some(frame) = resp_body.frame().await {
match frame {
Ok(frame) => {
if let Some(data) = frame.data_ref() {
stream.send_data(Bytes::copy_from_slice(data)).await
if let Ok(data) = frame.into_data() {
stream.send_data(data).await
.map_err(|e| anyhow::anyhow!("Failed to send H3 data: {}", e))?;
}
}

View File

@@ -47,6 +47,8 @@ pub struct ConnActivity {
/// checks the backend's original response headers for Alt-Svc before our
/// ResponseFilter injects its own. None when not in auto-detect mode or after H3 failure.
alt_svc_cache_key: Option<crate::protocol_cache::ProtocolCacheKey>,
/// The upstream request path that triggered Alt-Svc discovery. Logged for traceability.
alt_svc_request_url: Option<String>,
}
impl ConnActivity {
@@ -58,6 +60,7 @@ impl ConnActivity {
start: std::time::Instant::now(),
active_requests: None,
alt_svc_cache_key: None,
alt_svc_request_url: None,
}
}
}
@@ -69,15 +72,16 @@ const DEFAULT_CONNECT_TIMEOUT: std::time::Duration = std::time::Duration::from_s
/// If no new request arrives within this duration, the connection is closed.
const DEFAULT_HTTP_IDLE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(60);
/// Default HTTP max connection lifetime (1 hour).
/// HTTP connections are forcefully closed after this duration regardless of activity.
const DEFAULT_HTTP_MAX_LIFETIME: std::time::Duration = std::time::Duration::from_secs(3600);
/// Default WebSocket inactivity timeout (1 hour).
const DEFAULT_WS_INACTIVITY_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3600);
/// Default WebSocket max lifetime (24 hours).
const DEFAULT_WS_MAX_LIFETIME: std::time::Duration = std::time::Duration::from_secs(86400);
/// Timeout for QUIC (H3) backend connections. Short because UDP is often firewalled.
const QUIC_CONNECT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3);
/// Protocol decision for backend connection.
#[derive(Debug)]
enum ProtocolDecision {
@@ -219,6 +223,8 @@ pub struct HttpProxyService {
protocol_cache: Arc<crate::protocol_cache::ProtocolCache>,
/// HTTP keep-alive idle timeout: close connection if no new request arrives within this duration.
http_idle_timeout: std::time::Duration,
/// HTTP max connection lifetime: forcefully close connection after this duration regardless of activity.
http_max_lifetime: std::time::Duration,
/// WebSocket inactivity timeout (no data in either direction).
ws_inactivity_timeout: std::time::Duration,
/// WebSocket maximum connection lifetime.
@@ -245,6 +251,7 @@ impl HttpProxyService {
connection_pool: Arc::new(crate::connection_pool::ConnectionPool::new()),
protocol_cache: Arc::new(crate::protocol_cache::ProtocolCache::new()),
http_idle_timeout: DEFAULT_HTTP_IDLE_TIMEOUT,
http_max_lifetime: DEFAULT_HTTP_MAX_LIFETIME,
ws_inactivity_timeout: DEFAULT_WS_INACTIVITY_TIMEOUT,
ws_max_lifetime: DEFAULT_WS_MAX_LIFETIME,
quinn_client_endpoint: Arc::new(Self::create_quinn_client_endpoint()),
@@ -272,21 +279,24 @@ impl HttpProxyService {
connection_pool: Arc::new(crate::connection_pool::ConnectionPool::new()),
protocol_cache: Arc::new(crate::protocol_cache::ProtocolCache::new()),
http_idle_timeout: DEFAULT_HTTP_IDLE_TIMEOUT,
http_max_lifetime: DEFAULT_HTTP_MAX_LIFETIME,
ws_inactivity_timeout: DEFAULT_WS_INACTIVITY_TIMEOUT,
ws_max_lifetime: DEFAULT_WS_MAX_LIFETIME,
quinn_client_endpoint: Arc::new(Self::create_quinn_client_endpoint()),
}
}
/// Set the HTTP keep-alive idle timeout, WebSocket inactivity timeout, and
/// WebSocket max lifetime from connection config values.
/// Set the HTTP keep-alive idle timeout, HTTP max lifetime, WebSocket inactivity
/// timeout, and WebSocket max lifetime from connection config values.
pub fn set_connection_timeouts(
&mut self,
http_idle_timeout: std::time::Duration,
http_max_lifetime: std::time::Duration,
ws_inactivity_timeout: std::time::Duration,
ws_max_lifetime: std::time::Duration,
) {
self.http_idle_timeout = http_idle_timeout;
self.http_max_lifetime = http_max_lifetime;
self.ws_inactivity_timeout = ws_inactivity_timeout;
self.ws_max_lifetime = ws_max_lifetime;
}
@@ -311,6 +321,15 @@ impl HttpProxyService {
self.protocol_cache.clear();
}
/// Clean up expired entries in all per-route rate limiters.
/// Called from the background sampling task to prevent unbounded growth
/// when traffic stops after a burst of unique IPs.
pub fn cleanup_all_rate_limiters(&self) {
for entry in self.route_rate_limiters.iter() {
entry.value().cleanup();
}
}
/// Snapshot the protocol cache for metrics/UI display.
pub fn protocol_cache_snapshot(&self) -> Vec<crate::protocol_cache::ProtocolCacheEntry> {
self.protocol_cache.snapshot()
@@ -351,6 +370,7 @@ impl HttpProxyService {
// Capture timeouts before `self` is moved into the service closure.
let idle_timeout = self.http_idle_timeout;
let max_lifetime = self.http_max_lifetime;
// Activity tracker: updated at the START and END of each request.
// The idle watchdog checks this to determine if the connection is idle
@@ -371,7 +391,7 @@ impl HttpProxyService {
let cn = cancel_inner.clone();
let la = Arc::clone(&la_inner);
let st = start;
let ca = ConnActivity { last_activity: Arc::clone(&la_inner), start, active_requests: Some(Arc::clone(&ar_inner)), alt_svc_cache_key: None };
let ca = ConnActivity { last_activity: Arc::clone(&la_inner), start, active_requests: Some(Arc::clone(&ar_inner)), alt_svc_cache_key: None, alt_svc_request_url: None };
async move {
let req = req.map(|body| BoxBody::new(body));
let result = svc.handle_request(req, peer, port, cn, ca).await;
@@ -409,15 +429,23 @@ impl HttpProxyService {
}
}
_ = async {
// Idle watchdog: check every 5s whether the connection has been idle
// (no active requests AND no activity for idle_timeout).
// This avoids killing long-running requests or upgraded connections.
// Idle + lifetime watchdog: check every 5s whether the connection has been
// idle (no active requests AND no activity for idle_timeout) or exceeded
// the max connection lifetime.
let check_interval = std::time::Duration::from_secs(5);
let mut last_seen = 0u64;
loop {
tokio::time::sleep(check_interval).await;
// Never close while a request is in progress
// Check max connection lifetime (unconditional — even active connections
// must eventually be recycled to prevent resource accumulation).
if start.elapsed() >= max_lifetime {
debug!("HTTP connection exceeded max lifetime ({}s) from {}",
max_lifetime.as_secs(), peer_addr);
return;
}
// Never close for idleness while a request is in progress
if active_requests.load(Ordering::Relaxed) > 0 {
last_seen = last_activity.load(Ordering::Relaxed);
continue;
@@ -434,7 +462,7 @@ impl HttpProxyService {
last_seen = current;
}
} => {
debug!("HTTP connection idle timeout ({}s) from {}", idle_timeout.as_secs(), peer_addr);
debug!("HTTP connection timeout from {}", peer_addr);
conn.as_mut().graceful_shutdown();
// Give any in-flight work 5s to drain after graceful shutdown
let _ = tokio::time::timeout(std::time::Duration::from_secs(5), conn).await;
@@ -775,6 +803,7 @@ impl HttpProxyService {
// the backend's original Alt-Svc header before ResponseFilter injects our own.
if is_auto_detect_mode {
conn_activity.alt_svc_cache_key = Some(protocol_cache_key.clone());
conn_activity.alt_svc_request_url = Some(upstream_path.to_string());
}
// --- H3 path: try QUIC connection before TCP ---
@@ -787,10 +816,10 @@ impl HttpProxyService {
};
// Try H3 pool checkout first
if let Some((quic_conn, _age)) = self.connection_pool.checkout_h3(&h3_pool_key) {
if let Some((pooled_sr, quic_conn, _age)) = self.connection_pool.checkout_h3(&h3_pool_key) {
self.metrics.backend_pool_hit(&upstream_key);
let result = self.forward_h3(
quic_conn, parts, body, upstream_headers, &upstream_path,
quic_conn, Some(pooled_sr), parts, body, upstream_headers, &upstream_path,
route_match.route, route_id, &ip_str, &h3_pool_key, domain_str, &conn_activity, &upstream_key,
).await;
self.upstream_selector.connection_ended(&upstream_key);
@@ -803,7 +832,7 @@ impl HttpProxyService {
self.metrics.backend_pool_miss(&upstream_key);
self.metrics.backend_connection_opened(&upstream_key, std::time::Instant::now().elapsed());
let result = self.forward_h3(
quic_conn, parts, body, upstream_headers, &upstream_path,
quic_conn, None, parts, body, upstream_headers, &upstream_path,
route_match.route, route_id, &ip_str, &h3_pool_key, domain_str, &conn_activity, &upstream_key,
).await;
self.upstream_selector.connection_ended(&upstream_key);
@@ -962,7 +991,7 @@ impl HttpProxyService {
protocol: crate::connection_pool::PoolProtocol::H3,
};
let result = self.forward_h3(
quic_conn, parts, body, upstream_headers, &upstream_path,
quic_conn, None, parts, body, upstream_headers, &upstream_path,
route_match.route, route_id, &ip_str, &h3_pool_key, domain_str, &conn_activity, &upstream_key,
).await;
self.upstream_selector.connection_ended(&upstream_key);
@@ -1005,7 +1034,7 @@ impl HttpProxyService {
protocol: crate::connection_pool::PoolProtocol::H3,
};
let result = self.forward_h3(
quic_conn, parts, body, upstream_headers, &upstream_path,
quic_conn, None, parts, body, upstream_headers, &upstream_path,
route_match.route, route_id, &ip_str, &h3_pool_key, domain_str, &conn_activity, &upstream_key,
).await;
self.upstream_selector.connection_ended(&upstream_key);
@@ -1064,7 +1093,7 @@ impl HttpProxyService {
protocol: crate::connection_pool::PoolProtocol::H3,
};
let result = self.forward_h3(
quic_conn, parts, body, upstream_headers, &upstream_path,
quic_conn, None, parts, body, upstream_headers, &upstream_path,
route_match.route, route_id, &ip_str, &h3_pool_key, domain_str, &conn_activity, &upstream_key,
).await;
self.upstream_selector.connection_ended(&upstream_key);
@@ -1107,7 +1136,7 @@ impl HttpProxyService {
protocol: crate::connection_pool::PoolProtocol::H3,
};
let result = self.forward_h3(
quic_conn, parts, body, upstream_headers, &upstream_path,
quic_conn, None, parts, body, upstream_headers, &upstream_path,
route_match.route, route_id, &ip_str, &h3_pool_key, domain_str, &conn_activity, &upstream_key,
).await;
self.upstream_selector.connection_ended(&upstream_key);
@@ -1205,8 +1234,10 @@ impl HttpProxyService {
};
tokio::spawn(async move {
if let Err(e) = conn.await {
debug!("Upstream connection error: {}", e);
match tokio::time::timeout(std::time::Duration::from_secs(300), conn).await {
Ok(Err(e)) => debug!("Upstream connection error: {}", e),
Err(_) => debug!("H1 connection driver timed out after 300s"),
_ => {}
}
});
@@ -1799,8 +1830,10 @@ impl HttpProxyService {
};
tokio::spawn(async move {
if let Err(e) = conn.await {
debug!("H1 fallback: upstream connection error: {}", e);
match tokio::time::timeout(std::time::Duration::from_secs(300), conn).await {
Ok(Err(e)) => debug!("H1 fallback: upstream connection error: {}", e),
Err(_) => debug!("H1 fallback: connection driver timed out after 300s"),
_ => {}
}
});
@@ -1979,8 +2012,10 @@ impl HttpProxyService {
if let Some(ref cache_key) = conn_activity.alt_svc_cache_key {
if let Some(alt_svc) = resp_parts.headers.get("alt-svc").and_then(|v| v.to_str().ok()) {
if let Some(h3_port) = parse_alt_svc_h3_port(alt_svc) {
debug!(h3_port, "Backend advertises H3 via Alt-Svc");
self.protocol_cache.insert_h3(cache_key.clone(), h3_port, "Alt-Svc response header");
let url = conn_activity.alt_svc_request_url.as_deref().unwrap_or("-");
debug!(h3_port, url, "Backend advertises H3 via Alt-Svc");
let reason = format!("Alt-Svc response header ({})", url);
self.protocol_cache.insert_h3(cache_key.clone(), h3_port, &reason);
}
}
}
@@ -2734,7 +2769,12 @@ impl HttpProxyService {
let quic_crypto = quinn::crypto::rustls::QuicClientConfig::try_from(tls_config)
.expect("Failed to create QUIC client crypto config");
let client_config = quinn::ClientConfig::new(Arc::new(quic_crypto));
// Tune QUIC transport to match H2 flow-control: 2 MB per-stream receive window.
let mut transport = quinn::TransportConfig::default();
transport.stream_receive_window(quinn::VarInt::from_u32(2 * 1024 * 1024));
let mut client_config = quinn::ClientConfig::new(Arc::new(quic_crypto));
client_config.transport_config(Arc::new(transport));
let mut endpoint = quinn::Endpoint::client("0.0.0.0:0".parse().unwrap())
.expect("Failed to create QUIC client endpoint");
@@ -2756,8 +2796,8 @@ impl HttpProxyService {
let server_name = host.to_string();
let connecting = self.quinn_client_endpoint.connect(addr, &server_name)?;
let connection = tokio::time::timeout(QUIC_CONNECT_TIMEOUT, connecting).await
.map_err(|_| format!("QUIC connect timeout (3s) for {}", host))??;
let connection = tokio::time::timeout(self.connect_timeout, connecting).await
.map_err(|_| format!("QUIC connect timeout ({:?}) for {}", self.connect_timeout, host))??;
debug!("QUIC backend connection established to {}:{}", host, port);
Ok(connection)
@@ -2767,6 +2807,7 @@ impl HttpProxyService {
async fn forward_h3(
&self,
quic_conn: quinn::Connection,
pooled_sender: Option<h3::client::SendRequest<h3_quinn::OpenStreams, Bytes>>,
parts: hyper::http::request::Parts,
body: BoxBody<Bytes, hyper::Error>,
upstream_headers: hyper::HeaderMap,
@@ -2779,8 +2820,14 @@ impl HttpProxyService {
conn_activity: &ConnActivity,
backend_key: &str,
) -> Result<Response<BoxBody<Bytes, hyper::Error>>, hyper::Error> {
// Obtain the h3 SendRequest handle: skip handshake + driver on pool hit.
let (mut send_request, gen_holder) = if let Some(sr) = pooled_sender {
// Pool hit — reuse existing h3 session, no SETTINGS round-trip
(sr, None)
} else {
// Fresh QUIC connection — full h3 handshake + driver spawn
let h3_quinn_conn = h3_quinn::Connection::new(quic_conn.clone());
let (mut driver, mut send_request) = match h3::client::builder()
let (mut driver, sr) = match h3::client::builder()
.send_grease(false)
.build(h3_quinn_conn)
.await
@@ -2793,10 +2840,10 @@ impl HttpProxyService {
}
};
// Spawn the h3 connection driver
let gen_holder = Arc::new(std::sync::atomic::AtomicU64::new(u64::MAX));
{
let driver_pool = Arc::clone(&self.connection_pool);
let driver_pool_key = pool_key.clone();
let gen_holder = Arc::new(std::sync::atomic::AtomicU64::new(u64::MAX));
let driver_gen = Arc::clone(&gen_holder);
tokio::spawn(async move {
let close_err = std::future::poll_fn(|cx| driver.poll_close(cx)).await;
@@ -2806,6 +2853,9 @@ impl HttpProxyService {
driver_pool.remove_h3_if_generation(&driver_pool_key, g);
}
});
}
(sr, Some(gen_holder))
};
// Build the H3 request
let uri = hyper::Uri::builder()
@@ -2835,7 +2885,7 @@ impl HttpProxyService {
}
};
// Stream request body
// Stream request body (zero-copy: into_data yields owned Bytes)
let rid: Option<Arc<str>> = route_id.map(Arc::from);
let sip: Arc<str> = Arc::from(source_ip);
@@ -2845,9 +2895,9 @@ impl HttpProxyService {
while let Some(frame) = body.frame().await {
match frame {
Ok(frame) => {
if let Some(data) = frame.data_ref() {
if let Ok(data) = frame.into_data() {
self.metrics.record_bytes(data.len() as u64, 0, rid.as_deref(), Some(&sip));
if let Err(e) = stream.send_data(Bytes::copy_from_slice(data)).await {
if let Err(e) = stream.send_data(data).await {
error!(backend = %backend_key, error = %e, "H3 send_data failed");
return Ok(error_response(StatusCode::BAD_GATEWAY, "H3 body send failed"));
}
@@ -2889,8 +2939,23 @@ impl HttpProxyService {
ResponseFilter::apply_headers(route, headers, None);
}
// Stream response body back via an adapter
let h3_body = H3ClientResponseBody { stream };
// Stream response body back via unfold — correctly preserves waker across polls
let body_stream = futures::stream::unfold(stream, |mut s| async move {
match s.recv_data().await {
Ok(Some(mut buf)) => {
use bytes::Buf;
let data = buf.copy_to_bytes(buf.remaining());
Some((Ok::<_, hyper::Error>(http_body::Frame::data(data)), s))
}
Ok(None) => None,
Err(e) => {
warn!("H3 response body recv error: {}", e);
None
}
}
});
let h3_body = http_body_util::StreamBody::new(body_stream);
let counting_body = CountingBody::new(
h3_body,
Arc::clone(&self.metrics),
@@ -2907,10 +2972,16 @@ impl HttpProxyService {
let body: BoxBody<Bytes, hyper::Error> = BoxBody::new(counting_body);
// Register connection in pool on success
// Register connection in pool on success (fresh connections only)
if status != StatusCode::BAD_GATEWAY {
let g = self.connection_pool.register_h3(pool_key.clone(), quic_conn);
gen_holder.store(g, std::sync::atomic::Ordering::Relaxed);
if let Some(gh) = gen_holder {
let g = self.connection_pool.register_h3(
pool_key.clone(),
quic_conn,
send_request,
);
gh.store(g, std::sync::atomic::Ordering::Relaxed);
}
}
self.metrics.set_backend_protocol(backend_key, "h3");
@@ -2939,41 +3010,6 @@ fn parse_alt_svc_h3_port(header_value: &str) -> Option<u16> {
None
}
/// Response body adapter for H3 client responses.
/// Reads data from the h3 `RequestStream` recv side and presents it as an `http_body::Body`.
struct H3ClientResponseBody {
stream: h3::client::RequestStream<h3_quinn::BidiStream<Bytes>, Bytes>,
}
impl http_body::Body for H3ClientResponseBody {
type Data = Bytes;
type Error = hyper::Error;
fn poll_frame(
mut self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
// h3's recv_data is async, so we need to poll it manually.
// Use a small future to poll the recv_data call.
use std::future::Future;
let mut fut = Box::pin(self.stream.recv_data());
match fut.as_mut().poll(_cx) {
Poll::Ready(Ok(Some(mut buf))) => {
use bytes::Buf;
let data = Bytes::copy_from_slice(buf.chunk());
buf.advance(buf.remaining());
Poll::Ready(Some(Ok(http_body::Frame::data(data))))
}
Poll::Ready(Ok(None)) => Poll::Ready(None),
Poll::Ready(Err(e)) => {
warn!("H3 response body recv error: {}", e);
Poll::Ready(None)
}
Poll::Pending => Poll::Pending,
}
}
}
/// Insecure certificate verifier for backend TLS connections (fallback only).
/// The production path uses the shared config from tls_handler which has the same
/// behavior but with session resumption across all outbound connections.
@@ -3042,6 +3078,7 @@ impl Default for HttpProxyService {
connection_pool: Arc::new(crate::connection_pool::ConnectionPool::new()),
protocol_cache: Arc::new(crate::protocol_cache::ProtocolCache::new()),
http_idle_timeout: DEFAULT_HTTP_IDLE_TIMEOUT,
http_max_lifetime: DEFAULT_HTTP_MAX_LIFETIME,
ws_inactivity_timeout: DEFAULT_WS_INACTIVITY_TIMEOUT,
ws_max_lifetime: DEFAULT_WS_MAX_LIFETIME,
quinn_client_endpoint: Arc::new(Self::create_quinn_client_endpoint()),

View File

@@ -624,6 +624,24 @@ impl MetricsCollector {
self.ip_pending_tp.retain(|k, _| self.ip_connections.contains_key(k));
self.ip_throughput.retain(|k, _| self.ip_connections.contains_key(k));
self.ip_total_connections.retain(|k, _| self.ip_connections.contains_key(k));
// Safety-net: prune orphaned backend error/stats entries for backends
// that have no active or total connections (error-only backends).
// These accumulate when backend_connect_error/backend_handshake_error
// create entries but backend_connection_opened is never called.
let known_backends: HashSet<String> = self.backend_active.iter()
.map(|e| e.key().clone())
.chain(self.backend_total.iter().map(|e| e.key().clone()))
.collect();
self.backend_connect_errors.retain(|k, _| known_backends.contains(k));
self.backend_handshake_errors.retain(|k, _| known_backends.contains(k));
self.backend_request_errors.retain(|k, _| known_backends.contains(k));
self.backend_connect_time_us.retain(|k, _| known_backends.contains(k));
self.backend_connect_count.retain(|k, _| known_backends.contains(k));
self.backend_pool_hits.retain(|k, _| known_backends.contains(k));
self.backend_pool_misses.retain(|k, _| known_backends.contains(k));
self.backend_h2_failures.retain(|k, _| known_backends.contains(k));
self.backend_protocol.retain(|k, _| known_backends.contains(k));
}
/// Remove per-route metrics for route IDs that are no longer active.

View File

@@ -10,7 +10,6 @@ pub mod forwarder;
pub mod proxy_protocol;
pub mod tls_handler;
pub mod connection_tracker;
pub mod socket_relay;
pub mod socket_opts;
pub mod udp_session;
pub mod udp_listener;
@@ -22,7 +21,6 @@ pub use forwarder::*;
pub use proxy_protocol::*;
pub use tls_handler::*;
pub use connection_tracker::*;
pub use socket_relay::*;
pub use socket_opts::*;
pub use udp_session::*;
pub use udp_listener::*;

View File

@@ -77,6 +77,13 @@ struct RelaySession {
cancel: CancellationToken,
}
impl Drop for RelaySession {
fn drop(&mut self) {
self.cancel.cancel();
self.return_task.abort();
}
}
/// Create a QUIC endpoint with a PROXY protocol v2 relay layer.
///
/// Instead of giving the external socket to quinn, we:
@@ -634,7 +641,7 @@ async fn forward_quic_stream_to_tcp(
let la_watch = Arc::clone(&last_activity);
let c2b_abort = c2b.abort_handle();
let b2c_abort = b2c.abort_handle();
tokio::spawn(async move {
let watchdog = tokio::spawn(async move {
let check_interval = std::time::Duration::from_secs(5);
let mut last_seen = 0u64;
loop {
@@ -665,6 +672,7 @@ async fn forward_quic_stream_to_tcp(
let bytes_in = c2b.await.unwrap_or(0);
let bytes_out = b2c.await.unwrap_or(0);
watchdog.abort();
Ok((bytes_in, bytes_out))
}

View File

@@ -1,126 +1,4 @@
//! Socket handler relay for connecting client connections to a TypeScript handler
//! via a Unix domain socket.
//! Socket handler relay module.
//!
//! Protocol: Send a JSON metadata line terminated by `\n`, then bidirectional relay.
use tokio::net::UnixStream;
use tokio::io::{AsyncWriteExt, AsyncReadExt};
use tokio::net::TcpStream;
use serde::Serialize;
use tracing::debug;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct RelayMetadata {
connection_id: u64,
remote_ip: String,
remote_port: u16,
local_port: u16,
sni: Option<String>,
route_name: String,
initial_data_base64: Option<String>,
}
/// Relay a client connection to a TypeScript handler via Unix domain socket.
///
/// Protocol: Send a JSON metadata line terminated by `\n`, then bidirectional relay.
pub async fn relay_to_handler(
client: TcpStream,
relay_socket_path: &str,
connection_id: u64,
remote_ip: String,
remote_port: u16,
local_port: u16,
sni: Option<String>,
route_name: String,
initial_data: Option<&[u8]>,
) -> std::io::Result<()> {
debug!(
"Relaying connection {} to handler socket {}",
connection_id, relay_socket_path
);
// Connect to TypeScript handler Unix socket
let mut handler = UnixStream::connect(relay_socket_path).await?;
// Build and send metadata header
let initial_data_base64 = initial_data.map(base64_encode);
let metadata = RelayMetadata {
connection_id,
remote_ip,
remote_port,
local_port,
sni,
route_name,
initial_data_base64,
};
let metadata_json = serde_json::to_string(&metadata)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
handler.write_all(metadata_json.as_bytes()).await?;
handler.write_all(b"\n").await?;
// Bidirectional relay between client and handler
let (mut client_read, mut client_write) = client.into_split();
let (mut handler_read, mut handler_write) = handler.into_split();
let c2h = tokio::spawn(async move {
let mut buf = vec![0u8; 65536];
loop {
let n = match client_read.read(&mut buf).await {
Ok(0) | Err(_) => break,
Ok(n) => n,
};
if handler_write.write_all(&buf[..n]).await.is_err() {
break;
}
}
let _ = handler_write.shutdown().await;
});
let h2c = tokio::spawn(async move {
let mut buf = vec![0u8; 65536];
loop {
let n = match handler_read.read(&mut buf).await {
Ok(0) | Err(_) => break,
Ok(n) => n,
};
if client_write.write_all(&buf[..n]).await.is_err() {
break;
}
}
let _ = client_write.shutdown().await;
});
let _ = tokio::join!(c2h, h2c);
debug!("Relay connection {} completed", connection_id);
Ok(())
}
/// Simple base64 encoding without external dependency.
fn base64_encode(data: &[u8]) -> String {
const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut result = String::new();
for chunk in data.chunks(3) {
let b0 = chunk[0] as u32;
let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
let n = (b0 << 16) | (b1 << 8) | b2;
result.push(CHARS[((n >> 18) & 0x3F) as usize] as char);
result.push(CHARS[((n >> 12) & 0x3F) as usize] as char);
if chunk.len() > 1 {
result.push(CHARS[((n >> 6) & 0x3F) as usize] as char);
} else {
result.push('=');
}
if chunk.len() > 2 {
result.push(CHARS[(n & 0x3F) as usize] as char);
} else {
result.push('=');
}
}
result
}
//! Note: The actual relay logic lives in `tcp_listener::relay_to_socket_handler()`
//! which has proper timeouts, cancellation, and metrics integration.

View File

@@ -182,6 +182,7 @@ impl TcpListenerManager {
http_proxy_svc.set_backend_tls_config_alpn(tls_handler::shared_backend_tls_config_alpn());
http_proxy_svc.set_connection_timeouts(
std::time::Duration::from_millis(conn_config.socket_timeout_ms),
std::time::Duration::from_millis(conn_config.max_connection_lifetime_ms),
std::time::Duration::from_millis(conn_config.socket_timeout_ms),
std::time::Duration::from_millis(conn_config.max_connection_lifetime_ms),
);
@@ -220,6 +221,7 @@ impl TcpListenerManager {
http_proxy_svc.set_backend_tls_config_alpn(tls_handler::shared_backend_tls_config_alpn());
http_proxy_svc.set_connection_timeouts(
std::time::Duration::from_millis(conn_config.socket_timeout_ms),
std::time::Duration::from_millis(conn_config.max_connection_lifetime_ms),
std::time::Duration::from_millis(conn_config.socket_timeout_ms),
std::time::Duration::from_millis(conn_config.max_connection_lifetime_ms),
);
@@ -263,6 +265,7 @@ impl TcpListenerManager {
http_proxy_svc.set_backend_tls_config_alpn(tls_handler::shared_backend_tls_config_alpn());
http_proxy_svc.set_connection_timeouts(
std::time::Duration::from_millis(config.socket_timeout_ms),
std::time::Duration::from_millis(config.max_connection_lifetime_ms),
std::time::Duration::from_millis(config.socket_timeout_ms),
std::time::Duration::from_millis(config.max_connection_lifetime_ms),
);

View File

@@ -363,6 +363,7 @@ impl RustProxy {
// Start the throughput sampling task with cooperative cancellation
let metrics = Arc::clone(&self.metrics);
let conn_tracker = self.listener_manager.as_ref().unwrap().conn_tracker().clone();
let http_proxy = self.listener_manager.as_ref().unwrap().http_proxy().clone();
let interval_ms = self.options.metrics.as_ref()
.and_then(|m| m.sample_interval_ms)
.unwrap_or(1000);
@@ -378,6 +379,9 @@ impl RustProxy {
metrics.sample_all();
// Periodically clean up stale rate-limit timestamp entries
conn_tracker.cleanup_stale_timestamps();
// Clean up expired rate limiter entries to prevent unbounded
// growth from unique IPs after traffic stops
http_proxy.cleanup_all_rate_limiters();
}
}
}

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartproxy',
version: '26.2.0',
version: '26.2.4',
description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.'
}

View File

@@ -26,6 +26,8 @@ interface IDatagramRelayMessage {
* - TS→Rust: { type: "reply", sourceIp, sourcePort, destPort, payloadBase64 }
*/
export class DatagramHandlerServer {
private static readonly MAX_BUFFER_SIZE = 50 * 1024 * 1024; // 50 MB
private server: plugins.net.Server | null = null;
private connection: plugins.net.Socket | null = null;
private socketPath: string;
@@ -100,6 +102,11 @@ export class DatagramHandlerServer {
socket.on('data', (chunk: Buffer) => {
this.readBuffer = Buffer.concat([this.readBuffer, chunk]);
if (this.readBuffer.length > DatagramHandlerServer.MAX_BUFFER_SIZE) {
logger.log('error', `DatagramHandlerServer: buffer exceeded ${DatagramHandlerServer.MAX_BUFFER_SIZE} bytes, resetting`);
this.readBuffer = Buffer.alloc(0);
return;
}
this.processFrames();
});

View File

@@ -128,6 +128,7 @@ export class SmartProxy extends plugins.EventEmitter {
}
// Handle unexpected exit (only emits error if not intentionally stopping)
this.bridge.removeAllListeners('exit');
this.bridge.on('exit', (code: number | null, signal: string | null) => {
if (this.stopping) return;
logger.log('error', `RustProxy exited unexpectedly (code=${code}, signal=${signal})`, { component: 'smart-proxy' });

View File

@@ -69,14 +69,15 @@ export function createOffsetPortMappingRoute(options: {
priority?: number;
[key: string]: any;
}): IRouteConfig {
const { ports, targetHost, offset, name, domains, priority, ...rest } = options;
return createPortMappingRoute({
sourcePortRange: options.ports,
targetHost: options.targetHost,
portMapper: (context) => context.port + options.offset,
name: options.name || `Offset Mapping (${options.offset > 0 ? '+' : ''}${options.offset}) for ${options.domains || 'all domains'}`,
domains: options.domains,
priority: options.priority,
...options
sourcePortRange: ports,
targetHost,
portMapper: (context) => context.port + offset,
name: name || `Offset Mapping (${offset > 0 ? '+' : ''}${offset}) for ${domains || 'all domains'}`,
domains,
priority,
...rest
});
}

View File

@@ -258,8 +258,10 @@ export class RouteValidator {
errorMap.set(route.name, existingErrors);
valid = false;
}
if (route.name) {
routeNames.add(route.name);
}
}
// Validate each route
for (const route of routes) {
@@ -328,7 +330,7 @@ export class RouteValidator {
if (catchAllRoutes.length > 1) {
for (const route of catchAllRoutes) {
conflicts.push({
route: route.name,
route: route.name || 'unnamed',
message: `Multiple catch-all routes on port ${port}`
});
}

View File

@@ -7,8 +7,7 @@
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"verbatimModuleSyntax": true,
"baseUrl": ".",
"paths": {}
"ignoreDeprecations": "6.0"
},
"exclude": [
"dist_*/**/*.d.ts"