sleepy-socket 0.10.0 → 0.12.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 +60 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ Here's a minimalist example on how to connect and make a request:
|
|
|
21
21
|
```js
|
|
22
22
|
import SleepySocketClient from 'sleepy-socket'
|
|
23
23
|
|
|
24
|
-
const client = await SleepySocketClient.
|
|
24
|
+
const client = await SleepySocketClient.open('localhost', 3000)
|
|
25
25
|
const res = await client.get('/users')
|
|
26
26
|
|
|
27
27
|
console.log(res.status) // 200
|
|
@@ -30,7 +30,7 @@ console.log(res.body) // the parsed response body
|
|
|
30
30
|
await client.close()
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
`
|
|
33
|
+
`open()` is the only supported way to create a client. It's `async` because it doesn't resolve until the connection is fully established: it requests a ticket over HTTP, opens the WebSocket, and waits for the server's `welcome` message. Once it resolves, the client is ready to make requests.
|
|
34
34
|
|
|
35
35
|
### Making Requests
|
|
36
36
|
|
|
@@ -121,14 +121,42 @@ client.on('notification', message => {
|
|
|
121
121
|
|
|
122
122
|
If one of your handlers throws, the error is caught and logged, and the remaining handlers still receive the message.
|
|
123
123
|
|
|
124
|
+
### Close Event
|
|
125
|
+
|
|
126
|
+
The client emits a `close` event whenever the socket closes, regardless of the reason:
|
|
127
|
+
|
|
128
|
+
```js
|
|
129
|
+
client.on('close', payload => {
|
|
130
|
+
console.log(payload.code) // the WebSocket close code (e.g. 1000)
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
This fires on client-initiated closes (`client.close()`), server-initiated closes (`ws.drop()`), and unexpected drops (network loss, reaping). It is intended for centralized cleanup, such as removing a player from a lobby or updating UI state. The reconnect decision happens after the event fires.
|
|
135
|
+
|
|
136
|
+
### Connection Context
|
|
137
|
+
|
|
138
|
+
You can attach arbitrary app data to the initial connection using the `ctx` option:
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
const client = await SleepySocketClient.open('localhost', 3000, {
|
|
142
|
+
ctx: { gameId: 'abc', playerId: 'p1' },
|
|
143
|
+
})
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The server stores this in `ws.data.app` and preserves it through reconnects. On a reclaim (`PUT /ws/:clientId`), the client sends no body; the server is the source of truth for the connection context.
|
|
147
|
+
|
|
124
148
|
### Reconnection
|
|
125
149
|
|
|
126
|
-
The client reconnects automatically when the socket
|
|
150
|
+
The client reconnects automatically when the socket closes with a non-1000 code. A close code of `CloseCode.Ok` (1000) is treated as intentional and terminal, so `client.close()` and a server-side `ws.drop(clientId)` (which defaults to code 1000) do not trigger reconnect. Non-1000 codes such as network drops (`CloseCode.Abnormal`, 1006), server reaping (`CloseCode.Reaped`, 4999), and app-level kicks with a custom code (e.g. 4000) do trigger reconnect.
|
|
151
|
+
|
|
152
|
+
The client reclaims its previous session on reconnect, so `client.id` stays the same and you don't need to re-establish application state. If reclaim fails (expired session or invalid token), the client falls back to a fresh identity via `POST /ws`.
|
|
153
|
+
|
|
154
|
+
If the server rejects a reconnect handshake with an error response (surfaced as a `HandshakeError`), reconnect stops. A server-level rejection (such as "game is full") will not resolve on its own, so retrying would be wasteful.
|
|
127
155
|
|
|
128
156
|
You can tune the backoff:
|
|
129
157
|
|
|
130
158
|
```js
|
|
131
|
-
const client = await SleepySocketClient.
|
|
159
|
+
const client = await SleepySocketClient.open('localhost', 3000, {
|
|
132
160
|
reconnect: {
|
|
133
161
|
minDelay: 1_000,
|
|
134
162
|
maxDelay: 10_000,
|
|
@@ -140,7 +168,7 @@ const client = await SleepySocketClient.connect('localhost', 3000, {
|
|
|
140
168
|
Set `reconnect` to `false` to turn it off entirely:
|
|
141
169
|
|
|
142
170
|
```js
|
|
143
|
-
const client = await SleepySocketClient.
|
|
171
|
+
const client = await SleepySocketClient.open('localhost', 3000, {
|
|
144
172
|
reconnect: false,
|
|
145
173
|
})
|
|
146
174
|
```
|
|
@@ -156,7 +184,7 @@ For example, if you fire three requests that take 300ms, 100ms, and 200ms:
|
|
|
156
184
|
```js
|
|
157
185
|
import SleepySocketClient, { Queue } from 'sleepy-socket'
|
|
158
186
|
|
|
159
|
-
const client = await SleepySocketClient.
|
|
187
|
+
const client = await SleepySocketClient.open('localhost', 3000, {
|
|
160
188
|
queue: Queue.Fifo,
|
|
161
189
|
})
|
|
162
190
|
|
|
@@ -181,7 +209,7 @@ The three queue types resolve those promises differently:
|
|
|
181
209
|
If the server was created with a `mountPath`, give the client the same value:
|
|
182
210
|
|
|
183
211
|
```js
|
|
184
|
-
const client = await SleepySocketClient.
|
|
212
|
+
const client = await SleepySocketClient.open('localhost', 3000, {
|
|
185
213
|
mountPath: '/api/v2',
|
|
186
214
|
})
|
|
187
215
|
|
|
@@ -192,14 +220,14 @@ The routes you pass to request methods stay mount-relative. The client joins the
|
|
|
192
220
|
|
|
193
221
|
## API
|
|
194
222
|
|
|
195
|
-
### `SleepySocketClient.
|
|
223
|
+
### `SleepySocketClient.open(host, port, opts)`
|
|
196
224
|
|
|
197
|
-
This static method creates a client,
|
|
225
|
+
This static method creates a client, opens the connection, and resolves once the server has acknowledged it. It's the only supported way to construct a client.
|
|
198
226
|
|
|
199
227
|
The parameters are:
|
|
200
228
|
- `host`: the hostname, without a scheme, such as `'localhost'`
|
|
201
229
|
- `port`: the port number
|
|
202
|
-
- `opts`: an optional
|
|
230
|
+
- `opts`: an optional `OpenOptions` object
|
|
203
231
|
|
|
204
232
|
The `opts` object can contain these optional properties:
|
|
205
233
|
- `queue`: how responses are handed back, one of `Queue.None`, `Queue.Fifo`, or `Queue.Lifo`. Defaults to `Queue.None`. An unrecognized value throws a `RangeError`.
|
|
@@ -208,6 +236,7 @@ The `opts` object can contain these optional properties:
|
|
|
208
236
|
- `serverTimeout`: how long the client tolerates silence from the server, in milliseconds, before it considers the connection dead and closes it. Defaults to `120_000`.
|
|
209
237
|
- `mountPath`: the server's mount path prefix. Defaults to `''`.
|
|
210
238
|
- `reconnect`: an options object for reconnection behavior, or `false` to disable it
|
|
239
|
+
- `ctx`: arbitrary app data to attach to the connection. Sent in the `POST /ws` body on the initial connect. Not sent on reclaim.
|
|
211
240
|
|
|
212
241
|
The `reconnect` object can contain these optional properties:
|
|
213
242
|
- `minDelay`: the starting backoff delay in milliseconds. Defaults to `500`.
|
|
@@ -223,7 +252,7 @@ They throw synchronously if the client isn't connected, and their promises rejec
|
|
|
223
252
|
|
|
224
253
|
### `on(event, handler)`
|
|
225
254
|
|
|
226
|
-
Registers a handler for an event. The
|
|
255
|
+
Registers a handler for an event. The client emits two events: `'notification'` for server-pushed messages, and `'close'` when the socket closes (for any reason). Registering the same function twice is a no-op, since handlers are stored in a set.
|
|
227
256
|
|
|
228
257
|
### `off(event, handler)`
|
|
229
258
|
|
|
@@ -231,7 +260,7 @@ Removes a previously registered handler. It's safe to call with a handler that w
|
|
|
231
260
|
|
|
232
261
|
### `close()`
|
|
233
262
|
|
|
234
|
-
Closes the connection and rejects any in-flight requests.
|
|
263
|
+
Closes the connection and rejects any in-flight requests. The returned promise resolves only after the socket's `close` event fires, so the `close` event handler runs before `await client.close()` returns.
|
|
235
264
|
|
|
236
265
|
Note that closing is permanent. There's no reopen, and calling `close()` a second time throws. If you're calling it in a `finally` block, guard it with `isConnected`:
|
|
237
266
|
|
|
@@ -250,6 +279,8 @@ try {
|
|
|
250
279
|
All of these are read-only:
|
|
251
280
|
- `id`: the server-assigned client id, which survives reconnects
|
|
252
281
|
- `isConnected`: whether the client is currently connected and ready for requests
|
|
282
|
+
- `isConnecting`: whether the client is in the process of establishing a connection
|
|
283
|
+
- `isReconnecting`: whether a reconnect timer is pending and the client is not connected
|
|
253
284
|
- `socket`: the underlying `WebSocket`, or `null` while disconnected
|
|
254
285
|
- `connectionData`: whatever payload the server attached when the connection was established. This is where application data such as an auth token shows up.
|
|
255
286
|
- `token`: the reclaim token used internally to restore the session after a drop. This is not an application auth token; that would be on `connectionData`.
|
|
@@ -268,10 +299,26 @@ Contains the valid values for the `queue` option: `Queue.None`, `Queue.Fifo`, an
|
|
|
268
299
|
|
|
269
300
|
Contains the message type names used on the wire: `MessageType.Welcome`, `MessageType.Heartbeat`, `MessageType.Request`, `MessageType.Response`, and `MessageType.Notification`. A response message's `type` is always `MessageType.Response`, and a notification's is always `MessageType.Notification`.
|
|
270
301
|
|
|
302
|
+
### `CloseCode`
|
|
303
|
+
|
|
304
|
+
Contains the WebSocket close codes used by the protocol: `CloseCode.Ok` (1000), `CloseCode.Abnormal` (1006), and `CloseCode.Reaped` (4999). Protocol-level codes count down from 4999; app codes start at 4000.
|
|
305
|
+
|
|
306
|
+
### `StatusCode`
|
|
307
|
+
|
|
308
|
+
A const object covering the full range of HTTP status codes (1xx through 5xx), so you can reference statuses by name instead of by number.
|
|
309
|
+
|
|
310
|
+
### `HandshakeError`
|
|
311
|
+
|
|
312
|
+
A class thrown when the server rejects a handshake with a non-ok HTTP response and a JSON body. It has two properties:
|
|
313
|
+
- `status`: the HTTP status code (e.g. 409)
|
|
314
|
+
- `body`: the parsed JSON body from the server
|
|
315
|
+
|
|
316
|
+
During reconnect, a `HandshakeError` is treated as terminal. The reconnect loop stops rather than retrying.
|
|
317
|
+
|
|
271
318
|
## Errors
|
|
272
319
|
|
|
273
320
|
Most failures surface as thrown errors or rejected promises:
|
|
274
|
-
- `Invalid queue type: <value>`: a `RangeError` thrown by `
|
|
321
|
+
- `Invalid queue type: <value>`: a `RangeError` thrown by `open()` when `queue` isn't a valid `Queue` value. This is thrown before any network call is made.
|
|
275
322
|
- `Connection failed.`: the connection couldn't be established
|
|
276
323
|
- `Connection timed out.`: the connection wasn't established within `timeout` milliseconds
|
|
277
324
|
- `opts.headers must be a Headers instance`: a `TypeError` thrown when a request's `headers` option isn't a `Headers` object
|