wse-server 2.3.2__tar.gz → 2.4.1__tar.gz

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.
Files changed (28) hide show
  1. {wse_server-2.3.2 → wse_server-2.4.1}/PKG-INFO +14 -10
  2. {wse_server-2.3.2 → wse_server-2.4.1}/README.md +13 -9
  3. {wse_server-2.3.2 → wse_server-2.4.1}/pyproject.toml +1 -1
  4. {wse_server-2.3.2 → wse_server-2.4.1}/rust/Cargo.lock +3 -3
  5. {wse_server-2.3.2 → wse_server-2.4.1}/rust/Cargo.toml +1 -1
  6. {wse_server-2.3.2 → wse_server-2.4.1}/rust/src/wse/cluster.rs +215 -26
  7. {wse_server-2.3.2 → wse_server-2.4.1}/rust/src/wse/compression.rs +213 -4
  8. {wse_server-2.3.2 → wse_server-2.4.1}/rust/src/wse/presence.rs +292 -33
  9. {wse_server-2.3.2 → wse_server-2.4.1}/rust/src/wse/recovery.rs +57 -4
  10. {wse_server-2.3.2 → wse_server-2.4.1}/rust/src/wse/security.rs +14 -9
  11. {wse_server-2.3.2 → wse_server-2.4.1}/rust/src/wse/sequencer.rs +99 -12
  12. {wse_server-2.3.2 → wse_server-2.4.1}/rust/src/wse/server.rs +311 -48
  13. {wse_server-2.3.2 → wse_server-2.4.1}/wse_server/__init__.py +1 -1
  14. {wse_server-2.3.2 → wse_server-2.4.1}/LICENSE +0 -0
  15. {wse_server-2.3.2 → wse_server-2.4.1}/rust/.cargo/config.toml +0 -0
  16. {wse_server-2.3.2 → wse_server-2.4.1}/rust/src/jwt.rs +0 -0
  17. {wse_server-2.3.2 → wse_server-2.4.1}/rust/src/lib.rs +0 -0
  18. {wse_server-2.3.2 → wse_server-2.4.1}/rust/src/wse/filters.rs +0 -0
  19. {wse_server-2.3.2 → wse_server-2.4.1}/rust/src/wse/mod.rs +0 -0
  20. {wse_server-2.3.2 → wse_server-2.4.1}/rust/src/wse/queue.rs +0 -0
  21. {wse_server-2.3.2 → wse_server-2.4.1}/rust/src/wse/rate_limiter.rs +0 -0
  22. {wse_server-2.3.2 → wse_server-2.4.1}/rust/src/wse/reliability.rs +0 -0
  23. {wse_server-2.3.2 → wse_server-2.4.1}/rust/src/wse/transformer.rs +0 -0
  24. {wse_server-2.3.2 → wse_server-2.4.1}/wse_server/_wse_accel.pyi +0 -0
  25. {wse_server-2.3.2 → wse_server-2.4.1}/wse_server/core/__init__.py +0 -0
  26. {wse_server-2.3.2 → wse_server-2.4.1}/wse_server/core/filters.py +0 -0
  27. {wse_server-2.3.2 → wse_server-2.4.1}/wse_server/core/types.py +0 -0
  28. {wse_server-2.3.2 → wse_server-2.4.1}/wse_server/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wse-server
3
- Version: 2.3.2
3
+ Version: 2.4.1
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -80,7 +80,7 @@ High-performance WebSocket server built in Rust with native clustering, E2E encr
80
80
  |---------|---------|
81
81
  | **Key exchange** | ECDH P-256 (per-connection keypair, automatic during handshake) |
82
82
  | **Encryption** | AES-GCM-256 with unique 12-byte IV per message |
83
- | **Key derivation** | HKDF-SHA256 (salt: `wse-encryption`, info: `aes-gcm-key`) |
83
+ | **Key derivation** | HKDF-SHA256 (no salt, info: `wse-encryption/aes-gcm-key`) |
84
84
  | **Wire format** | `E:` prefix + 12-byte IV + AES-GCM ciphertext + 16-byte auth tag |
