redweb-client 0.1.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/LICENSE +7 -0
- package/README.md +189 -0
- package/dist/index.cjs +522 -0
- package/dist/index.d.cts +149 -0
- package/dist/index.d.ts +149 -0
- package/dist/index.js +489 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# redweb-client
|
|
2
|
+
|
|
3
|
+
Browser-first WebSocket client for [Redweb](https://redweb.magnisolution.com/). It adds connection lifecycle, typed message subscriptions, request correlation, bounded queuing, opt-in reconnection, and exact Redweb 0.9 protocol envelopes without adding runtime dependencies.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install redweb-client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The package ships ESM, CommonJS, and TypeScript declarations. It targets modern browsers with `WebSocket`, `URL`, `AbortController`, and `Blob`; Node 18+ is supported when a WebSocket implementation is supplied through `webSocketFactory`.
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
Redweb server:
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
const { BaseHandler, HttpServer, SocketRoute, SocketServer } = require('redweb')
|
|
19
|
+
|
|
20
|
+
class ChatHandler extends BaseHandler {
|
|
21
|
+
constructor() { super('chat') }
|
|
22
|
+
|
|
23
|
+
onMessage(socket, message) {
|
|
24
|
+
socket.broadcast({ type: 'chat:message', text: message.text })
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
class ChatRoute extends SocketRoute {
|
|
29
|
+
constructor() {
|
|
30
|
+
super({
|
|
31
|
+
path: '/chat',
|
|
32
|
+
handlers: [ChatHandler],
|
|
33
|
+
allowDuplicateConnections: true,
|
|
34
|
+
websocketOptions: { maxPayload: 64 * 1024 },
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const http = new HttpServer({ listen: false, publicPaths: ['./public'] })
|
|
40
|
+
const sockets = new SocketServer({ server: http.server, routes: [ChatRoute] })
|
|
41
|
+
http.server.listen(3030)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Browser:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { RedwebClient } from 'redweb-client'
|
|
48
|
+
|
|
49
|
+
const client = new RedwebClient('/chat', {
|
|
50
|
+
baseUrl: window.location.href,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
client.on('chat:message', message => {
|
|
54
|
+
console.log(message.text)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
client.onError(error => console.error(error))
|
|
58
|
+
|
|
59
|
+
await client.connect()
|
|
60
|
+
client.send('chat', { text: 'Hello' })
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Relative paths use `window.location` automatically in browsers. `http:` and `https:` bases are converted to `ws:` and `wss:`.
|
|
64
|
+
|
|
65
|
+
## Versioned Redweb protocol
|
|
66
|
+
|
|
67
|
+
Enable the same finite version on the Redweb route and client:
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
// Server route
|
|
71
|
+
super({
|
|
72
|
+
path: '/match',
|
|
73
|
+
handlers: [MoveHandler],
|
|
74
|
+
protocol: { versions: ['2', '1'] },
|
|
75
|
+
})
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
// Browser client
|
|
80
|
+
const client = new RedwebClient('wss://game.example/match', { version: '2' })
|
|
81
|
+
await client.connect()
|
|
82
|
+
|
|
83
|
+
const response = await client.request(
|
|
84
|
+
'move',
|
|
85
|
+
{ x: 4, y: 2 },
|
|
86
|
+
{ requestId: crypto.randomUUID(), sequence: 17 },
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The client adds `?redwebVersion=2` when needed. If the URL already contains `redwebVersion`, the client infers that version when `version` is omitted and rejects a conflicting configured value. Browsers cannot provide Redweb's alternative `x-redweb-version` handshake header or inspect the `426` response headers; unsupported versions therefore appear as an ordinary WebSocket connection failure.
|
|
91
|
+
|
|
92
|
+
Protocol sends use the exact shape:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
{ v, type, payload, requestId?, sequence? }
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Framework error responses reject a correlated `request()` with `RedwebProtocolError`, while still being delivered to `on('error', ...)` and `onAny(...)` subscribers.
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import { ERROR_CODES, RedwebProtocolError } from 'redweb-client'
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
await client.request('move', { x: 4, y: 2 })
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (error instanceof RedwebProtocolError && error.code === ERROR_CODES.RATE_LIMITED) {
|
|
107
|
+
// Back off; do not automatically replay application commands.
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Legacy routes
|
|
113
|
+
|
|
114
|
+
Without `version`, object payload properties stay at the top level because legacy Redweb handlers commonly read `message.text`, `message.action`, and similar fields:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
client.send('chat', { text: 'Hello' })
|
|
118
|
+
// { type: 'chat', text: 'Hello' }
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Scalar and array payloads use `{ type, payload }`. Legacy framework failures such as `{ error: 'Handler failed' }` are observable with `on('error', ...)`; they generally cannot be correlated because Redweb's legacy error shape does not carry `requestId`.
|
|
122
|
+
|
|
123
|
+
## Typed event maps
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
interface Incoming {
|
|
127
|
+
'player:moved': { playerId: string; x: number; y: number }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
interface Outgoing {
|
|
131
|
+
move: { x: number; y: number }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const client = new RedwebClient<Incoming, Outgoing>('/match')
|
|
135
|
+
client.send('move', { x: 4, y: 2 })
|
|
136
|
+
client.on('player:moved', message => console.log(message))
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
TypeScript improves application ergonomics; it does not validate arbitrary application payloads at runtime. Validate untrusted payloads in your application.
|
|
140
|
+
|
|
141
|
+
## Lifecycle and sending
|
|
142
|
+
|
|
143
|
+
- `connect()` is idempotent while connecting or open and resolves after the socket opens.
|
|
144
|
+
- `close(code?, reason?)` suppresses reconnection, rejects pending requests, and discards unsent queued data.
|
|
145
|
+
- `dispose()` closes once, clears subscriptions and queued messages, and permanently prevents reuse.
|
|
146
|
+
- `send(type, payload, metadata?)` builds a legacy or protocol message.
|
|
147
|
+
- `sendRaw(data)` sends text or binary data without encoding it.
|
|
148
|
+
- Sends while disconnected throw by default. Set a finite `maxQueueSize` to queue a bounded number of messages until the next open. A queued request is removed if it times out, is aborted, or is rejected by `close()`; a command cannot execute later after its caller has been told it was cancelled.
|
|
149
|
+
- `request()` correlates a response by `requestId`; `waitFor()` waits for a message type. Both support deadlines and `AbortSignal`.
|
|
150
|
+
|
|
151
|
+
Queued messages are never replayed after they have been sent. Redweb does not provide exactly-once delivery, durable state, or automatic application acknowledgements; `requestId` is correlation and `sequence` is application ordering only.
|
|
152
|
+
|
|
153
|
+
## Reconnection
|
|
154
|
+
|
|
155
|
+
Reconnection is disabled by default:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
const client = new RedwebClient('/match', {
|
|
159
|
+
reconnect: {
|
|
160
|
+
enabled: true,
|
|
161
|
+
maxAttempts: 8,
|
|
162
|
+
initialDelayMs: 250,
|
|
163
|
+
maxDelayMs: 10_000,
|
|
164
|
+
factor: 2,
|
|
165
|
+
jitter: 0.2,
|
|
166
|
+
shouldReconnect: event => event.code !== 1000 && event.code !== 1008,
|
|
167
|
+
},
|
|
168
|
+
})
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The default retry policy avoids normal closure (`1000`) and policy violations (`1008`). Manual `close()` and `dispose()` always cancel retries. Customize `shouldReconnect` for deployment shutdown (`1001`), handler failure (`1011`), capacity/backpressure (`1013`), or network-loss behavior. Reconnection never automatically resends application messages or resumes Redweb sessions; session IDs and resume commands belong to the application protocol.
|
|
172
|
+
|
|
173
|
+
## Binary messages and heartbeat
|
|
174
|
+
|
|
175
|
+
The client requests `arraybuffer` delivery and passes incoming `ArrayBuffer`, typed-array, or `Blob` values to `onBinary()`. Redweb's protocol binary codec is application-defined, so this package does not guess how to decode it. Use `sendRaw()` and your codec around `onBinary()`.
|
|
176
|
+
|
|
177
|
+
Browser ping/pong handling is automatic and inaccessible to JavaScript. Redweb's route-level heartbeat operates at the WebSocket protocol layer; no application heartbeat is required here.
|
|
178
|
+
|
|
179
|
+
## Authentication and security
|
|
180
|
+
|
|
181
|
+
Browser WebSockets cannot set arbitrary request headers. Use secure, same-site cookies or a short-lived query credential accepted by your Redweb admission hook. Avoid persistent credentials and never put long-lived tokens in URLs, logs, or reconnect configuration. Origin validation, authentication, placement, capacity, and authorization remain server responsibilities.
|
|
182
|
+
|
|
183
|
+
## Development and verification
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
npm run check
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The clean-worktree gate builds first, resolves the public declarations through the package export map, then runs unit tests plus mock-free integration tests against a real local Redweb HTTP/WebSocket server. Coverage is enforced at 100% for statements, branches, functions, and lines.
|