@push.rocks/smartproxy 23.0.0 → 23.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +17 -0
- package/dist_rust/{rustproxy → rustproxy_linux_amd64} +0 -0
- package/dist_rust/rustproxy_linux_arm64 +0 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.d.ts +9 -21
- package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.js +84 -212
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +2 -3
- package/npmextra.json +3 -0
- package/package.json +13 -11
- package/readme.md +41 -11
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/plugins.ts +2 -0
- package/ts/proxies/smart-proxy/rust-proxy-bridge.ts +103 -233
- package/ts/proxies/smart-proxy/smart-proxy.ts +1 -2
- package/dist_ts/common/eventUtils.d.ts +0 -14
- package/dist_ts/common/eventUtils.js +0 -20
- package/dist_ts/common/types.d.ts +0 -82
- package/dist_ts/common/types.js +0 -15
- package/dist_ts/core/utils/event-system.d.ts +0 -200
- package/dist_ts/core/utils/event-system.js +0 -224
- package/dist_ts/core/utils/event-utils.d.ts +0 -15
- package/dist_ts/core/utils/event-utils.js +0 -11
- package/dist_ts/core/utils/route-manager.d.ts +0 -88
- package/dist_ts/core/utils/route-manager.js +0 -342
- package/dist_ts/core/utils/route-utils.d.ts +0 -28
- package/dist_ts/core/utils/route-utils.js +0 -67
- package/dist_ts/detection/detectors/http-detector-v2.d.ts +0 -33
- package/dist_ts/detection/detectors/http-detector-v2.js +0 -87
- package/dist_ts/detection/detectors/tls-detector-v2.d.ts +0 -33
- package/dist_ts/detection/detectors/tls-detector-v2.js +0 -80
- package/dist_ts/detection/protocol-detector-v2.d.ts +0 -46
- package/dist_ts/detection/protocol-detector-v2.js +0 -116
- package/dist_ts/forwarding/config/forwarding-types.d.ts +0 -42
- package/dist_ts/forwarding/config/forwarding-types.js +0 -18
- package/dist_ts/forwarding/config/index.d.ts +0 -9
- package/dist_ts/forwarding/config/index.js +0 -10
- package/dist_ts/forwarding/factory/forwarding-factory.d.ts +0 -25
- package/dist_ts/forwarding/factory/forwarding-factory.js +0 -172
- package/dist_ts/forwarding/factory/index.d.ts +0 -4
- package/dist_ts/forwarding/factory/index.js +0 -5
- package/dist_ts/forwarding/handlers/base-handler.d.ts +0 -62
- package/dist_ts/forwarding/handlers/base-handler.js +0 -121
- package/dist_ts/forwarding/handlers/http-handler.d.ts +0 -30
- package/dist_ts/forwarding/handlers/http-handler.js +0 -143
- package/dist_ts/forwarding/handlers/https-passthrough-handler.d.ts +0 -29
- package/dist_ts/forwarding/handlers/https-passthrough-handler.js +0 -156
- package/dist_ts/forwarding/handlers/https-terminate-to-http-handler.d.ts +0 -36
- package/dist_ts/forwarding/handlers/https-terminate-to-http-handler.js +0 -276
- package/dist_ts/forwarding/handlers/https-terminate-to-https-handler.d.ts +0 -35
- package/dist_ts/forwarding/handlers/https-terminate-to-https-handler.js +0 -261
- package/dist_ts/forwarding/handlers/index.d.ts +0 -8
- package/dist_ts/forwarding/handlers/index.js +0 -9
- package/dist_ts/forwarding/index.d.ts +0 -13
- package/dist_ts/forwarding/index.js +0 -16
- package/dist_ts/http/index.d.ts +0 -5
- package/dist_ts/http/index.js +0 -8
- package/dist_ts/http/models/http-types.d.ts +0 -6
- package/dist_ts/http/models/http-types.js +0 -7
- package/dist_ts/http/router/index.d.ts +0 -8
- package/dist_ts/http/router/index.js +0 -7
- package/dist_ts/http/router/proxy-router.d.ts +0 -115
- package/dist_ts/http/router/proxy-router.js +0 -325
- package/dist_ts/http/router/route-router.d.ts +0 -108
- package/dist_ts/http/router/route-router.js +0 -393
- package/dist_ts/protocols/tls/constants.d.ts +0 -122
- package/dist_ts/protocols/tls/constants.js +0 -135
- package/dist_ts/protocols/tls/parser.d.ts +0 -53
- package/dist_ts/protocols/tls/parser.js +0 -294
- package/dist_ts/protocols/tls/types.d.ts +0 -65
- package/dist_ts/protocols/tls/types.js +0 -5
- package/dist_ts/proxies/http-proxy/certificate-manager.d.ts +0 -95
- package/dist_ts/proxies/http-proxy/certificate-manager.js +0 -214
- package/dist_ts/proxies/http-proxy/connection-pool.d.ts +0 -47
- package/dist_ts/proxies/http-proxy/connection-pool.js +0 -195
- package/dist_ts/proxies/http-proxy/context-creator.d.ts +0 -34
- package/dist_ts/proxies/http-proxy/context-creator.js +0 -108
- package/dist_ts/proxies/http-proxy/default-certificates.d.ts +0 -54
- package/dist_ts/proxies/http-proxy/default-certificates.js +0 -127
- package/dist_ts/proxies/http-proxy/function-cache.d.ts +0 -95
- package/dist_ts/proxies/http-proxy/function-cache.js +0 -215
- package/dist_ts/proxies/http-proxy/handlers/index.d.ts +0 -4
- package/dist_ts/proxies/http-proxy/handlers/index.js +0 -6
- package/dist_ts/proxies/http-proxy/handlers/redirect-handler.d.ts +0 -18
- package/dist_ts/proxies/http-proxy/handlers/redirect-handler.js +0 -78
- package/dist_ts/proxies/http-proxy/handlers/static-handler.d.ts +0 -19
- package/dist_ts/proxies/http-proxy/handlers/static-handler.js +0 -211
- package/dist_ts/proxies/http-proxy/http-proxy.d.ts +0 -117
- package/dist_ts/proxies/http-proxy/http-proxy.js +0 -521
- package/dist_ts/proxies/http-proxy/http-request-handler.d.ts +0 -40
- package/dist_ts/proxies/http-proxy/http-request-handler.js +0 -257
- package/dist_ts/proxies/http-proxy/http2-request-handler.d.ts +0 -24
- package/dist_ts/proxies/http-proxy/http2-request-handler.js +0 -201
- package/dist_ts/proxies/http-proxy/index.d.ts +0 -14
- package/dist_ts/proxies/http-proxy/index.js +0 -16
- package/dist_ts/proxies/http-proxy/models/http-types.d.ts +0 -117
- package/dist_ts/proxies/http-proxy/models/http-types.js +0 -92
- package/dist_ts/proxies/http-proxy/models/index.d.ts +0 -5
- package/dist_ts/proxies/http-proxy/models/index.js +0 -6
- package/dist_ts/proxies/http-proxy/models/types.d.ts +0 -75
- package/dist_ts/proxies/http-proxy/models/types.js +0 -35
- package/dist_ts/proxies/http-proxy/request-handler.d.ts +0 -97
- package/dist_ts/proxies/http-proxy/request-handler.js +0 -737
- package/dist_ts/proxies/http-proxy/security-manager.d.ts +0 -98
- package/dist_ts/proxies/http-proxy/security-manager.js +0 -341
- package/dist_ts/proxies/http-proxy/websocket-handler.d.ts +0 -50
- package/dist_ts/proxies/http-proxy/websocket-handler.js +0 -505
- package/dist_ts/proxies/smart-proxy/acme-state-manager.d.ts +0 -42
- package/dist_ts/proxies/smart-proxy/acme-state-manager.js +0 -101
- package/dist_ts/proxies/smart-proxy/cert-store.d.ts +0 -10
- package/dist_ts/proxies/smart-proxy/cert-store.js +0 -72
- package/dist_ts/proxies/smart-proxy/certificate-manager.d.ts +0 -164
- package/dist_ts/proxies/smart-proxy/certificate-manager.js +0 -745
- package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +0 -128
- package/dist_ts/proxies/smart-proxy/connection-manager.js +0 -689
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.d.ts +0 -43
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +0 -180
- package/dist_ts/proxies/smart-proxy/metrics-collector.d.ts +0 -98
- package/dist_ts/proxies/smart-proxy/metrics-collector.js +0 -355
- package/dist_ts/proxies/smart-proxy/nftables-manager.d.ts +0 -82
- package/dist_ts/proxies/smart-proxy/nftables-manager.js +0 -237
- package/dist_ts/proxies/smart-proxy/port-manager.d.ts +0 -117
- package/dist_ts/proxies/smart-proxy/port-manager.js +0 -318
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +0 -60
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +0 -1407
- package/dist_ts/proxies/smart-proxy/route-manager.d.ts +0 -112
- package/dist_ts/proxies/smart-proxy/route-manager.js +0 -453
- package/dist_ts/proxies/smart-proxy/route-orchestrator.d.ts +0 -56
- package/dist_ts/proxies/smart-proxy/route-orchestrator.js +0 -204
- package/dist_ts/proxies/smart-proxy/rust-binary-locator.d.ts +0 -23
- package/dist_ts/proxies/smart-proxy/rust-binary-locator.js +0 -104
- package/dist_ts/proxies/smart-proxy/security-manager.d.ts +0 -74
- package/dist_ts/proxies/smart-proxy/security-manager.js +0 -227
- package/dist_ts/proxies/smart-proxy/throughput-tracker.d.ts +0 -36
- package/dist_ts/proxies/smart-proxy/throughput-tracker.js +0 -115
- package/dist_ts/proxies/smart-proxy/timeout-manager.d.ts +0 -48
- package/dist_ts/proxies/smart-proxy/timeout-manager.js +0 -158
- package/dist_ts/proxies/smart-proxy/tls-manager.d.ts +0 -50
- package/dist_ts/proxies/smart-proxy/tls-manager.js +0 -110
- package/dist_ts/proxies/smart-proxy/utils/route-patterns.d.ts +0 -161
- package/dist_ts/proxies/smart-proxy/utils/route-patterns.js +0 -282
- package/dist_ts/proxies/smart-proxy/utils/route-validators.d.ts +0 -73
- package/dist_ts/proxies/smart-proxy/utils/route-validators.js +0 -259
- package/dist_ts/routing/router/proxy-router.d.ts +0 -115
- package/dist_ts/routing/router/proxy-router.js +0 -325
- package/dist_ts/routing/router/route-router.d.ts +0 -108
- package/dist_ts/routing/router/route-router.js +0 -393
- package/dist_ts/tls/alerts/index.d.ts +0 -4
- package/dist_ts/tls/alerts/index.js +0 -5
- package/dist_ts/tls/alerts/tls-alert.d.ts +0 -150
- package/dist_ts/tls/alerts/tls-alert.js +0 -226
- package/dist_ts/tls/sni/client-hello-parser.d.ts +0 -100
- package/dist_ts/tls/sni/client-hello-parser.js +0 -464
- package/dist_ts/tls/sni/sni-extraction.d.ts +0 -58
- package/dist_ts/tls/sni/sni-extraction.js +0 -275
- package/dist_ts/tls/utils/index.d.ts +0 -4
- package/dist_ts/tls/utils/index.js +0 -5
- package/dist_ts/tls/utils/tls-utils.d.ts +0 -49
- package/dist_ts/tls/utils/tls-utils.js +0 -75
- package/ts/proxies/smart-proxy/rust-binary-locator.ts +0 -112
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@push.rocks/smartproxy",
|
|
3
|
-
"version": "23.
|
|
3
|
+
"version": "23.1.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"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.",
|
|
6
6
|
"main": "dist_ts/index.js",
|
|
@@ -10,16 +10,17 @@
|
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"scripts": {
|
|
12
12
|
"test": "(tstest test/**/test*.ts --verbose --timeout 60 --logfile)",
|
|
13
|
-
"build": "(tsbuild tsfolders --allowimplicitany)",
|
|
13
|
+
"build": "(tsbuild tsfolders --allowimplicitany) && (tsrust)",
|
|
14
14
|
"format": "(gitzone format)",
|
|
15
15
|
"buildDocs": "tsdoc"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
|
-
"@git.zone/tsbuild": "^
|
|
19
|
-
"@git.zone/tsrun": "^2.0.
|
|
20
|
-
"@git.zone/
|
|
21
|
-
"@
|
|
22
|
-
"@
|
|
18
|
+
"@git.zone/tsbuild": "^4.1.2",
|
|
19
|
+
"@git.zone/tsrun": "^2.0.1",
|
|
20
|
+
"@git.zone/tsrust": "^1.3.0",
|
|
21
|
+
"@git.zone/tstest": "^3.1.8",
|
|
22
|
+
"@push.rocks/smartserve": "^2.0.1",
|
|
23
|
+
"@types/node": "^25.2.3",
|
|
23
24
|
"typescript": "^5.9.3",
|
|
24
25
|
"why-is-node-running": "^3.2.2"
|
|
25
26
|
},
|
|
@@ -28,20 +29,21 @@
|
|
|
28
29
|
"@push.rocks/smartacme": "^8.0.0",
|
|
29
30
|
"@push.rocks/smartcrypto": "^2.0.4",
|
|
30
31
|
"@push.rocks/smartdelay": "^3.0.5",
|
|
31
|
-
"@push.rocks/smartfile": "^13.1.
|
|
32
|
+
"@push.rocks/smartfile": "^13.1.2",
|
|
32
33
|
"@push.rocks/smartlog": "^3.1.10",
|
|
33
34
|
"@push.rocks/smartnetwork": "^4.4.0",
|
|
34
35
|
"@push.rocks/smartpromise": "^4.2.3",
|
|
35
36
|
"@push.rocks/smartrequest": "^5.0.1",
|
|
37
|
+
"@push.rocks/smartrust": "^1.2.0",
|
|
36
38
|
"@push.rocks/smartrx": "^3.0.10",
|
|
37
39
|
"@push.rocks/smartstring": "^4.1.0",
|
|
38
|
-
"@push.rocks/taskbuffer": "^
|
|
40
|
+
"@push.rocks/taskbuffer": "^4.2.0",
|
|
39
41
|
"@tsclass/tsclass": "^9.3.0",
|
|
40
42
|
"@types/minimatch": "^6.0.0",
|
|
41
43
|
"@types/ws": "^8.18.1",
|
|
42
|
-
"minimatch": "^10.1.
|
|
44
|
+
"minimatch": "^10.1.2",
|
|
43
45
|
"pretty-ms": "^9.3.0",
|
|
44
|
-
"ws": "^8.
|
|
46
|
+
"ws": "^8.19.0"
|
|
45
47
|
},
|
|
46
48
|
"files": [
|
|
47
49
|
"ts/**/*",
|
package/readme.md
CHANGED
|
@@ -378,7 +378,9 @@ await proxy.updateRoutes([...newRoutes]);
|
|
|
378
378
|
// Get real-time metrics
|
|
379
379
|
const metrics = proxy.getMetrics();
|
|
380
380
|
console.log(`Active connections: ${metrics.connections.active()}`);
|
|
381
|
-
console.log(`
|
|
381
|
+
console.log(`Bytes in: ${metrics.totals.bytesIn()}`);
|
|
382
|
+
console.log(`Requests/sec: ${metrics.requests.perSecond()}`);
|
|
383
|
+
console.log(`Throughput in: ${metrics.throughput.instant().in} bytes/sec`);
|
|
382
384
|
|
|
383
385
|
// Get detailed statistics from the Rust engine
|
|
384
386
|
const stats = await proxy.getStatistics();
|
|
@@ -486,8 +488,8 @@ SmartProxy uses a hybrid **Rust + TypeScript** architecture:
|
|
|
486
488
|
|
|
487
489
|
- **Rust Engine** handles all networking, TLS, HTTP proxying, connection management, security, and metrics
|
|
488
490
|
- **TypeScript** provides the npm API, configuration types, route helpers, validation, and socket handler callbacks
|
|
489
|
-
- **IPC** — JSON commands/events over stdin/stdout
|
|
490
|
-
- **Socket Relay** —
|
|
491
|
+
- **IPC** — The TypeScript wrapper uses [`@push.rocks/smartrust`](https://code.foss.global/push.rocks/smartrust) for type-safe JSON commands/events over stdin/stdout
|
|
492
|
+
- **Socket Relay** — A Unix domain socket server for routes requiring TypeScript-side handling (socket handlers, dynamic host/port functions)
|
|
491
493
|
|
|
492
494
|
## 🎯 Route Configuration Reference
|
|
493
495
|
|
|
@@ -699,7 +701,7 @@ interface ISmartProxyOptions {
|
|
|
699
701
|
|
|
700
702
|
// Timeouts
|
|
701
703
|
connectionTimeout?: number; // Backend connection timeout (default: 30s)
|
|
702
|
-
initialDataTimeout?: number; // Initial data/SNI timeout (default:
|
|
704
|
+
initialDataTimeout?: number; // Initial data/SNI timeout (default: 120s)
|
|
703
705
|
socketTimeout?: number; // Socket inactivity timeout (default: 1h)
|
|
704
706
|
maxConnectionLifetime?: number; // Max connection lifetime (default: 24h)
|
|
705
707
|
inactivityTimeout?: number; // Inactivity timeout (default: 4h)
|
|
@@ -724,12 +726,40 @@ interface ISmartProxyOptions {
|
|
|
724
726
|
// Behavior
|
|
725
727
|
enableDetailedLogging?: boolean; // Verbose connection logging
|
|
726
728
|
enableTlsDebugLogging?: boolean; // TLS handshake debug logging
|
|
727
|
-
|
|
728
|
-
// Rust binary
|
|
729
|
-
rustBinaryPath?: string; // Custom path to the Rust binary
|
|
730
729
|
}
|
|
731
730
|
```
|
|
732
731
|
|
|
732
|
+
### IMetrics Interface
|
|
733
|
+
|
|
734
|
+
The `getMetrics()` method returns a cached metrics adapter that polls the Rust engine:
|
|
735
|
+
|
|
736
|
+
```typescript
|
|
737
|
+
const metrics = proxy.getMetrics();
|
|
738
|
+
|
|
739
|
+
// Connection metrics
|
|
740
|
+
metrics.connections.active(); // Current active connections
|
|
741
|
+
metrics.connections.total(); // Total connections since start
|
|
742
|
+
metrics.connections.byRoute(); // Map<routeName, activeCount>
|
|
743
|
+
metrics.connections.byIP(); // Map<ip, activeCount>
|
|
744
|
+
metrics.connections.topIPs(10); // Top N IPs by connection count
|
|
745
|
+
|
|
746
|
+
// Throughput (bytes/sec)
|
|
747
|
+
metrics.throughput.instant(); // { in: number, out: number }
|
|
748
|
+
metrics.throughput.recent(); // Recent average
|
|
749
|
+
metrics.throughput.average(); // Overall average
|
|
750
|
+
metrics.throughput.byRoute(); // Map<routeName, { in, out }>
|
|
751
|
+
|
|
752
|
+
// Request rates
|
|
753
|
+
metrics.requests.perSecond(); // Requests per second
|
|
754
|
+
metrics.requests.perMinute(); // Requests per minute
|
|
755
|
+
metrics.requests.total(); // Total requests
|
|
756
|
+
|
|
757
|
+
// Cumulative totals
|
|
758
|
+
metrics.totals.bytesIn(); // Total bytes received
|
|
759
|
+
metrics.totals.bytesOut(); // Total bytes sent
|
|
760
|
+
metrics.totals.connections(); // Total connections
|
|
761
|
+
```
|
|
762
|
+
|
|
733
763
|
## 🐛 Troubleshooting
|
|
734
764
|
|
|
735
765
|
### Certificate Issues
|
|
@@ -746,13 +776,13 @@ interface ISmartProxyOptions {
|
|
|
746
776
|
- ✅ Enable debug logging with `enableDetailedLogging: true`
|
|
747
777
|
|
|
748
778
|
### Rust Binary Not Found
|
|
779
|
+
|
|
749
780
|
SmartProxy searches for the Rust binary in this order:
|
|
750
781
|
1. `SMARTPROXY_RUST_BINARY` environment variable
|
|
751
782
|
2. Platform-specific npm package (`@push.rocks/smartproxy-linux-x64`, etc.)
|
|
752
|
-
3.
|
|
753
|
-
4.
|
|
754
|
-
|
|
755
|
-
Set `rustBinaryPath` in options to override.
|
|
783
|
+
3. `dist_rust/rustproxy` relative to the package root (built by `tsrust`)
|
|
784
|
+
4. Local dev build (`./rust/target/release/rustproxy`)
|
|
785
|
+
5. System PATH (`rustproxy`)
|
|
756
786
|
|
|
757
787
|
### Performance Tuning
|
|
758
788
|
- ✅ Use NFTables forwarding for high-traffic routes (Linux only)
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartproxy',
|
|
6
|
-
version: '23.
|
|
6
|
+
version: '23.1.1',
|
|
7
7
|
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.'
|
|
8
8
|
}
|
package/ts/plugins.ts
CHANGED
|
@@ -31,6 +31,7 @@ import * as smartlog from '@push.rocks/smartlog';
|
|
|
31
31
|
import * as smartlogDestinationLocal from '@push.rocks/smartlog/destination-local';
|
|
32
32
|
import * as taskbuffer from '@push.rocks/taskbuffer';
|
|
33
33
|
import * as smartrx from '@push.rocks/smartrx';
|
|
34
|
+
import * as smartrust from '@push.rocks/smartrust';
|
|
34
35
|
|
|
35
36
|
export {
|
|
36
37
|
lik,
|
|
@@ -47,6 +48,7 @@ export {
|
|
|
47
48
|
smartlogDestinationLocal,
|
|
48
49
|
taskbuffer,
|
|
49
50
|
smartrx,
|
|
51
|
+
smartrust,
|
|
50
52
|
};
|
|
51
53
|
|
|
52
54
|
// third party scope
|
|
@@ -1,310 +1,180 @@
|
|
|
1
1
|
import * as plugins from '../../plugins.js';
|
|
2
2
|
import { logger } from '../../core/utils/logger.js';
|
|
3
|
-
import { RustBinaryLocator } from './rust-binary-locator.js';
|
|
4
3
|
import type { IRouteConfig } from './models/route-types.js';
|
|
5
|
-
import { ChildProcess, spawn } from 'child_process';
|
|
6
|
-
import { createInterface, Interface as ReadlineInterface } from 'readline';
|
|
7
4
|
|
|
8
5
|
/**
|
|
9
|
-
*
|
|
6
|
+
* Type-safe command definitions for the Rust proxy IPC protocol.
|
|
10
7
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
params:
|
|
8
|
+
type TSmartProxyCommands = {
|
|
9
|
+
start: { params: { config: any }; result: void };
|
|
10
|
+
stop: { params: Record<string, never>; result: void };
|
|
11
|
+
updateRoutes: { params: { routes: IRouteConfig[] }; result: void };
|
|
12
|
+
getMetrics: { params: Record<string, never>; result: any };
|
|
13
|
+
getStatistics: { params: Record<string, never>; result: any };
|
|
14
|
+
provisionCertificate: { params: { routeName: string }; result: void };
|
|
15
|
+
renewCertificate: { params: { routeName: string }; result: void };
|
|
16
|
+
getCertificateStatus: { params: { routeName: string }; result: any };
|
|
17
|
+
getListeningPorts: { params: Record<string, never>; result: { ports: number[] } };
|
|
18
|
+
getNftablesStatus: { params: Record<string, never>; result: any };
|
|
19
|
+
setSocketHandlerRelay: { params: { socketPath: string }; result: void };
|
|
20
|
+
addListeningPort: { params: { port: number }; result: void };
|
|
21
|
+
removeListeningPort: { params: { port: number }; result: void };
|
|
22
|
+
loadCertificate: { params: { domain: string; cert: string; key: string; ca?: string }; result: void };
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get the package root directory using import.meta.url.
|
|
27
|
+
* This file is at ts/proxies/smart-proxy/, so package root is 3 levels up.
|
|
28
|
+
*/
|
|
29
|
+
function getPackageRoot(): string {
|
|
30
|
+
const thisDir = plugins.path.dirname(plugins.url.fileURLToPath(import.meta.url));
|
|
31
|
+
return plugins.path.resolve(thisDir, '..', '..', '..');
|
|
15
32
|
}
|
|
16
33
|
|
|
17
34
|
/**
|
|
18
|
-
*
|
|
35
|
+
* Map Node.js process.platform/process.arch to tsrust's friendly name suffix.
|
|
36
|
+
* tsrust names cross-compiled binaries as: rustproxy_linux_amd64, rustproxy_linux_arm64, etc.
|
|
19
37
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
38
|
+
function getTsrustPlatformSuffix(): string | null {
|
|
39
|
+
const archMap: Record<string, string> = { x64: 'amd64', arm64: 'arm64' };
|
|
40
|
+
const osMap: Record<string, string> = { linux: 'linux', darwin: 'macos' };
|
|
41
|
+
const os = osMap[process.platform];
|
|
42
|
+
const arch = archMap[process.arch];
|
|
43
|
+
if (os && arch) {
|
|
44
|
+
return `${os}_${arch}`;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
25
47
|
}
|
|
26
48
|
|
|
27
49
|
/**
|
|
28
|
-
*
|
|
50
|
+
* Build local search paths for the Rust binary, including dist_rust/ candidates
|
|
51
|
+
* (built by tsrust) and local development build paths.
|
|
29
52
|
*/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
53
|
+
function buildLocalPaths(): string[] {
|
|
54
|
+
const packageRoot = getPackageRoot();
|
|
55
|
+
const suffix = getTsrustPlatformSuffix();
|
|
56
|
+
const paths: string[] = [];
|
|
57
|
+
|
|
58
|
+
// dist_rust/ candidates (tsrust cross-compiled output)
|
|
59
|
+
if (suffix) {
|
|
60
|
+
paths.push(plugins.path.join(packageRoot, 'dist_rust', `rustproxy_${suffix}`));
|
|
61
|
+
}
|
|
62
|
+
paths.push(plugins.path.join(packageRoot, 'dist_rust', 'rustproxy'));
|
|
63
|
+
|
|
64
|
+
// Local dev build paths
|
|
65
|
+
paths.push(plugins.path.resolve(process.cwd(), 'rust', 'target', 'release', 'rustproxy'));
|
|
66
|
+
paths.push(plugins.path.resolve(process.cwd(), 'rust', 'target', 'debug', 'rustproxy'));
|
|
67
|
+
|
|
68
|
+
return paths;
|
|
33
69
|
}
|
|
34
70
|
|
|
35
71
|
/**
|
|
36
72
|
* Bridge between TypeScript SmartProxy and the Rust binary.
|
|
37
|
-
*
|
|
73
|
+
* Wraps @push.rocks/smartrust's RustBridge with type-safe command definitions.
|
|
38
74
|
*/
|
|
39
75
|
export class RustProxyBridge extends plugins.EventEmitter {
|
|
40
|
-
private
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
76
|
+
private bridge: plugins.smartrust.RustBridge<TSmartProxyCommands>;
|
|
77
|
+
|
|
78
|
+
constructor() {
|
|
79
|
+
super();
|
|
80
|
+
|
|
81
|
+
this.bridge = new plugins.smartrust.RustBridge<TSmartProxyCommands>({
|
|
82
|
+
binaryName: 'rustproxy',
|
|
83
|
+
envVarName: 'SMARTPROXY_RUST_BINARY',
|
|
84
|
+
platformPackagePrefix: '@push.rocks/smartproxy',
|
|
85
|
+
localPaths: buildLocalPaths(),
|
|
86
|
+
maxPayloadSize: 100 * 1024 * 1024, // 100 MB – route configs with many entries can be large
|
|
87
|
+
logger: {
|
|
88
|
+
log: (level: string, message: string, data?: Record<string, any>) => {
|
|
89
|
+
logger.log(level as any, message, data);
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Forward events from the inner bridge
|
|
95
|
+
this.bridge.on('exit', (code: number | null, signal: string | null) => {
|
|
96
|
+
this.emit('exit', code, signal);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
52
99
|
|
|
53
100
|
/**
|
|
54
101
|
* Spawn the Rust binary in management mode.
|
|
55
102
|
* Returns true if the binary was found and spawned successfully.
|
|
56
103
|
*/
|
|
57
104
|
public async spawn(): Promise<boolean> {
|
|
58
|
-
|
|
59
|
-
if (!this.binaryPath) {
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return new Promise<boolean>((resolve) => {
|
|
64
|
-
try {
|
|
65
|
-
this.process = spawn(this.binaryPath!, ['--management'], {
|
|
66
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
67
|
-
env: { ...process.env },
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
// Handle stderr (logging from Rust goes here)
|
|
71
|
-
const stderrHandler = (data: Buffer) => {
|
|
72
|
-
const lines = data.toString().split('\n').filter(l => l.trim());
|
|
73
|
-
for (const line of lines) {
|
|
74
|
-
logger.log('debug', `[rustproxy] ${line}`, { component: 'rust-bridge' });
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
this.process.stderr?.on('data', stderrHandler);
|
|
78
|
-
|
|
79
|
-
// Handle stdout (JSON IPC)
|
|
80
|
-
this.readline = createInterface({ input: this.process.stdout! });
|
|
81
|
-
this.readline.on('line', (line: string) => {
|
|
82
|
-
this.handleLine(line.trim());
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// Handle process exit
|
|
86
|
-
this.process.on('exit', (code, signal) => {
|
|
87
|
-
logger.log('info', `RustProxy process exited (code=${code}, signal=${signal})`, { component: 'rust-bridge' });
|
|
88
|
-
this.cleanup();
|
|
89
|
-
this.emit('exit', code, signal);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
this.process.on('error', (err) => {
|
|
93
|
-
logger.log('error', `RustProxy process error: ${err.message}`, { component: 'rust-bridge' });
|
|
94
|
-
this.cleanup();
|
|
95
|
-
resolve(false);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// Wait for the 'ready' event from Rust
|
|
99
|
-
const readyTimeout = setTimeout(() => {
|
|
100
|
-
logger.log('error', 'RustProxy did not send ready event within 10s', { component: 'rust-bridge' });
|
|
101
|
-
this.kill();
|
|
102
|
-
resolve(false);
|
|
103
|
-
}, 10000);
|
|
104
|
-
|
|
105
|
-
this.once('management:ready', () => {
|
|
106
|
-
clearTimeout(readyTimeout);
|
|
107
|
-
this.isRunning = true;
|
|
108
|
-
logger.log('info', 'RustProxy bridge connected', { component: 'rust-bridge' });
|
|
109
|
-
resolve(true);
|
|
110
|
-
});
|
|
111
|
-
} catch (err: any) {
|
|
112
|
-
logger.log('error', `Failed to spawn RustProxy: ${err.message}`, { component: 'rust-bridge' });
|
|
113
|
-
resolve(false);
|
|
114
|
-
}
|
|
115
|
-
});
|
|
105
|
+
return this.bridge.spawn();
|
|
116
106
|
}
|
|
117
107
|
|
|
118
108
|
/**
|
|
119
|
-
*
|
|
109
|
+
* Kill the Rust process and clean up.
|
|
120
110
|
*/
|
|
121
|
-
public
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const id = `req_${++this.requestCounter}`;
|
|
127
|
-
const request: IManagementRequest = { id, method, params };
|
|
128
|
-
|
|
129
|
-
return new Promise<any>((resolve, reject) => {
|
|
130
|
-
const timer = setTimeout(() => {
|
|
131
|
-
this.pendingRequests.delete(id);
|
|
132
|
-
reject(new Error(`RustProxy command '${method}' timed out after ${this.requestTimeoutMs}ms`));
|
|
133
|
-
}, this.requestTimeoutMs);
|
|
134
|
-
|
|
135
|
-
this.pendingRequests.set(id, { resolve, reject, timer });
|
|
111
|
+
public kill(): void {
|
|
112
|
+
this.bridge.kill();
|
|
113
|
+
}
|
|
136
114
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
reject(new Error(`Failed to write to RustProxy stdin: ${err.message}`));
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
});
|
|
115
|
+
/**
|
|
116
|
+
* Whether the bridge is currently running.
|
|
117
|
+
*/
|
|
118
|
+
public get running(): boolean {
|
|
119
|
+
return this.bridge.running;
|
|
146
120
|
}
|
|
147
121
|
|
|
148
|
-
// Convenience methods for each management command
|
|
122
|
+
// --- Convenience methods for each management command ---
|
|
149
123
|
|
|
150
124
|
public async startProxy(config: any): Promise<void> {
|
|
151
|
-
await this.sendCommand('start', { config });
|
|
125
|
+
await this.bridge.sendCommand('start', { config });
|
|
152
126
|
}
|
|
153
127
|
|
|
154
128
|
public async stopProxy(): Promise<void> {
|
|
155
|
-
await this.sendCommand('stop');
|
|
129
|
+
await this.bridge.sendCommand('stop', {} as Record<string, never>);
|
|
156
130
|
}
|
|
157
131
|
|
|
158
132
|
public async updateRoutes(routes: IRouteConfig[]): Promise<void> {
|
|
159
|
-
await this.sendCommand('updateRoutes', { routes });
|
|
133
|
+
await this.bridge.sendCommand('updateRoutes', { routes });
|
|
160
134
|
}
|
|
161
135
|
|
|
162
136
|
public async getMetrics(): Promise<any> {
|
|
163
|
-
return this.sendCommand('getMetrics');
|
|
137
|
+
return this.bridge.sendCommand('getMetrics', {} as Record<string, never>);
|
|
164
138
|
}
|
|
165
139
|
|
|
166
140
|
public async getStatistics(): Promise<any> {
|
|
167
|
-
return this.sendCommand('getStatistics');
|
|
141
|
+
return this.bridge.sendCommand('getStatistics', {} as Record<string, never>);
|
|
168
142
|
}
|
|
169
143
|
|
|
170
144
|
public async provisionCertificate(routeName: string): Promise<void> {
|
|
171
|
-
await this.sendCommand('provisionCertificate', { routeName });
|
|
145
|
+
await this.bridge.sendCommand('provisionCertificate', { routeName });
|
|
172
146
|
}
|
|
173
147
|
|
|
174
148
|
public async renewCertificate(routeName: string): Promise<void> {
|
|
175
|
-
await this.sendCommand('renewCertificate', { routeName });
|
|
149
|
+
await this.bridge.sendCommand('renewCertificate', { routeName });
|
|
176
150
|
}
|
|
177
151
|
|
|
178
152
|
public async getCertificateStatus(routeName: string): Promise<any> {
|
|
179
|
-
return this.sendCommand('getCertificateStatus', { routeName });
|
|
153
|
+
return this.bridge.sendCommand('getCertificateStatus', { routeName });
|
|
180
154
|
}
|
|
181
155
|
|
|
182
156
|
public async getListeningPorts(): Promise<number[]> {
|
|
183
|
-
const result = await this.sendCommand('getListeningPorts');
|
|
157
|
+
const result = await this.bridge.sendCommand('getListeningPorts', {} as Record<string, never>);
|
|
184
158
|
return result?.ports ?? [];
|
|
185
159
|
}
|
|
186
160
|
|
|
187
161
|
public async getNftablesStatus(): Promise<any> {
|
|
188
|
-
return this.sendCommand('getNftablesStatus');
|
|
162
|
+
return this.bridge.sendCommand('getNftablesStatus', {} as Record<string, never>);
|
|
189
163
|
}
|
|
190
164
|
|
|
191
165
|
public async setSocketHandlerRelay(socketPath: string): Promise<void> {
|
|
192
|
-
await this.sendCommand('setSocketHandlerRelay', { socketPath });
|
|
166
|
+
await this.bridge.sendCommand('setSocketHandlerRelay', { socketPath });
|
|
193
167
|
}
|
|
194
168
|
|
|
195
169
|
public async addListeningPort(port: number): Promise<void> {
|
|
196
|
-
await this.sendCommand('addListeningPort', { port });
|
|
170
|
+
await this.bridge.sendCommand('addListeningPort', { port });
|
|
197
171
|
}
|
|
198
172
|
|
|
199
173
|
public async removeListeningPort(port: number): Promise<void> {
|
|
200
|
-
await this.sendCommand('removeListeningPort', { port });
|
|
174
|
+
await this.bridge.sendCommand('removeListeningPort', { port });
|
|
201
175
|
}
|
|
202
176
|
|
|
203
177
|
public async loadCertificate(domain: string, cert: string, key: string, ca?: string): Promise<void> {
|
|
204
|
-
await this.sendCommand('loadCertificate', { domain, cert, key, ca });
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Kill the Rust process and clean up all stdio streams.
|
|
209
|
-
*/
|
|
210
|
-
public kill(): void {
|
|
211
|
-
if (this.process) {
|
|
212
|
-
const proc = this.process;
|
|
213
|
-
this.process = null;
|
|
214
|
-
this.isRunning = false;
|
|
215
|
-
|
|
216
|
-
// Close readline (reads from stdout)
|
|
217
|
-
if (this.readline) {
|
|
218
|
-
this.readline.close();
|
|
219
|
-
this.readline = null;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Reject pending requests
|
|
223
|
-
for (const [, pending] of this.pendingRequests) {
|
|
224
|
-
clearTimeout(pending.timer);
|
|
225
|
-
pending.reject(new Error('RustProxy process killed'));
|
|
226
|
-
}
|
|
227
|
-
this.pendingRequests.clear();
|
|
228
|
-
|
|
229
|
-
// Remove all listeners so nothing keeps references
|
|
230
|
-
proc.removeAllListeners();
|
|
231
|
-
proc.stdout?.removeAllListeners();
|
|
232
|
-
proc.stderr?.removeAllListeners();
|
|
233
|
-
proc.stdin?.removeAllListeners();
|
|
234
|
-
|
|
235
|
-
// Kill the process
|
|
236
|
-
try { proc.kill('SIGTERM'); } catch { /* already dead */ }
|
|
237
|
-
|
|
238
|
-
// Destroy all stdio pipes to free handles
|
|
239
|
-
try { proc.stdin?.destroy(); } catch { /* ignore */ }
|
|
240
|
-
try { proc.stdout?.destroy(); } catch { /* ignore */ }
|
|
241
|
-
try { proc.stderr?.destroy(); } catch { /* ignore */ }
|
|
242
|
-
|
|
243
|
-
// Unref process so Node doesn't wait for it
|
|
244
|
-
try { proc.unref(); } catch { /* ignore */ }
|
|
245
|
-
|
|
246
|
-
// Force kill after 5 seconds
|
|
247
|
-
setTimeout(() => {
|
|
248
|
-
try { proc.kill('SIGKILL'); } catch { /* already dead */ }
|
|
249
|
-
}, 5000).unref();
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Whether the bridge is currently running.
|
|
255
|
-
*/
|
|
256
|
-
public get running(): boolean {
|
|
257
|
-
return this.isRunning;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
private handleLine(line: string): void {
|
|
261
|
-
if (!line) return;
|
|
262
|
-
|
|
263
|
-
let parsed: any;
|
|
264
|
-
try {
|
|
265
|
-
parsed = JSON.parse(line);
|
|
266
|
-
} catch {
|
|
267
|
-
logger.log('warn', `Non-JSON output from RustProxy: ${line}`, { component: 'rust-bridge' });
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Check if it's an event (has 'event' field)
|
|
272
|
-
if ('event' in parsed) {
|
|
273
|
-
const event = parsed as IManagementEvent;
|
|
274
|
-
this.emit(`management:${event.event}`, event.data);
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Otherwise it's a response (has 'id' field)
|
|
279
|
-
if ('id' in parsed) {
|
|
280
|
-
const response = parsed as IManagementResponse;
|
|
281
|
-
const pending = this.pendingRequests.get(response.id);
|
|
282
|
-
if (pending) {
|
|
283
|
-
clearTimeout(pending.timer);
|
|
284
|
-
this.pendingRequests.delete(response.id);
|
|
285
|
-
if (response.success) {
|
|
286
|
-
pending.resolve(response.result);
|
|
287
|
-
} else {
|
|
288
|
-
pending.reject(new Error(response.error || 'Unknown error from RustProxy'));
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
private cleanup(): void {
|
|
295
|
-
this.isRunning = false;
|
|
296
|
-
this.process = null;
|
|
297
|
-
|
|
298
|
-
if (this.readline) {
|
|
299
|
-
this.readline.close();
|
|
300
|
-
this.readline = null;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Reject all pending requests
|
|
304
|
-
for (const [id, pending] of this.pendingRequests) {
|
|
305
|
-
clearTimeout(pending.timer);
|
|
306
|
-
pending.reject(new Error('RustProxy process exited'));
|
|
307
|
-
}
|
|
308
|
-
this.pendingRequests.clear();
|
|
178
|
+
await this.bridge.sendCommand('loadCertificate', { domain, cert, key, ca });
|
|
309
179
|
}
|
|
310
180
|
}
|
|
@@ -3,7 +3,6 @@ import { logger } from '../../core/utils/logger.js';
|
|
|
3
3
|
|
|
4
4
|
// Rust bridge and helpers
|
|
5
5
|
import { RustProxyBridge } from './rust-proxy-bridge.js';
|
|
6
|
-
import { RustBinaryLocator } from './rust-binary-locator.js';
|
|
7
6
|
import { RoutePreprocessor } from './route-preprocessor.js';
|
|
8
7
|
import { SocketHandlerServer } from './socket-handler-server.js';
|
|
9
8
|
import { RustMetricsAdapter } from './rust-metrics-adapter.js';
|
|
@@ -120,7 +119,7 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
120
119
|
if (!spawned) {
|
|
121
120
|
throw new Error(
|
|
122
121
|
'RustProxy binary not found. Set SMARTPROXY_RUST_BINARY env var, install the platform package, ' +
|
|
123
|
-
'or build locally with:
|
|
122
|
+
'or build locally with: pnpm build'
|
|
124
123
|
);
|
|
125
124
|
}
|
|
126
125
|
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { ICertificateData, ICertificateFailure, ICertificateExpiring } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Subscribers callback definitions for Port80Handler events
|
|
4
|
-
*/
|
|
5
|
-
export interface Port80HandlerSubscribers {
|
|
6
|
-
onCertificateIssued?: (data: ICertificateData) => void;
|
|
7
|
-
onCertificateRenewed?: (data: ICertificateData) => void;
|
|
8
|
-
onCertificateFailed?: (data: ICertificateFailure) => void;
|
|
9
|
-
onCertificateExpiring?: (data: ICertificateExpiring) => void;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Subscribes to Port80Handler events based on provided callbacks
|
|
13
|
-
*/
|
|
14
|
-
export declare function subscribeToPort80Handler(handler: any, subscribers: Port80HandlerSubscribers): void;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
// Port80Handler removed - use SmartCertManager instead
|
|
2
|
-
import { Port80HandlerEvents } from './types.js';
|
|
3
|
-
/**
|
|
4
|
-
* Subscribes to Port80Handler events based on provided callbacks
|
|
5
|
-
*/
|
|
6
|
-
export function subscribeToPort80Handler(handler, subscribers) {
|
|
7
|
-
if (subscribers.onCertificateIssued) {
|
|
8
|
-
handler.on(Port80HandlerEvents.CERTIFICATE_ISSUED, subscribers.onCertificateIssued);
|
|
9
|
-
}
|
|
10
|
-
if (subscribers.onCertificateRenewed) {
|
|
11
|
-
handler.on(Port80HandlerEvents.CERTIFICATE_RENEWED, subscribers.onCertificateRenewed);
|
|
12
|
-
}
|
|
13
|
-
if (subscribers.onCertificateFailed) {
|
|
14
|
-
handler.on(Port80HandlerEvents.CERTIFICATE_FAILED, subscribers.onCertificateFailed);
|
|
15
|
-
}
|
|
16
|
-
if (subscribers.onCertificateExpiring) {
|
|
17
|
-
handler.on(Port80HandlerEvents.CERTIFICATE_EXPIRING, subscribers.onCertificateExpiring);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXZlbnRVdGlscy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2NvbW1vbi9ldmVudFV0aWxzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLHVEQUF1RDtBQUN2RCxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFhakQ7O0dBRUc7QUFDSCxNQUFNLFVBQVUsd0JBQXdCLENBQ3RDLE9BQVksRUFDWixXQUFxQztJQUVyQyxJQUFJLFdBQVcsQ0FBQyxtQkFBbUIsRUFBRSxDQUFDO1FBQ3BDLE9BQU8sQ0FBQyxFQUFFLENBQUMsbUJBQW1CLENBQUMsa0JBQWtCLEVBQUUsV0FBVyxDQUFDLG1CQUFtQixDQUFDLENBQUM7SUFDdEYsQ0FBQztJQUNELElBQUksV0FBVyxDQUFDLG9CQUFvQixFQUFFLENBQUM7UUFDckMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxtQkFBbUIsQ0FBQyxtQkFBbUIsRUFBRSxXQUFXLENBQUMsb0JBQW9CLENBQUMsQ0FBQztJQUN4RixDQUFDO0lBQ0QsSUFBSSxXQUFXLENBQUMsbUJBQW1CLEVBQUUsQ0FBQztRQUNwQyxPQUFPLENBQUMsRUFBRSxDQUFDLG1CQUFtQixDQUFDLGtCQUFrQixFQUFFLFdBQVcsQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDO0lBQ3RGLENBQUM7SUFDRCxJQUFJLFdBQVcsQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO1FBQ3RDLE9BQU8sQ0FBQyxFQUFFLENBQUMsbUJBQW1CLENBQUMsb0JBQW9CLEVBQUUsV0FBVyxDQUFDLHFCQUFxQixDQUFDLENBQUM7SUFDMUYsQ0FBQztBQUNILENBQUMifQ==
|