85
85
  | **Key rotation** | Configurable rotation interval (default 1 hour), automatic renegotiation |
86
86
  | **Replay prevention** | Nonce cache (10K entries, 5-minute TTL) on the client side |
@@ -122,6 +122,7 @@ High-performance WebSocket server built in Rust with native clustering, E2E encr
122
122
  | **Memory management** | Global budget (default 256 MB), TTL eviction, LRU eviction when over budget |
123
123
  | **Zero-copy storage** | Recovery entries share `Bytes` (Arc) with the broadcast path |
124
124
  | **Recovery on reconnect** | `subscribe_with_recovery()` replays missed messages automatically |
125
+ | **Per-message stamp** | When recovery is enabled, every topic broadcast carries `tp`/`e`/`o` (topic, epoch, offset) so clients dedup idempotently and detect gaps from the messages themselves |
125
126
 
126
127
  ### Client SDKs (Python + TypeScript/React)
127
128
 
@@ -132,8 +133,8 @@ High-performance WebSocket server built in Rust with native clustering, E2E encr
132
133
  | **Circuit breaker** | CLOSED/OPEN/HALF_OPEN state machine, prevents connection storms |
133
134
  | **Rate limiting** | Client-side token bucket, coordinates with server feedback |
134
135
  | **E2E encryption** | Wire-compatible AES-GCM-256 + ECDH P-256 (both clients speak the same protocol) |
135
- | **Event sequencing** | Duplicate detection (sliding window) + out-of-order buffering |
136
- | **Network monitor** | Real-time latency, jitter, packet loss measurement, quality scoring |
136
+ | **Event sequencing** | Duplicate detection (sliding window) + per-topic `(epoch, offset)` idempotent dedup with automatic gap recovery |
137
+ | **Network monitor** | Real-time latency, jitter, and connection-quality scoring (Python client also estimates packet loss) |
137
138
  | **Priority queues** | 5 levels from CRITICAL to BACKGROUND |
138
139
  | **Offline queue** | IndexedDB persistence (TypeScript), replayed on reconnect |
139
140
  | **Compression** | Automatic zlib for messages above threshold |
@@ -146,8 +147,11 @@ High-performance WebSocket server built in Rust with native clustering, E2E encr
146
147
  |---------|---------|
147
148
  | **Origin validation** | Configure in reverse proxy (nginx/Caddy) to prevent CSWSH |
148
149
  | **Cookie auth** | `access_token` HTTP-only cookie with `Secure + SameSite=Lax` (OWASP recommended for browsers) |
149
- | **Frame protection** | 1 MB max frame size, serde_json parsing (no eval), escaped user IDs in server_ready |
150
+ | **Frame protection** | 1 MB max frame size, serde_json parsing (no eval), escaped user IDs in server_ready; inbound msgpack rejected beyond 64 nesting levels |
150
151
  | **Cluster frame protection** | zstd decompression output capped at 1 MB (MAX_FRAME_SIZE), protocol version validation |
152
+ | **Slowloris protection** | At most 512 concurrent pre-handshake connections; sockets that never complete the WS upgrade are bounded and dropped |
153
+ | **Bounded callback dispatch** | Callback mode caps in-flight `on_message` callbacks at 256 per connection; excess inbound work is shed and counted as rate-limited |
154
+ | **Fail-closed recovery ACL** | Recovery replay applies the connection's topic ACL — unauthorized topics are never replayed or position-probed |
151
155
 
152
156
  ---
153
157
 
@@ -359,10 +363,10 @@ result = server.subscribe_with_recovery(
359
363
  epoch=client_epoch, # From previous session
360
364
  offset=client_offset, # From previous session
361
365
  )
