@push.rocks/smartvpn 1.3.0 → 1.4.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/dist_rust/smartvpn_daemon_linux_amd64 +0 -0
- package/dist_rust/smartvpn_daemon_linux_arm64 +0 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/smartvpn.classes.vpnconfig.js +6 -3
- package/dist_ts/smartvpn.interfaces.d.ts +10 -0
- package/package.json +9 -8
- package/readme.md +141 -14
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/smartvpn.classes.vpnconfig.ts +5 -2
- package/ts/smartvpn.interfaces.ts +10 -0
|
Binary file
|
|
Binary file
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartvpn',
|
|
6
|
-
version: '1.
|
|
6
|
+
version: '1.4.1',
|
|
7
7
|
description: 'A VPN solution with TypeScript control plane and Rust data plane daemon'
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxzQkFBc0I7SUFDNUIsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLHlFQUF5RTtDQUN2RixDQUFBIn0=
|
|
@@ -10,8 +10,11 @@ export class VpnConfig {
|
|
|
10
10
|
if (!config.serverUrl) {
|
|
11
11
|
throw new Error('VpnConfig: serverUrl is required');
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
// For QUIC-only transport, serverUrl is a host:port address; for WebSocket/auto it must be ws:// or wss://
|
|
14
|
+
if (config.transport !== 'quic') {
|
|
15
|
+
if (!config.serverUrl.startsWith('wss://') && !config.serverUrl.startsWith('ws://')) {
|
|
16
|
+
throw new Error('VpnConfig: serverUrl must start with wss:// or ws:// (for WebSocket transport)');
|
|
17
|
+
}
|
|
15
18
|
}
|
|
16
19
|
if (!config.serverPublicKey) {
|
|
17
20
|
throw new Error('VpnConfig: serverPublicKey is required');
|
|
@@ -95,4 +98,4 @@ export class VpnConfig {
|
|
|
95
98
|
return !isNaN(prefixNum) && prefixNum >= 0 && prefixNum <= 32;
|
|
96
99
|
}
|
|
97
100
|
}
|
|
98
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
101
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnR2cG4uY2xhc3Nlcy52cG5jb25maWcuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydHZwbi5jbGFzc2VzLnZwbmNvbmZpZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHVCQUF1QixDQUFDO0FBTWpEOztHQUVHO0FBQ0gsTUFBTSxPQUFPLFNBQVM7SUFDcEI7O09BRUc7SUFDSSxNQUFNLENBQUMsb0JBQW9CLENBQUMsTUFBd0I7UUFDekQsSUFBSSxDQUFDLE1BQU0sQ0FBQyxTQUFTLEVBQUUsQ0FBQztZQUN0QixNQUFNLElBQUksS0FBSyxDQUFDLGtDQUFrQyxDQUFDLENBQUM7UUFDdEQsQ0FBQztRQUNELDJHQUEyRztRQUMzRyxJQUFJLE1BQU0sQ0FBQyxTQUFTLEtBQUssTUFBTSxFQUFFLENBQUM7WUFDaEMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsVUFBVSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztnQkFDcEYsTUFBTSxJQUFJLEtBQUssQ0FBQyxnRkFBZ0YsQ0FBQyxDQUFDO1lBQ3BHLENBQUM7UUFDSCxDQUFDO1FBQ0QsSUFBSSxDQUFDLE1BQU0sQ0FBQyxlQUFlLEVBQUUsQ0FBQztZQUM1QixNQUFNLElBQUksS0FBSyxDQUFDLHdDQUF3QyxDQUFDLENBQUM7UUFDNUQsQ0FBQztRQUNELElBQUksTUFBTSxDQUFDLEdBQUcsS0FBSyxTQUFTLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxHQUFHLEdBQUcsSUFBSSxNQUFNLENBQUMsR0FBRyxHQUFHLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDekUsTUFBTSxJQUFJLEtBQUssQ0FBQyw4Q0FBOEMsQ0FBQyxDQUFDO1FBQ2xFLENBQUM7UUFDRCxJQUFJLE1BQU0sQ0FBQyxxQkFBcUIsS0FBSyxTQUFTLElBQUksTUFBTSxD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
|
|
@@ -21,6 +21,10 @@ export interface IVpnClientConfig {
|
|
|
21
21
|
mtu?: number;
|
|
22
22
|
/** Keepalive interval in seconds (default: 30) */
|
|
23
23
|
keepaliveIntervalSecs?: number;
|
|
24
|
+
/** Transport protocol: 'auto' (default, tries QUIC then WS), 'websocket', or 'quic' */
|
|
25
|
+
transport?: 'auto' | 'websocket' | 'quic';
|
|
26
|
+
/** For QUIC: SHA-256 hash of server certificate (base64) for cert pinning */
|
|
27
|
+
serverCertHash?: string;
|
|
24
28
|
}
|
|
25
29
|
export interface IVpnClientOptions {
|
|
26
30
|
transport: TVpnTransportOptions;
|
|
@@ -51,6 +55,12 @@ export interface IVpnServerConfig {
|
|
|
51
55
|
defaultRateLimitBytesPerSec?: number;
|
|
52
56
|
/** Default burst size for new clients (bytes). Omit for unlimited. */
|
|
53
57
|
defaultBurstBytes?: number;
|
|
58
|
+
/** Transport mode: 'both' (default, WS+QUIC), 'websocket', or 'quic' */
|
|
59
|
+
transportMode?: 'websocket' | 'quic' | 'both';
|
|
60
|
+
/** QUIC listen address (host:port). Defaults to listenAddr. */
|
|
61
|
+
quicListenAddr?: string;
|
|
62
|
+
/** QUIC idle timeout in seconds (default: 30) */
|
|
63
|
+
quicIdleTimeoutSecs?: number;
|
|
54
64
|
}
|
|
55
65
|
export interface IVpnServerOptions {
|
|
56
66
|
transport: TVpnTransportOptions;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@push.rocks/smartvpn",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A VPN solution with TypeScript control plane and Rust data plane daemon",
|
|
6
6
|
"type": "module",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"typings": "dist_ts/index.d.ts",
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "(tsbuild tsfolders --allowimplicitany) && (tsrust)",
|
|
14
|
+
"test:before": "(tsrust)",
|
|
14
15
|
"test": "tstest test/ --verbose",
|
|
15
16
|
"buildDocs": "tsdoc"
|
|
16
17
|
},
|
|
@@ -28,15 +29,15 @@
|
|
|
28
29
|
],
|
|
29
30
|
"license": "MIT",
|
|
30
31
|
"dependencies": {
|
|
31
|
-
"@push.rocks/
|
|
32
|
-
"@push.rocks/
|
|
32
|
+
"@push.rocks/smartpath": "^6.0.0",
|
|
33
|
+
"@push.rocks/smartrust": "^1.3.2"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
|
-
"@git.zone/tsbuild": "^
|
|
36
|
-
"@git.zone/tsrun": "^
|
|
37
|
-
"@git.zone/
|
|
38
|
-
"@git.zone/
|
|
39
|
-
"@types/node": "^
|
|
36
|
+
"@git.zone/tsbuild": "^4.3.0",
|
|
37
|
+
"@git.zone/tsrun": "^2.0.1",
|
|
38
|
+
"@git.zone/tsrust": "^1.3.0",
|
|
39
|
+
"@git.zone/tstest": "^3.5.0",
|
|
40
|
+
"@types/node": "^25.5.0"
|
|
40
41
|
},
|
|
41
42
|
"files": [
|
|
42
43
|
"ts/**/*",
|
package/readme.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
A high-performance VPN with a **TypeScript control plane** and a **Rust data plane daemon**. Manage VPN connections with clean, fully-typed APIs while all networking heavy lifting — encryption, tunneling, QoS, rate limiting — runs at native speed in Rust.
|
|
4
4
|
|
|
5
|
+
🔒 **Noise NK** handshake + **XChaCha20-Poly1305** encryption
|
|
6
|
+
🚀 **Dual transport**: WebSocket (Cloudflare-friendly) and raw **QUIC** (with datagram support)
|
|
7
|
+
📊 **Adaptive QoS**: packet classification, priority queues, per-client rate limiting
|
|
8
|
+
🔄 **Auto-transport**: tries QUIC first, falls back to WebSocket seamlessly
|
|
9
|
+
📡 **Real-time telemetry**: RTT, jitter, loss, link health — all exposed via typed APIs
|
|
10
|
+
|
|
5
11
|
## Issue Reporting and Security
|
|
6
12
|
|
|
7
13
|
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
|
|
@@ -17,11 +23,13 @@ pnpm install @push.rocks/smartvpn
|
|
|
17
23
|
```
|
|
18
24
|
TypeScript (control plane) Rust (data plane)
|
|
19
25
|
┌──────────────────────────┐ ┌────────────────────────────────────┐
|
|
20
|
-
│ VpnClient / VpnServer │ │ smartvpn_daemon
|
|
26
|
+
│ VpnClient / VpnServer │ │ smartvpn_daemon │
|
|
21
27
|
│ └─ VpnBridge │──stdio/──▶ │ ├─ management (JSON IPC) │
|
|
22
|
-
│ └─ RustBridge │ socket │ ├─
|
|
23
|
-
│ (smartrust) │ │ ├─
|
|
24
|
-
└──────────────────────────┘ │
|
|
28
|
+
│ └─ RustBridge │ socket │ ├─ transport_trait (abstraction) │
|
|
29
|
+
│ (smartrust) │ │ │ ├─ transport (WebSocket/TLS) │
|
|
30
|
+
└──────────────────────────┘ │ │ └─ quic_transport (QUIC/UDP) │
|
|
31
|
+
│ ├─ crypto (Noise NK + XCha20) │
|
|
32
|
+
│ ├─ codec (binary framing) │
|
|
25
33
|
│ ├─ keepalive (adaptive state FSM) │
|
|
26
34
|
│ ├─ telemetry (RTT/jitter/loss) │
|
|
27
35
|
│ ├─ qos (classify + priority Q) │
|
|
@@ -37,8 +45,10 @@ TypeScript (control plane) Rust (data plane)
|
|
|
37
45
|
|
|
38
46
|
| Decision | Choice | Why |
|
|
39
47
|
|----------|--------|-----|
|
|
40
|
-
| Transport | WebSocket
|
|
41
|
-
|
|
|
48
|
+
| Transport | WebSocket + QUIC (dual) | WS works through Cloudflare; QUIC gives lower latency + unreliable datagrams |
|
|
49
|
+
| Auto-transport | QUIC first, WS fallback | Best performance when QUIC is available, graceful degradation when it's not |
|
|
50
|
+
| Encryption | Noise NK + XChaCha20-Poly1305 | Strong forward secrecy, large nonce space (no counter sync needed) |
|
|
51
|
+
| QUIC auth | Certificate hash pinning | WireGuard-style trust model — no CA needed, just pin the server cert hash |
|
|
42
52
|
| Keepalive | Adaptive app-level pings | Cloudflare drops WS pings; interval adapts to link health (10–60s) |
|
|
43
53
|
| QoS | Packet classification + priority queues | DNS/SSH/ICMP always drain first; bulk flows get deprioritized |
|
|
44
54
|
| Rate limiting | Per-client token bucket | Byte-granular, dynamically reconfigurable via IPC |
|
|
@@ -89,6 +99,39 @@ await client.disconnect();
|
|
|
89
99
|
client.stop();
|
|
90
100
|
```
|
|
91
101
|
|
|
102
|
+
### VPN Client with QUIC
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { VpnClient } from '@push.rocks/smartvpn';
|
|
106
|
+
|
|
107
|
+
// Explicit QUIC — serverUrl is host:port, pinned by cert hash
|
|
108
|
+
const quicClient = new VpnClient({
|
|
109
|
+
transport: { transport: 'stdio' },
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
await quicClient.start();
|
|
113
|
+
|
|
114
|
+
const { assignedIp } = await quicClient.connect({
|
|
115
|
+
serverUrl: 'vpn.example.com:443',
|
|
116
|
+
serverPublicKey: 'BASE64_SERVER_PUBLIC_KEY',
|
|
117
|
+
transport: 'quic',
|
|
118
|
+
serverCertHash: 'BASE64_SHA256_CERT_HASH', // printed by server on startup
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Or use auto-transport: tries QUIC first (3s timeout), falls back to WS
|
|
122
|
+
const autoClient = new VpnClient({
|
|
123
|
+
transport: { transport: 'stdio' },
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
await autoClient.start();
|
|
127
|
+
|
|
128
|
+
await autoClient.connect({
|
|
129
|
+
serverUrl: 'wss://vpn.example.com/tunnel', // WS URL — host:port extracted for QUIC attempt
|
|
130
|
+
serverPublicKey: 'BASE64_SERVER_PUBLIC_KEY',
|
|
131
|
+
transport: 'auto', // default — QUIC first, then WS
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
92
135
|
### VPN Server
|
|
93
136
|
|
|
94
137
|
```typescript
|
|
@@ -100,10 +143,9 @@ const server = new VpnServer({
|
|
|
100
143
|
|
|
101
144
|
// Generate a Noise keypair first
|
|
102
145
|
await server.start();
|
|
103
|
-
// If you don't have keys yet:
|
|
104
146
|
const keypair = await server.generateKeypair();
|
|
105
147
|
|
|
106
|
-
// Start the VPN listener
|
|
148
|
+
// Start the VPN listener
|
|
107
149
|
await server.start({
|
|
108
150
|
listenAddr: '0.0.0.0:443',
|
|
109
151
|
privateKey: keypair.privateKey,
|
|
@@ -112,6 +154,12 @@ await server.start({
|
|
|
112
154
|
dns: ['1.1.1.1'],
|
|
113
155
|
mtu: 1420,
|
|
114
156
|
enableNat: true,
|
|
157
|
+
// Transport mode: 'websocket', 'quic', or 'both' (default)
|
|
158
|
+
transportMode: 'both',
|
|
159
|
+
// Optional: separate QUIC listen address
|
|
160
|
+
quicListenAddr: '0.0.0.0:4433',
|
|
161
|
+
// Optional: QUIC idle timeout
|
|
162
|
+
quicIdleTimeoutSecs: 30,
|
|
115
163
|
// Optional: default rate limit for all new clients
|
|
116
164
|
defaultRateLimitBytesPerSec: 10_000_000, // 10 MB/s
|
|
117
165
|
defaultBurstBytes: 20_000_000, // 20 MB burst
|
|
@@ -247,11 +295,66 @@ Both `VpnClient` and `VpnServer` extend `EventEmitter`:
|
|
|
247
295
|
```typescript
|
|
248
296
|
client.on('exit', ({ code, signal }) => { /* daemon exited */ });
|
|
249
297
|
client.on('reconnected', () => { /* socket reconnected */ });
|
|
298
|
+
client.on('status', (status) => { /* IVpnStatus update */ });
|
|
299
|
+
client.on('error', (error) => { /* error from daemon */ });
|
|
250
300
|
|
|
251
301
|
server.on('client-connected', (info) => { /* IVpnClientInfo */ });
|
|
252
302
|
server.on('client-disconnected', ({ clientId, reason }) => { /* ... */ });
|
|
303
|
+
server.on('started', () => { /* server listener started */ });
|
|
304
|
+
server.on('stopped', () => { /* server listener stopped */ });
|
|
253
305
|
```
|
|
254
306
|
|
|
307
|
+
## 🌐 Transport Modes
|
|
308
|
+
|
|
309
|
+
smartvpn supports two transport protocols through a unified transport abstraction layer. Both use the same encryption, framing, and QoS pipeline — the transport is swappable without changing any application logic.
|
|
310
|
+
|
|
311
|
+
### WebSocket (default)
|
|
312
|
+
|
|
313
|
+
- Works through Cloudflare, reverse proxies, and HTTP load balancers
|
|
314
|
+
- Reliable delivery only (no datagram support)
|
|
315
|
+
- URL format: `wss://host/path` or `ws://host:port/path`
|
|
316
|
+
|
|
317
|
+
### QUIC
|
|
318
|
+
|
|
319
|
+
- Lower latency, built-in multiplexing, 0-RTT connection establishment
|
|
320
|
+
- Supports **unreliable datagrams** for IP packets (with automatic fallback to reliable if oversized)
|
|
321
|
+
- Certificate hash pinning — no CA chain needed, WireGuard-style trust
|
|
322
|
+
- URL format: `host:port`
|
|
323
|
+
- ALPN protocol: `smartvpn`
|
|
324
|
+
|
|
325
|
+
### Auto-Transport (Recommended)
|
|
326
|
+
|
|
327
|
+
The default `transport: 'auto'` mode gives you the best of both worlds:
|
|
328
|
+
|
|
329
|
+
1. Extract `host:port` from the WebSocket URL
|
|
330
|
+
2. Attempt QUIC connection (3-second timeout)
|
|
331
|
+
3. If QUIC fails or times out → fall back to WebSocket
|
|
332
|
+
4. Completely transparent to the application
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
await client.connect({
|
|
336
|
+
serverUrl: 'wss://vpn.example.com/tunnel',
|
|
337
|
+
serverPublicKey: '...',
|
|
338
|
+
transport: 'auto', // default — QUIC first, WS fallback
|
|
339
|
+
});
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Server Dual-Mode
|
|
343
|
+
|
|
344
|
+
The server can listen on both transports simultaneously:
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
await server.start({
|
|
348
|
+
listenAddr: '0.0.0.0:443', // WebSocket listener
|
|
349
|
+
quicListenAddr: '0.0.0.0:4433', // QUIC listener (optional, defaults to listenAddr)
|
|
350
|
+
transportMode: 'both', // 'websocket' | 'quic' | 'both' (default)
|
|
351
|
+
quicIdleTimeoutSecs: 30, // QUIC connection idle timeout
|
|
352
|
+
// ... other config
|
|
353
|
+
});
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
When using `'both'` mode, the server logs the QUIC certificate hash on startup — share this with clients for cert pinning.
|
|
357
|
+
|
|
255
358
|
## 📊 QoS System
|
|
256
359
|
|
|
257
360
|
The Rust daemon includes a full QoS stack that operates on decrypted IP packets:
|
|
@@ -337,9 +440,26 @@ Post-handshake, all IP packets are encrypted with **XChaCha20-Poly1305**:
|
|
|
337
440
|
- 16-byte authentication tags
|
|
338
441
|
- Wire format: `[nonce:24B][ciphertext:var][tag:16B]`
|
|
339
442
|
|
|
443
|
+
### QUIC Certificate Pinning
|
|
444
|
+
|
|
445
|
+
When using QUIC transport, the server generates a self-signed TLS certificate (or uses a configured PEM). Instead of relying on a CA chain, clients pin the server's certificate by its **SHA-256 hash** (base64-encoded) — a WireGuard-inspired trust model:
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
// Server logs the cert hash on startup:
|
|
449
|
+
// "QUIC cert hash: <BASE64_HASH>"
|
|
450
|
+
|
|
451
|
+
// Client pins it:
|
|
452
|
+
await client.connect({
|
|
453
|
+
serverUrl: 'vpn.example.com:443',
|
|
454
|
+
transport: 'quic',
|
|
455
|
+
serverCertHash: '<BASE64_HASH>',
|
|
456
|
+
serverPublicKey: '...',
|
|
457
|
+
});
|
|
458
|
+
```
|
|
459
|
+
|
|
340
460
|
## 📦 Binary Protocol
|
|
341
461
|
|
|
342
|
-
Inside the WebSocket
|
|
462
|
+
Inside the tunnel (both WebSocket and QUIC reliable channels), packets use a simple binary framing:
|
|
343
463
|
|
|
344
464
|
```
|
|
345
465
|
┌──────────┬──────────┬────────────────────┐
|
|
@@ -359,6 +479,8 @@ Inside the WebSocket tunnel, packets use a simple binary framing:
|
|
|
359
479
|
| `SessionResumeErr` | `0x32` | Resume rejected |
|
|
360
480
|
| `Disconnect` | `0x3F` | Graceful disconnect |
|
|
361
481
|
|
|
482
|
+
When QUIC datagrams are available, IP packets can optionally be sent via the unreliable datagram channel for lower latency. Packets that exceed the max datagram size automatically fall back to the reliable stream.
|
|
483
|
+
|
|
362
484
|
## 🛠️ Rust Daemon CLI
|
|
363
485
|
|
|
364
486
|
```bash
|
|
@@ -385,12 +507,12 @@ pnpm build
|
|
|
385
507
|
# Build Rust only (debug)
|
|
386
508
|
cd rust && cargo build
|
|
387
509
|
|
|
388
|
-
# Run all tests (
|
|
510
|
+
# Run all tests (77 Rust + 59 TypeScript)
|
|
389
511
|
cd rust && cargo test
|
|
390
512
|
pnpm test
|
|
391
513
|
```
|
|
392
514
|
|
|
393
|
-
## TypeScript Interfaces
|
|
515
|
+
## 📘 TypeScript Interfaces
|
|
394
516
|
|
|
395
517
|
<details>
|
|
396
518
|
<summary>Click to expand full type definitions</summary>
|
|
@@ -410,8 +532,10 @@ type TVpnTransportOptions =
|
|
|
410
532
|
|
|
411
533
|
// Client config
|
|
412
534
|
interface IVpnClientConfig {
|
|
413
|
-
serverUrl: string;
|
|
414
|
-
serverPublicKey: string;
|
|
535
|
+
serverUrl: string; // WS: 'wss://host/path' | QUIC: 'host:port'
|
|
536
|
+
serverPublicKey: string; // Base64-encoded Noise static key
|
|
537
|
+
transport?: 'auto' | 'websocket' | 'quic'; // Default: 'auto'
|
|
538
|
+
serverCertHash?: string; // SHA-256 cert hash (base64) for QUIC pinning
|
|
415
539
|
dns?: string[];
|
|
416
540
|
mtu?: number;
|
|
417
541
|
keepaliveIntervalSecs?: number;
|
|
@@ -429,6 +553,9 @@ interface IVpnServerConfig {
|
|
|
429
553
|
mtu?: number;
|
|
430
554
|
keepaliveIntervalSecs?: number;
|
|
431
555
|
enableNat?: boolean;
|
|
556
|
+
transportMode?: 'websocket' | 'quic' | 'both'; // Default: 'both'
|
|
557
|
+
quicListenAddr?: string; // Separate QUIC bind address
|
|
558
|
+
quicIdleTimeoutSecs?: number; // QUIC idle timeout (default: 30)
|
|
432
559
|
defaultRateLimitBytesPerSec?: number;
|
|
433
560
|
defaultBurstBytes?: number;
|
|
434
561
|
}
|
|
@@ -537,7 +664,7 @@ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark G
|
|
|
537
664
|
|
|
538
665
|
### Company Information
|
|
539
666
|
|
|
540
|
-
Task Venture Capital GmbH
|
|
667
|
+
Task Venture Capital GmbH
|
|
541
668
|
Registered at District Court Bremen HRB 35230 HB, Germany
|
|
542
669
|
|
|
543
670
|
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -15,8 +15,11 @@ export class VpnConfig {
|
|
|
15
15
|
if (!config.serverUrl) {
|
|
16
16
|
throw new Error('VpnConfig: serverUrl is required');
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
// For QUIC-only transport, serverUrl is a host:port address; for WebSocket/auto it must be ws:// or wss://
|
|
19
|
+
if (config.transport !== 'quic') {
|
|
20
|
+
if (!config.serverUrl.startsWith('wss://') && !config.serverUrl.startsWith('ws://')) {
|
|
21
|
+
throw new Error('VpnConfig: serverUrl must start with wss:// or ws:// (for WebSocket transport)');
|
|
22
|
+
}
|
|
20
23
|
}
|
|
21
24
|
if (!config.serverPublicKey) {
|
|
22
25
|
throw new Error('VpnConfig: serverPublicKey is required');
|
|
@@ -32,6 +32,10 @@ export interface IVpnClientConfig {
|
|
|
32
32
|
mtu?: number;
|
|
33
33
|
/** Keepalive interval in seconds (default: 30) */
|
|
34
34
|
keepaliveIntervalSecs?: number;
|
|
35
|
+
/** Transport protocol: 'auto' (default, tries QUIC then WS), 'websocket', or 'quic' */
|
|
36
|
+
transport?: 'auto' | 'websocket' | 'quic';
|
|
37
|
+
/** For QUIC: SHA-256 hash of server certificate (base64) for cert pinning */
|
|
38
|
+
serverCertHash?: string;
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
export interface IVpnClientOptions {
|
|
@@ -68,6 +72,12 @@ export interface IVpnServerConfig {
|
|
|
68
72
|
defaultRateLimitBytesPerSec?: number;
|
|
69
73
|
/** Default burst size for new clients (bytes). Omit for unlimited. */
|
|
70
74
|
defaultBurstBytes?: number;
|
|
75
|
+
/** Transport mode: 'both' (default, WS+QUIC), 'websocket', or 'quic' */
|
|
76
|
+
transportMode?: 'websocket' | 'quic' | 'both';
|
|
77
|
+
/** QUIC listen address (host:port). Defaults to listenAddr. */
|
|
78
|
+
quicListenAddr?: string;
|
|
79
|
+
/** QUIC idle timeout in seconds (default: 30) */
|
|
80
|
+
quicIdleTimeoutSecs?: number;
|
|
71
81
|
}
|
|
72
82
|
|
|
73
83
|
export interface IVpnServerOptions {
|