@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.
Files changed (2) hide show
  1. package/README.md +63 -60
  2. 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 ws = new ReconnectingWebSocket({
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
- ws.on("open", () => console.log("Connected"));
28
- ws.on("message", (data) => console.log("Received:", data));
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
- ws.connect();
32
- ws.send({ type: "hello" }); // sends as JSON
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 | Default | Description |
42
- |---|---|---|---|
43
- | `url` | `string` | — | WebSocket server URL |
44
- | `backoff` | `BackoffOptions` | `{ initialDelayMs: 1000, maxDelayMs: 30000, multiplier: 2 }` | Reconnection backoff config |
45
- | `heartbeat` | `HeartbeatOptions` | `{ intervalMs: 30000, timeoutMs: 10000 }` | Ping/pong monitoring |
46
- | `auth` | `AuthOptions` | | Authentication (token fetched on each connect) |
47
- | `protocols` | `string[]` | — | WebSocket subprotocols |
48
- | `headers` | `Record<string, string>` | | Additional handshake 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
- ws.connect(); // Connect or reconnect (idempotent)
54
- ws.disconnect(); // Stop and prevent future reconnects
55
- ws.reconnect(); // Force reconnect with fresh auth token
56
- ws.send(message); // Send JSON-serializable message
57
- ws.stopReconnecting(); // Allow in-flight work but prevent reconnection
58
- ws.connected; // Read-only: true if socket is open
59
- ws.on(event, listener); // Register event listener (returns unsubscribe function)
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
- ws.on("open", () => { /* connected */ });
66
- ws.on("close", (code, reason) => { /* disconnected */ });
67
- ws.on("error", (error) => { /* connection or parse error */ });
68
- ws.on("message", (data) => { /* message received & parsed */ });
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 opts = { initialDelayMs: 1000, maxDelayMs: 30000, multiplier: 2 };
77
-
78
- getBackoffDelay(0, opts); // 1000 ms
79
- getBackoffDelay(1, opts); // 2000 ms
80
- getBackoffDelay(2, opts); // 4000 ms
81
- getBackoffDelay(10, opts); // capped at 30000 ms
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
- // 60s token → refresh at 30s (50% wins)
92
- calculateTokenRefreshTime(Date.now(), Date.now() + 60_000);
97
+ const issuedAt = Date.now();
98
+ const expiresAt = issuedAt + 10 * 60_000; // 10-minute token
93
99
 
94
- // 5min token → refresh at 3min (2min buffer wins)
95
- calculateTokenRefreshTime(Date.now(), Date.now() + 5 * 60_000);
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
- ```typescript
115
- const tracker = new RequestTracker();
116
-
117
- tracker.tryAccept(); // false if draining, otherwise increments active & returns true
118
- tracker.complete(); // decrements active; emits 'drained' when active reaches 0
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
- tracker.draining; // true if draining
122
- tracker.active; // number of in-flight requests
123
+ ### Events
123
124
 
124
- tracker.on("draining", (reason) => { /* draining started */ });
125
- tracker.on("drained", () => { /* all requests complete */ });
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
- const tracker = new RequestTracker();
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
- // Accept a request
138
- if (tracker.tryAccept()) {
139
- processRequest().finally(() => tracker.complete());
139
+ // In an HTTP request handler:
140
+ if (!tracker.tryAccept()) {
141
+ return res.status(503).send("Server shutting down");
140
142
  }
141
143
 
142
- // On shutdown signal
143
- tracker.startDraining("Server shutdown");
144
+ // Do work...
145
+
146
+ tracker.complete();
144
147
  ```
145
148
 
146
149
  ## Public API Reference
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardlydifficult/websocket",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "files": [