362
- # {"topics": {"prices": {"epoch": 123, "offset": 456, "recovered": True, "count": 12}}}
366
+ # {"topics": {"prices": {"epoch": "0000007b", "offset": 456, "recovered": True, "count": 12}}}
363
367
  ```
364
368
 
365
- The server maintains per-topic ring buffers (power-of-2 capacity, bitmask indexing). Clients store the `epoch` and `offset` from their last received message. On reconnect, the server replays missed messages from the ring buffer. If the gap is too large or the epoch has changed, the client receives a `NotRecovered` status and should re-subscribe from scratch.
369
+ The server maintains per-topic ring buffers (power-of-2 capacity, bitmask indexing). When recovery is enabled, every topic broadcast is stamped with `tp`/`e`/`o` (topic, epoch, offset) fields, so clients track their per-topic position from the messages themselves, drop duplicates idempotently by `(epoch, offset)`, and automatically request recovery when they detect an offset gap. On reconnect, the server replays missed messages from the ring buffer. If the gap is too large or the epoch has changed, the client receives a `NotRecovered` status and should re-subscribe from scratch.
366
370
 
367
371
  Memory is managed with a global budget (default 256 MB), TTL eviction for idle buffers, and LRU eviction when over budget.
368
372
 
@@ -543,7 +547,7 @@ def handle(event):
543
547
  client.run_forever()
544
548
  ```
545
549
 
546
- Key features: 4 reconnect strategies (exponential, linear, fibonacci, adaptive), connection pool with health scoring and 3 load balancing strategies, circuit breaker, token bucket rate limiter, event sequencer with dedup and reorder buffering, network quality monitoring (latency/jitter/packet loss).
550
+ Key features: 4 reconnect strategies (exponential, linear, fibonacci, adaptive), connection pool with health scoring and 3 load balancing strategies, circuit breaker, token bucket rate limiter, event sequencer with id dedup plus per-topic `(epoch, offset)` idempotent dedup and gap recovery, network quality monitoring (latency/jitter/packet loss).
547
551
 
548
552
  See [python-client/](python-client/) for full source and examples.
549
553
 
