@hardlydifficult/websocket 1.0.7 → 1.0.8
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 +63 -60
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,23 +13,18 @@ npm install @hardlydifficult/websocket
|
|
|
13
13
|
```typescript
|
|
14
14
|
import { ReconnectingWebSocket } from "@hardlydifficult/websocket";
|
|
15
15
|
|
|
16
|
-
const
|
|
16
|
+
const client = new ReconnectingWebSocket({
|
|
17
17
|
url: "wss://api.example.com/ws",
|
|
18
18
|
auth: {
|
|
19
19
|
getToken: () => "Bearer token",
|
|
20
20
|
},
|
|
21
|
-
heartbeat: {
|
|
22
|
-
intervalMs: 30000, // ping every 30s
|
|
23
|
-
timeoutMs: 10000, // terminate if no pong in 10s
|
|
24
|
-
},
|
|
25
21
|
});
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
ws.on("close", (code, reason) => console.log("Closed:", code, reason));
|
|
23
|
+
client.on("open", () => console.log("Connected!"));
|
|
24
|
+
client.on("message", (data) => console.log("Received:", data));
|
|
30
25
|
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
client.connect();
|
|
27
|
+
client.send({ type: "ping" });
|
|
33
28
|
```
|
|
34
29
|
|
|
35
30
|
## Auto-Reconnecting WebSocket
|
|
@@ -38,49 +33,60 @@ ws.send({ type: "hello" }); // sends as JSON
|
|
|
38
33
|
|
|
39
34
|
### Constructor Options
|
|
40
35
|
|
|
41
|
-
| Option | Type |
|
|
42
|
-
|
|
43
|
-
| `url` | `string` |
|
|
44
|
-
| `backoff
|
|
45
|
-
| `heartbeat
|
|
46
|
-
| `auth
|
|
47
|
-
| `protocols
|
|
48
|
-
| `headers
|
|
36
|
+
| Option | Type | Description |
|
|
37
|
+
|--------|------|-------------|
|
|
38
|
+
| `url` | `string` | WebSocket server URL |
|
|
39
|
+
| `backoff`? | `BackoffOptions` | Exponential backoff configuration (defaults: `initialDelayMs=1000`, `maxDelayMs=30000`, `multiplier=2`) |
|
|
40
|
+
| `heartbeat`? | `HeartbeatOptions` | Heartbeat ping configuration (defaults: `intervalMs=30000`, `timeoutMs=10000`) |
|
|
41
|
+
| `auth`? | `AuthOptions` | Auth configuration with a `getToken` function |
|
|
42
|
+
| `protocols`? | `string[]` | WebSocket subprotocols |
|
|
43
|
+
| `headers`? | `Record<string, string>` | Additional headers for the WebSocket handshake |
|
|
49
44
|
|
|
50
45
|
### Core Methods
|
|
51
46
|
|
|
52
47
|
```typescript
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
48
|
+
client.connect(); // Connect or reconnect (idempotent)
|
|
49
|
+
client.disconnect(); // Stop and prevent future reconnects
|
|
50
|
+
client.reconnect(); // Force reconnect with fresh auth token
|
|
51
|
+
client.send(message); // Send JSON-serializable message
|
|
52
|
+
client.stopReconnecting(); // Allow in-flight work but prevent reconnection
|
|
53
|
+
client.connected; // Read-only: true if socket is open
|
|
54
|
+
client.on(event, listener); // Register event listener (returns unsubscribe function)
|
|
60
55
|
```
|
|
61
56
|
|
|
57
|
+
Each `on()` call returns an unsubscribe function.
|
|
58
|
+
|
|
62
59
|
### Event Types
|
|
63
60
|
|
|
64
61
|
```typescript
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
client.on("open", () => { /* connected */ });
|
|
63
|
+
client.on("close", (code, reason) => { /* disconnected */ });
|
|
64
|
+
client.on("error", (error) => { /* connection or parse error */ });
|
|
65
|
+
client.on("message", (data) => { /* message received & parsed */ });
|
|
69
66
|
```
|
|
70
67
|
|
|
71
68
|
### Backoff Behavior
|
|
72
69
|
|
|
70
|
+
Use `getBackoffDelay` to compute delays for custom reconnection strategies:
|
|
71
|
+
|
|
73
72
|
```typescript
|
|
74
73
|
import { getBackoffDelay } from "@hardlydifficult/websocket";
|
|
75
74
|
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
75
|
+
const delay = getBackoffDelay(3, {
|
|
76
|
+
initialDelayMs: 1000,
|
|
77
|
+
maxDelayMs: 30000,
|
|
78
|
+
multiplier: 2,
|
|
79
|
+
});
|
|
80
|
+
// delay = 8000 (1000 * 2^3)
|
|
82
81
|
```
|
|
83
82
|
|
|
83
|
+
| Attempt | Delay |
|
|
84
|
+
|---------|-------|
|
|
85
|
+
| 0 | 1000 ms |
|
|
86
|
+
| 1 | 2000 ms |
|
|
87
|
+
| 2 | 4000 ms |
|
|
88
|
+
| 10 | capped at 30000 ms |
|
|
89
|
+
|
|
84
90
|
## Proactive Token Refresh
|
|
85
91
|
|
|
86
92
|
`calculateTokenRefreshTime` schedules token refresh before expiry, using either 50% lifetime (short tokens) or a 2-minute buffer (longer tokens):
|
|
@@ -88,20 +94,17 @@ getBackoffDelay(10, opts); // capped at 30000 ms
|
|
|
88
94
|
```typescript
|
|
89
95
|
import { calculateTokenRefreshTime } from "@hardlydifficult/websocket";
|
|
90
96
|
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
const issuedAt = Date.now();
|
|
98
|
+
const expiresAt = issuedAt + 10 * 60_000; // 10-minute token
|
|
93
99
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
// Reconnect to refresh token
|
|
98
|
-
ws.reconnect(); // fetches fresh token from auth
|
|
100
|
+
const refreshAt = calculateTokenRefreshTime(issuedAt, expiresAt);
|
|
101
|
+
// refreshAt = expiresAt - 2 * 60_000 (2-min buffer)
|
|
99
102
|
```
|
|
100
103
|
|
|
101
104
|
### Token Refresh Strategy
|
|
102
105
|
|
|
103
106
|
| Token lifetime | Refresh strategy | Example (60s token) | Example (5min token) |
|
|
104
|
-
|
|
107
|
+
|----------------|------------------|---------------------|----------------------|
|
|
105
108
|
| Short (≤4min) | 50% lifetime | 30s after issue | N/A |
|
|
106
109
|
| Long (>4min) | 2min before expiry | N/A | 3min after issue |
|
|
107
110
|
|
|
@@ -111,36 +114,36 @@ ws.reconnect(); // fetches fresh token from auth
|
|
|
111
114
|
|
|
112
115
|
### Methods
|
|
113
116
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
tracker.startDraining("reason"); // enter drain mode; rejects new requests
|
|
117
|
+
- `tryAccept(): boolean` — Accept a new request; returns `false` if draining
|
|
118
|
+
- `complete(): void` — Mark request as complete
|
|
119
|
+
- `startDraining(reason: string): void` — Enter draining mode; idempotent
|
|
120
|
+
- `draining: boolean` — Read-only draining state
|
|
121
|
+
- `active: number` — Active request count
|
|
120
122
|
|
|
121
|
-
|
|
122
|
-
tracker.active; // number of in-flight requests
|
|
123
|
+
### Events
|
|
123
124
|
|
|
124
|
-
|
|
125
|
-
tracker.on("
|
|
125
|
+
```typescript
|
|
126
|
+
tracker.on("draining", (reason) => console.log("Draining:", reason));
|
|
127
|
+
tracker.on("drained", () => console.log("All requests complete"));
|
|
126
128
|
```
|
|
127
129
|
|
|
128
130
|
### Example Usage
|
|
129
131
|
|
|
130
132
|
```typescript
|
|
131
|
-
|
|
132
|
-
let activeRequests = 0;
|
|
133
|
+
import { RequestTracker } from "@hardlydifficult/websocket";
|
|
133
134
|
|
|
135
|
+
const tracker = new RequestTracker();
|
|
134
136
|
tracker.on("draining", (reason) => console.log("Draining:", reason));
|
|
135
137
|
tracker.on("drained", () => console.log("All requests complete"));
|
|
136
138
|
|
|
137
|
-
//
|
|
138
|
-
if (tracker.tryAccept()) {
|
|
139
|
-
|
|
139
|
+
// In an HTTP request handler:
|
|
140
|
+
if (!tracker.tryAccept()) {
|
|
141
|
+
return res.status(503).send("Server shutting down");
|
|
140
142
|
}
|
|
141
143
|
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
+
// Do work...
|
|
145
|
+
|
|
146
|
+
tracker.complete();
|
|
144
147
|
```
|
|
145
148
|
|
|
146
149
|
## Public API Reference
|