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.
Files changed (2) hide show
  1. package/README.md +60 -13
  2. 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.connect('localhost', 3000)
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
- `connect()` 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.
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 drops. It reclaims its previous session, so `client.id` stays the same across a reconnect and you don't need to re-establish application state.
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.connect('localhost', 3000, {
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.connect('localhost', 3000, {
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.connect('localhost', 3000, {
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.connect('localhost', 3000, {
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.connect(host, port, opts)`
223
+ ### `SleepySocketClient.open(host, port, opts)`
196
224
 
197
- This static method creates a client, connects it, and resolves once the server has acknowledged the connection. It's the only supported way to construct 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 options object
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 only event emitted is `'notification'`. Registering the same function twice is a no-op, since handlers are stored in a set.
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. It returns a promise, so it's worth awaiting before your process exits.
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 `connect()` when `queue` isn't a valid `Queue` value. This is thrown before any network call is made.
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
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "A dependency-free WebSocket client for sleepy-serv",
4
4
  "author": "Travis J True",
5
5
  "license": "MIT",
6
- "version": "0.10.0",
6
+ "version": "0.12.0",
7
7
  "exports": {
8
8
  ".": {
9
9
  "types": "./dist/index.d.ts",