@@ -571,7 +575,7 @@ function App() {
571
575
 
572
576
  Key features: offline queue with IndexedDB persistence, adaptive quality management, connection pool with health scoring, E2E encryption (Web Crypto API), message batching, 5 priority levels, Zustand store for external state access.
573
577
 
574
- See [client/](client/) for full source and examples.
578
+ See [ts-client/](ts-client/) for full source and examples.
575
579
 
576
580
  ---
577
581
 
@@ -614,7 +618,7 @@ Working examples in the [`examples/`](examples/) directory:
614
618
  - [Rust Utilities](docs/RUST_UTILITIES.md) - priority queues, rate limiters, sequencing, compression, crypto, JWT
615
619
  - [Security](docs/SECURITY.md) - JWT, encryption, mTLS, rate limiting, circuit breaker
616
620
  - [Deployment](docs/DEPLOYMENT.md) - production setup, Docker, Kubernetes, cluster configuration
617
- - [Migration Guide](docs/MIGRATION.md) - upgrading from v1.x to v2.0
621
+ - [Migration Guide](docs/MIGRATION.md) - upgrading from v1.x to v2.x
618
622
  - [Contributing](CONTRIBUTING.md) - development setup and coding standards
619
623
  - [Changelog](CHANGELOG.md) - version history
620
624
 
@@ -39,7 +39,7 @@ High-performance WebSocket server built in Rust with native clustering, E2E encr
39
39
  |---------|---------|
40
40
  | **Key exchange** | ECDH P-256 (per-connection keypair, automatic during handshake) |
41
41
  | **Encryption** | AES-GCM-256 with unique 12-byte IV per message |
42
- | **Key derivation** | HKDF-SHA256 (salt: `wse-encryption`, info: `aes-gcm-key`) |
42
+ | **Key derivation** | HKDF-SHA256 (no salt, info: `wse-encryption/aes-gcm-key`) |
43
43
  | **Wire format** | `E:` prefix + 12-byte IV + AES-GCM ciphertext + 16-byte auth tag |
44
44
  | **Key rotation** | Configurable rotation interval (default 1 hour), automatic renegotiation |
45
45
  | **Replay prevention** | Nonce cache (10K entries, 5-minute TTL) on the client side |
@@ -81,6 +81,7 @@ High-performance WebSocket server built in Rust with native clustering, E2E encr
81
81
  | **Memory management** | Global budget (default 256 MB), TTL eviction, LRU eviction when over budget |
82
82
  | **Zero-copy storage** | Recovery entries share `Bytes` (Arc) with the broadcast path |
83
83
  | **Recovery on reconnect** | `subscribe_with_recovery()` replays missed messages automatically |
84
+ | **Per-message stamp** | When recovery is enabled, every topic broadcast carries `tp`/`e`/`o` (topic, epoch, offset) so clients dedup idempotently and detect gaps from the messages themselves |
84
85
 
85
86
  ### Client SDKs (Python + TypeScript/React)
86
87
 
@@ -91,8 +92,8 @@ High-performance WebSocket server built in Rust with native clustering, E2E encr
91
92
  | **Circuit breaker** | CLOSED/OPEN/HALF_OPEN state machine, prevents connection storms |
92
93
  | **Rate limiting** | Client-side token bucket, coordinates with server feedback |
93
94
  | **E2E encryption** | Wire-compatible AES-GCM-256 + ECDH P-256 (both clients speak the same protocol) |
94
- | **Event sequencing** | Duplicate detection (sliding window) + out-of-order buffering |
95
- | **Network monitor** | Real-time latency, jitter, packet loss measurement, quality scoring |
95
+ | **Event sequencing** | Duplicate detection (sliding window) + per-topic `(epoch, offset)` idempotent dedup with automatic gap recovery |
96
+ | **Network monitor** | Real-time latency, jitter, and connection-quality scoring (Python client also estimates packet loss) |
96
97
  | **Priority queues** | 5 levels from CRITICAL to BACKGROUND |
97
98
  | **Offline queue** | IndexedDB persistence (TypeScript), replayed on reconnect |
98
99
  | **Compression** | Automatic zlib for messages above threshold |
@@ -105,8 +106,11 @@ High-performance WebSocket server built in Rust with native clustering, E2E encr
105
106
  |---------|---------|
106
107
  | **Origin validation** | Configure in reverse proxy (nginx/Caddy) to prevent CSWSH |
107
108
  | **Cookie auth** | `access_token` HTTP-only cookie with `Secure + SameSite=Lax` (OWASP recommended for browsers) |
108
- | **Frame protection** | 1 MB max frame size, serde_json parsing (no eval), escaped user IDs in server_ready |
109
+ | **Frame protection** | 1 MB max frame size, serde_json parsing (no eval), escaped user IDs in server_ready; inbound msgpack rejected beyond 64 nesting levels |
109
110
  | **Cluster frame protection** | zstd decompression output capped at 1 MB (MAX_FRAME_SIZE), protocol version validation |
111
+ | **Slowloris protection** | At most 512 concurrent pre-handshake connections; sockets that never complete the WS upgrade are bounded and dropped |
112
+ | **Bounded callback dispatch** | Callback mode caps in-flight `on_message` callbacks at 256 per connection; excess inbound work is shed and counted as rate-limited |
113
+ | **Fail-closed recovery ACL** | Recovery replay applies the connection's topic ACL — unauthorized topics are never replayed or position-probed |
110
114
 
111
115
  ---
112
116
 
@@ -318,10 +322,10 @@ result = server.subscribe_with_recovery(
318
322
  epoch=client_epoch, # From previous session
319
323
  offset=client_offset, # From previous session
320
324
  )
321
- # {"topics": {"prices": {"epoch": 123, "offset": 456, "recovered": True, "count": 12}}}
325
+ # {"topics": {"prices": {"epoch": "0000007b", "offset": 456, "recovered": True, "count": 12}}}
322
326
  ```
323
327
 
324
- The server maintains per-topic ring buffers (power-of-2 capacity, bitmask indexing). Clients store the `epoch` and `offset` from their last received message. On reconnect, the server replays missed messages from the ring buffer. If the gap is too large or the epoch has changed, the client receives a `NotRecovered` status and should re-subscribe from scratch.
328
+ The server maintains per-topic ring buffers (power-of-2 capacity, bitmask indexing). When recovery is enabled, every topic broadcast is stamped with `tp`/`e`/`o` (topic, epoch, offset) fields, so clients track their per-topic position from the messages themselves, drop duplicates idempotently by `(epoch, offset)`, and automatically request recovery when they detect an offset gap. On reconnect, the server replays missed messages from the ring buffer. If the gap is too large or the epoch has changed, the client receives a `NotRecovered` status and should re-subscribe from scratch.
325
329
 
326
330
  Memory is managed with a global budget (default 256 MB), TTL eviction for idle buffers, and LRU eviction when over budget.
327
331
 
@@ -502,7 +506,7 @@ def handle(event):
502
506
  client.run_forever()
503
507
  ```
504
508
 
505
- Key features: 4 reconnect strategies (exponential, linear, fibonacci, adaptive), connection pool with health scoring and 3 load balancing strategies, circuit breaker, token bucket rate limiter, event sequencer with dedup and reorder buffering, network quality monitoring (latency/jitter/packet loss).
509
+ Key features: 4 reconnect strategies (exponential, linear, fibonacci, adaptive), connection pool with health scoring and 3 load balancing strategies, circuit breaker, token bucket rate limiter, event sequencer with id dedup plus per-topic `(epoch, offset)` idempotent dedup and gap recovery, network quality monitoring (latency/jitter/packet loss).
506
510
 
507
511
  See [python-client/](python-client/) for full source and examples.
508
512
 
@@ -530,7 +534,7 @@ function App() {
530
534
 
531
535
  Key features: offline queue with IndexedDB persistence, adaptive quality management, connection pool with health scoring, E2E encryption (Web Crypto API), message batching, 5 priority levels, Zustand store for external state access.
532
536
 
533
- See [client/](client/) for full source and examples.
537
+ See [ts-client/](ts-client/) for full source and examples.
534
538
 
535
539
  ---
536
540
 
@@ -573,7 +577,7 @@ Working examples in the [`examples/`](examples/) directory:
573
577
  - [Rust Utilities](docs/RUST_UTILITIES.md) - priority queues, rate limiters, sequencing, compression, crypto, JWT
574
578
  - [Security](docs/SECURITY.md) - JWT, encryption, mTLS, rate limiting, circuit breaker
575
579
  - [Deployment](docs/DEPLOYMENT.md) - production setup, Docker, Kubernetes, cluster configuration
576
- - [Migration Guide](docs/MIGRATION.md) - upgrading from v1.x to v2.0
580
+ - [Migration Guide](docs/MIGRATION.md) - upgrading from v1.x to v2.x
577
581
  - [Contributing](CONTRIBUTING.md) - development setup and coding standards
578
582
  - [Changelog](CHANGELOG.md) - version history
579
583
 
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "wse-server"
7
- version = "2.3.2"
7
+ version = "2.4.1"
8
8
  description = "Rust-powered WebSocket engine for Python. Up to 5M fan-out del/s. Zero-GIL JWT."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -1491,9 +1491,9 @@ dependencies = [
1491
1491
 
1492
1492
  [[package]]
1493
1493
  name = "rustls-webpki"
1494
- version = "0.103.10"
1494
+ version = "0.103.13"
1495
1495
  source = "registry+https://github.com/rust-lang/crates.io-index"
1496
- checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef"
1496
+ checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
1497
1497
  dependencies = [
1498
1498
  "aws-lc-rs",
1499
1499
  "ring",
@@ -2327,7 +2327,7 @@ dependencies = [
2327
2327
 
2328
2328
  [[package]]
2329
2329
  name = "wse-accel"
2330
- version = "2.3.2"
2330
+ version = "2.4.0"
2331
2331
  dependencies = [
2332
2332
  "aes-gcm",
2333
2333
  "ahash",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "wse-accel"
3
- version = "2.3.2"
3
+ version = "2.4.0"
4
4
  edition = "2024"
5
5
 
6
6
  [lib]