wse-client 2.2.0 → 2.3.0
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/README.md +38 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ High-performance WebSocket server built in Rust with native clustering, E2E encr
|
|
|
14
14
|
| Feature | Details |
|
|
15
15
|
|---------|---------|
|
|
16
16
|
| **Rust core** | tokio async runtime, tungstenite WebSocket transport, dedicated thread pool, zero GIL on the data path |
|
|
17
|
-
| **JWT authentication** |
|
|
17
|
+
| **JWT authentication** | HS256, RS256, ES256 algorithms via jsonwebtoken crate. Validated during handshake (0.01ms), cookie + Authorization header extraction, key rotation, kid validation |
|
|
18
18
|
| **Protocol negotiation** | `client_hello`/`server_hello` handshake with feature discovery, capability advertisement, version agreement |
|
|
19
19
|
| **Topic subscriptions** | Per-connection topic subscriptions with automatic cleanup on disconnect |
|
|
20
20
|
| **Pre-framed broadcast** | WebSocket frame built once, shared via Arc across all connections, single allocation per broadcast |
|
|
@@ -29,6 +29,9 @@ High-performance WebSocket server built in Rust with native clustering, E2E encr
|
|
|
29
29
|
| **Compression** | zlib for client-facing messages above threshold (default 1024 bytes) |
|
|
30
30
|
| **MessagePack** | Opt-in binary transport via `?format=msgpack`, roughly 2x faster serialization, 30% smaller |
|
|
31
31
|
| **Message signing** | Selective HMAC-SHA256 signing for critical operations, nonce-based replay prevention |
|
|
32
|
+
| **Queue groups** | Round-robin dispatch within named groups for load-balanced worker pools |
|
|
33
|
+
| **Topic ACL** | Per-connection allow/deny glob patterns for topic access control |
|
|
34
|
+
| **Graceful drain** | `drain()` sends Close frame to all clients, rejects new connections, notifies cluster peers |
|
|
32
35
|
|
|
33
36
|
### End-to-End Encryption
|
|
34
37
|
|
|
@@ -55,6 +58,7 @@ High-performance WebSocket server built in Rust with native clustering, E2E encr
|
|
|
55
58
|
| **Circuit breaker** | 10 failures to open, 60s reset, 3 half-open probe calls |
|
|
56
59
|
| **Dead letter queue** | 1000-entry ring buffer for failed cluster sends |
|
|
57
60
|
| **Presence sync** | PresenceUpdate/PresenceFull frames, CRDT last-write-wins conflict resolution |
|
|
61
|
+
| **Topology API** | `cluster_info()` returns connected peer list with address, instance_id, status |
|
|
58
62
|
|
|
59
63
|
### Presence Tracking
|
|
60
64
|
|
|
@@ -99,7 +103,7 @@ High-performance WebSocket server built in Rust with native clustering, E2E encr
|
|
|
99
103
|
|
|
100
104
|
| Feature | Details |
|
|
101
105
|
|---------|---------|
|
|
102
|
-
| **Origin validation** |
|
|
106
|
+
| **Origin validation** | Configure in reverse proxy (nginx/Caddy) to prevent CSWSH |
|
|
103
107
|
| **Cookie auth** | `access_token` HTTP-only cookie with `Secure + SameSite=Lax` (OWASP recommended for browsers) |
|
|
104
108
|
| **Frame protection** | 1 MB max frame size, serde_json parsing (no eval), escaped user IDs in server_ready |
|
|
105
109
|
| **Cluster frame protection** | zstd decompression output capped at 1 MB (MAX_FRAME_SIZE), protocol version validation |
|
|
@@ -163,9 +167,14 @@ token = rust_jwt_encode(
|
|
|
163
167
|
| `host` | required | Bind address |
|
|
164
168
|
| `port` | required | Bind port |
|
|
165
169
|
| `max_connections` | 1000 | Maximum concurrent WebSocket connections |
|
|
166
|
-
| `jwt_secret` | None |
|
|
170
|
+
| `jwt_secret` | None | JWT key for validation. HS256: shared secret (bytes, min 32). RS256/ES256: PEM public key. `None` disables auth |
|
|
167
171
|
| `jwt_issuer` | None | Expected `iss` claim. Skipped if `None` |
|
|
168
172
|
| `jwt_audience` | None | Expected `aud` claim. Skipped if `None` |
|
|
173
|
+
| `jwt_cookie_name` | "access_token" | Cookie name for JWT token extraction |
|
|
174
|
+
| `jwt_previous_secret` | None | Previous key for zero-downtime rotation. HS256: previous secret. RS256/ES256: previous public key PEM |
|
|
175
|
+
| `jwt_key_id` | None | Expected `kid` header claim. Rejects tokens with mismatched key ID |
|
|
176
|
+
| `jwt_algorithm` | None | JWT algorithm: `"HS256"` (default), `"RS256"`, or `"ES256"` |
|
|
177
|
+
| `jwt_private_key` | None | PEM private key for RS256/ES256 token encoding. Not needed for HS256 |
|
|
169
178
|
| `max_inbound_queue_size` | 131072 | Drain mode bounded queue capacity |
|
|
170
179
|
| `recovery_enabled` | False | Enable per-topic message recovery buffers |
|
|
171
180
|
| `recovery_buffer_size` | 128 | Ring buffer slots per topic (rounded to power-of-2) |
|
|
@@ -176,7 +185,6 @@ token = rust_jwt_encode(
|
|
|
176
185
|
| `presence_max_data_size` | 4096 | Max bytes for a user's presence metadata |
|
|
177
186
|
| `presence_max_members` | 0 | Max tracked members per topic (0 = unlimited) |
|
|
178
187
|
| `max_outbound_queue_bytes` | 16777216 | Per-connection outbound buffer limit (bytes, default 16 MB). Messages dropped when exceeded |
|
|
179
|
-
| `jwt_cookie_name` | "access_token" | Cookie name for JWT token extraction |
|
|
180
188
|
| `rate_limit_capacity` | 100000.0 | Token bucket capacity per connection |
|
|
181
189
|
| `rate_limit_refill` | 10000.0 | Token bucket refill rate per second |
|
|
182
190
|
| `max_message_size` | 1048576 | Maximum WebSocket frame size in bytes (default 1 MB) |
|
|
@@ -250,6 +258,7 @@ server.broadcast(topic, text) # Fan-out to topic subscribers
|
|
|
250
258
|
```python
|
|
251
259
|
server.subscribe_connection(conn_id, ["prices", "news"]) # Subscribe to topics
|
|
252
260
|
server.subscribe_connection(conn_id, ["chat"], {"status": "online"}) # Subscribe with presence data
|
|
261
|
+
server.subscribe_connection(conn_id, ["tasks"], queue_group="workers") # Subscribe with queue group (round-robin)
|
|
253
262
|
server.unsubscribe_connection(conn_id, ["news"]) # Unsubscribe from specific topics
|
|
254
263
|
server.unsubscribe_connection(conn_id, None) # Unsubscribe from all topics
|
|
255
264
|
server.get_topic_subscriber_count("prices") # Subscriber count for a topic
|
|
@@ -257,6 +266,26 @@ server.get_topic_subscriber_count("prices") # Subscrib
|
|
|
257
266
|
|
|
258
267
|
Subscriptions are cleaned up automatically on disconnect. In cluster mode, interest changes are propagated to peers via SUB/UNSUB frames.
|
|
259
268
|
|
|
269
|
+
**Queue groups**: connections in the same `queue_group` receive messages round-robin instead of fanout. Normal subscribers (no queue group) still receive all messages. Useful for distributing work across a pool of consumers.
|
|
270
|
+
|
|
271
|
+
### Topic ACL
|
|
272
|
+
|
|
273
|
+
Per-connection topic access control with glob pattern matching.
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
# Allow only "user:*" topics, deny everything else
|
|
277
|
+
server.set_topic_acl(conn_id, allow=["user:*"])
|
|
278
|
+
|
|
279
|
+
# Allow "data:*" but deny "data:internal:*"
|
|
280
|
+
server.set_topic_acl(conn_id, allow=["data:*"], deny=["data:internal:*"])
|
|
281
|
+
|
|
282
|
+
# Must be called before subscribe_connection
|
|
283
|
+
server.subscribe_connection(conn_id, ["data:prices"]) # allowed
|
|
284
|
+
server.subscribe_connection(conn_id, ["data:internal:audit"]) # denied
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Deny patterns take precedence over allow patterns. Supports `*` (any characters) and `?` (single character) wildcards. Applied at subscribe time.
|
|
288
|
+
|
|
260
289
|
### Presence Tracking
|
|
261
290
|
|
|
262
291
|
Requires `presence_enabled=True` in the constructor.
|
|
@@ -317,6 +346,7 @@ server.connect_cluster(
|
|
|
317
346
|
|
|
318
347
|
server.cluster_connected() # True if connected to at least one peer
|
|
319
348
|
server.cluster_peers_count() # Number of active peer connections
|
|
349
|
+
server.cluster_info() # List of connected peers (address, instance_id, connected)
|
|
320
350
|
```
|
|
321
351
|
|
|
322
352
|
Nodes form a full TCP mesh automatically. The cluster protocol uses a custom binary frame format with an 8-byte header, 12 message types, and capability negotiation during handshake. Features:
|
|
@@ -364,11 +394,14 @@ health = server.health_snapshot()
|
|
|
364
394
|
server.get_connection_count() # Lock-free AtomicUsize read
|
|
365
395
|
server.get_connections() # List all connection IDs (snapshot)
|
|
366
396
|
server.disconnect(conn_id) # Force-disconnect a connection
|
|
397
|
+
server.drain(close_code=4300, close_reason="shutting down", timeout=10) # Graceful drain
|
|
367
398
|
server.inbound_queue_depth() # Events waiting to be drained
|
|
368
399
|
server.inbound_dropped_count() # Events dropped due to full queue
|
|
369
400
|
server.get_cluster_dlq_entries() # Retrieve failed cluster messages from dead letter queue
|
|
370
401
|
```
|
|
371
402
|
|
|
403
|
+
`drain()` sends a WebSocket Close frame to all connected clients and rejects new connections. The drain wait runs as a separate task, so the command processor stays responsive. Use for zero-downtime deployments and rolling restarts.
|
|
404
|
+
|
|
372
405
|
---
|
|
373
406
|
|
|
374
407
|
## Security
|
|
@@ -382,7 +415,7 @@ Token delivery:
|
|
|
382
415
|
- **Backend clients**: `Authorization: Bearer <token>` header and/or `access_token` cookie
|
|
383
416
|
- **API clients**: `Authorization: Bearer <token>` header
|
|
384
417
|
|
|
385
|
-
Required claims: `sub` (user ID), `exp` (expiration)
|
|
418
|
+
Required claims: `sub` (user ID), `exp` (expiration). Recommended: `iat` (issued at). Optional: `iss`, `aud` (validated if configured).
|
|
386
419
|
|
|
387
420
|
### End-to-End Encryption
|
|
388
421
|
|
package/package.json
CHANGED