@wcstack/websocket 1.8.1
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.ja.md +546 -0
- package/README.md +546 -0
- package/dist/auto.js +3 -0
- package/dist/auto.min.js +3 -0
- package/dist/index.d.ts +141 -0
- package/dist/index.esm.js +440 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.esm.min.js +2 -0
- package/dist/index.esm.min.js.map +1 -0
- package/package.json +70 -0
package/README.md
ADDED
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
# @wcstack/websocket
|
|
2
|
+
|
|
3
|
+
`@wcstack/websocket` is a headless WebSocket component for the wcstack ecosystem.
|
|
4
|
+
|
|
5
|
+
It is not a visual UI widget.
|
|
6
|
+
It is an **I/O node** that connects WebSocket communication to reactive state.
|
|
7
|
+
|
|
8
|
+
With `@wcstack/state`, `<wcs-ws>` can be bound directly through path contracts:
|
|
9
|
+
|
|
10
|
+
- **input / command surface**: `url`, `trigger`, `send`
|
|
11
|
+
- **output state surface**: `message`, `connected`, `loading`, `error`, `readyState`
|
|
12
|
+
|
|
13
|
+
This means real-time communication can be expressed declaratively in HTML, without writing `new WebSocket()`, `onmessage`, or connection glue code in your UI layer.
|
|
14
|
+
|
|
15
|
+
`@wcstack/websocket` follows the [HAWC](https://github.com/wc-bindable-protocol/wc-bindable-protocol/blob/main/docs/articles/HAWC.md) architecture:
|
|
16
|
+
|
|
17
|
+
- **Core** (`WebSocketCore`) handles connection, messaging, reconnection, and async state
|
|
18
|
+
- **Shell** (`<wcs-ws>`) connects that state to the DOM
|
|
19
|
+
- frameworks and binding systems consume it through [wc-bindable-protocol](https://github.com/wc-bindable-protocol/wc-bindable-protocol)
|
|
20
|
+
|
|
21
|
+
## Why this exists
|
|
22
|
+
|
|
23
|
+
Real-time features typically require imperative WebSocket management: connection lifecycle, reconnection logic, message parsing, error handling, and cleanup on disconnect.
|
|
24
|
+
|
|
25
|
+
`@wcstack/websocket` moves that logic into a reusable component and exposes the result as bindable state.
|
|
26
|
+
|
|
27
|
+
With `@wcstack/state`, the flow becomes:
|
|
28
|
+
|
|
29
|
+
1. state determines the `url` (or `trigger` fires)
|
|
30
|
+
2. `<wcs-ws>` opens the connection
|
|
31
|
+
3. incoming messages arrive as `message`, connection status as `connected`, `loading`, `error`
|
|
32
|
+
4. UI binds to those paths with `data-wcs`
|
|
33
|
+
|
|
34
|
+
This turns real-time communication into **state transitions**, not imperative event wiring.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install @wcstack/websocket
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
### 1. Reactive WebSocket from state
|
|
45
|
+
|
|
46
|
+
When `<wcs-ws>` is connected to the DOM with a `url`, it automatically opens a WebSocket connection. JSON messages are automatically parsed.
|
|
47
|
+
|
|
48
|
+
```html
|
|
49
|
+
<script type="module" src="https://esm.run/@wcstack/state/auto"></script>
|
|
50
|
+
<script type="module" src="https://esm.run/@wcstack/websocket/auto"></script>
|
|
51
|
+
|
|
52
|
+
<wcs-state>
|
|
53
|
+
<script type="application/json">
|
|
54
|
+
{
|
|
55
|
+
"lastMessage": null,
|
|
56
|
+
"isConnected": false,
|
|
57
|
+
"isLoading": false
|
|
58
|
+
}
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<wcs-ws
|
|
62
|
+
url="wss://example.com/ws"
|
|
63
|
+
data-wcs="message: lastMessage; connected: isConnected; loading: isLoading">
|
|
64
|
+
</wcs-ws>
|
|
65
|
+
|
|
66
|
+
<p data-wcs="textContent: isConnected|then('Connected','Disconnected')"></p>
|
|
67
|
+
<pre data-wcs="textContent: lastMessage|json"></pre>
|
|
68
|
+
</wcs-state>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
This is the default mode:
|
|
72
|
+
|
|
73
|
+
- set `url`
|
|
74
|
+
- receive `message`
|
|
75
|
+
- optionally bind `connected`, `loading`, `error`, and `readyState`
|
|
76
|
+
|
|
77
|
+
### 2. Sending messages from state
|
|
78
|
+
|
|
79
|
+
Use the `send` property to push data to the server. Setting `send` transmits the value immediately; objects are automatically JSON-stringified.
|
|
80
|
+
|
|
81
|
+
```html
|
|
82
|
+
<wcs-state>
|
|
83
|
+
<script type="module">
|
|
84
|
+
export default {
|
|
85
|
+
chatInput: "",
|
|
86
|
+
lastMessage: null,
|
|
87
|
+
outgoing: null,
|
|
88
|
+
|
|
89
|
+
sendChat() {
|
|
90
|
+
this.outgoing = { type: "chat", content: this.chatInput };
|
|
91
|
+
this.chatInput = "";
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
</script>
|
|
95
|
+
|
|
96
|
+
<wcs-ws
|
|
97
|
+
url="wss://example.com/ws"
|
|
98
|
+
data-wcs="message: lastMessage; send: outgoing">
|
|
99
|
+
</wcs-ws>
|
|
100
|
+
|
|
101
|
+
<input data-wcs="value: chatInput" placeholder="Type a message">
|
|
102
|
+
<button data-wcs="onclick: sendChat">Send</button>
|
|
103
|
+
|
|
104
|
+
<pre data-wcs="textContent: lastMessage|json"></pre>
|
|
105
|
+
</wcs-state>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 3. Manual connection with `trigger`
|
|
109
|
+
|
|
110
|
+
Use `manual` when you want to control when the connection opens.
|
|
111
|
+
|
|
112
|
+
```html
|
|
113
|
+
<wcs-state>
|
|
114
|
+
<script type="module">
|
|
115
|
+
export default {
|
|
116
|
+
shouldConnect: false,
|
|
117
|
+
lastMessage: null,
|
|
118
|
+
isConnected: false,
|
|
119
|
+
|
|
120
|
+
openConnection() {
|
|
121
|
+
this.shouldConnect = true;
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
</script>
|
|
125
|
+
|
|
126
|
+
<wcs-ws
|
|
127
|
+
url="wss://example.com/ws"
|
|
128
|
+
manual
|
|
129
|
+
data-wcs="trigger: shouldConnect; message: lastMessage; connected: isConnected">
|
|
130
|
+
</wcs-ws>
|
|
131
|
+
|
|
132
|
+
<button data-wcs="onclick: openConnection">Connect</button>
|
|
133
|
+
<p data-wcs="textContent: isConnected|then('Connected','Disconnected')"></p>
|
|
134
|
+
</wcs-state>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`trigger` is a **one-way command surface**:
|
|
138
|
+
|
|
139
|
+
- writing `true` opens the connection
|
|
140
|
+
- it resets itself to `false` after the connection is initiated
|
|
141
|
+
- the reset emits `wcs-ws:trigger-changed`
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
external write: false → true No event (triggers connect)
|
|
145
|
+
auto-reset: true → false Dispatches wcs-ws:trigger-changed
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 4. Auto-reconnect
|
|
149
|
+
|
|
150
|
+
```html
|
|
151
|
+
<wcs-ws
|
|
152
|
+
url="wss://example.com/ws"
|
|
153
|
+
auto-reconnect
|
|
154
|
+
reconnect-interval="5000"
|
|
155
|
+
max-reconnects="10"
|
|
156
|
+
data-wcs="message: lastMessage; connected: isConnected; error: wsError">
|
|
157
|
+
</wcs-ws>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
When the connection drops unexpectedly (close code other than 1000), `<wcs-ws>` automatically reconnects:
|
|
161
|
+
|
|
162
|
+
- waits `reconnect-interval` ms (default: 3000)
|
|
163
|
+
- retries up to `max-reconnects` times (default: Infinity)
|
|
164
|
+
- resets the retry count on successful reconnection
|
|
165
|
+
|
|
166
|
+
## State Surface vs Command Surface
|
|
167
|
+
|
|
168
|
+
`<wcs-ws>` exposes two different kinds of properties.
|
|
169
|
+
|
|
170
|
+
### Output state (bindable async state)
|
|
171
|
+
|
|
172
|
+
These properties represent the current connection state and are the main HAWC surface:
|
|
173
|
+
|
|
174
|
+
| Property | Type | Description |
|
|
175
|
+
|----------|------|-------------|
|
|
176
|
+
| `message` | `any` | Latest received message (JSON auto-parsed) |
|
|
177
|
+
| `connected` | `boolean` | `true` while WebSocket is open |
|
|
178
|
+
| `loading` | `boolean` | `true` while connecting |
|
|
179
|
+
| `error` | `WcsWsError \| Event \| null` | Connection or close error |
|
|
180
|
+
| `readyState` | `number` | WebSocket readyState constant |
|
|
181
|
+
|
|
182
|
+
### Input / command surface
|
|
183
|
+
|
|
184
|
+
These properties control connection and messaging from HTML, JS, or `@wcstack/state` bindings:
|
|
185
|
+
|
|
186
|
+
| Property | Type | Description |
|
|
187
|
+
|----------|------|-------------|
|
|
188
|
+
| `url` | `string` | WebSocket endpoint URL |
|
|
189
|
+
| `trigger` | `boolean` | One-way connection trigger |
|
|
190
|
+
| `send` | `any` | Set to transmit data (auto-stringifies objects) |
|
|
191
|
+
| `manual` | `boolean` | Disables auto-connect on DOM attach |
|
|
192
|
+
|
|
193
|
+
## Architecture
|
|
194
|
+
|
|
195
|
+
`@wcstack/websocket` follows the HAWC architecture.
|
|
196
|
+
|
|
197
|
+
### Core: `WebSocketCore`
|
|
198
|
+
|
|
199
|
+
`WebSocketCore` is a pure `EventTarget` class.
|
|
200
|
+
It contains:
|
|
201
|
+
|
|
202
|
+
- WebSocket connection management
|
|
203
|
+
- automatic reconnection logic
|
|
204
|
+
- JSON message parsing
|
|
205
|
+
- async state transitions
|
|
206
|
+
- `wc-bindable-protocol` declaration
|
|
207
|
+
|
|
208
|
+
It can run headlessly in any runtime that supports `EventTarget` and `WebSocket`.
|
|
209
|
+
|
|
210
|
+
### Shell: `<wcs-ws>`
|
|
211
|
+
|
|
212
|
+
`<wcs-ws>` is a thin `HTMLElement` wrapper around `WebSocketCore`.
|
|
213
|
+
It adds:
|
|
214
|
+
|
|
215
|
+
- attribute / property mapping
|
|
216
|
+
- DOM lifecycle integration
|
|
217
|
+
- declarative helpers: `trigger`, `send`
|
|
218
|
+
|
|
219
|
+
This split keeps the connection logic portable while allowing DOM-based binding systems such as `@wcstack/state` to interact with it naturally.
|
|
220
|
+
|
|
221
|
+
### Target injection
|
|
222
|
+
|
|
223
|
+
The Core dispatches events directly on the Shell via **target injection**, so no event re-dispatch is needed.
|
|
224
|
+
|
|
225
|
+
## Headless Usage (Core only)
|
|
226
|
+
|
|
227
|
+
`WebSocketCore` can be used standalone without the DOM. Since it declares `static wcBindable`, you can use `@wc-bindable/core`'s `bind()` to subscribe to its state:
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
import { WebSocketCore } from "@wcstack/websocket";
|
|
231
|
+
import { bind } from "@wc-bindable/core";
|
|
232
|
+
|
|
233
|
+
const core = new WebSocketCore();
|
|
234
|
+
|
|
235
|
+
const unbind = bind(core, (name, value) => {
|
|
236
|
+
console.log(`${name}:`, value);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
core.connect("wss://example.com/ws", {
|
|
240
|
+
autoReconnect: true,
|
|
241
|
+
reconnectInterval: 5000,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Send a message
|
|
245
|
+
core.send(JSON.stringify({ type: "ping" }));
|
|
246
|
+
|
|
247
|
+
// Clean up
|
|
248
|
+
core.close();
|
|
249
|
+
unbind();
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
This works in Node.js, Deno, Cloudflare Workers — anywhere `EventTarget` and `WebSocket` are available.
|
|
253
|
+
|
|
254
|
+
## URL Observation
|
|
255
|
+
|
|
256
|
+
By default, `<wcs-ws>` automatically opens a connection when:
|
|
257
|
+
|
|
258
|
+
1. it is connected to the DOM and `url` is set
|
|
259
|
+
2. the `url` attribute changes while connected to the DOM
|
|
260
|
+
|
|
261
|
+
Set the `manual` attribute to disable auto-connect and control the connection explicitly via `connect()` or `trigger`.
|
|
262
|
+
|
|
263
|
+
## Programmatic Usage
|
|
264
|
+
|
|
265
|
+
```javascript
|
|
266
|
+
const wsEl = document.querySelector("wcs-ws");
|
|
267
|
+
|
|
268
|
+
// Connect manually
|
|
269
|
+
wsEl.connect();
|
|
270
|
+
|
|
271
|
+
// Send data
|
|
272
|
+
wsEl.sendMessage(JSON.stringify({ type: "chat", content: "hello" }));
|
|
273
|
+
|
|
274
|
+
console.log(wsEl.message); // latest message
|
|
275
|
+
console.log(wsEl.connected); // boolean
|
|
276
|
+
console.log(wsEl.loading); // boolean
|
|
277
|
+
console.log(wsEl.error); // error info or null
|
|
278
|
+
console.log(wsEl.readyState); // WebSocket readyState
|
|
279
|
+
|
|
280
|
+
// Close
|
|
281
|
+
wsEl.close();
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Optional DOM Triggering
|
|
285
|
+
|
|
286
|
+
If `autoTrigger` is enabled (default), clicking an element with `data-wstarget` triggers the corresponding `<wcs-ws>` element:
|
|
287
|
+
|
|
288
|
+
```html
|
|
289
|
+
<button data-wstarget="my-ws">Connect</button>
|
|
290
|
+
<wcs-ws id="my-ws" url="wss://example.com/ws" manual></wcs-ws>
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Event delegation is used — works with dynamically added elements. The `closest()` API handles nested elements (e.g., icon inside a button).
|
|
294
|
+
|
|
295
|
+
If the target id does not match any element, or the matched element is not a `<wcs-ws>`, the click is silently ignored.
|
|
296
|
+
|
|
297
|
+
This is a convenience feature.
|
|
298
|
+
In wcstack applications, **state-driven triggering via `trigger`** is usually the primary pattern.
|
|
299
|
+
|
|
300
|
+
## Elements
|
|
301
|
+
|
|
302
|
+
### `<wcs-ws>`
|
|
303
|
+
|
|
304
|
+
| Attribute | Type | Default | Description |
|
|
305
|
+
|-----------|------|---------|-------------|
|
|
306
|
+
| `url` | `string` | — | WebSocket endpoint URL |
|
|
307
|
+
| `protocols` | `string` | — | Comma-separated subprotocol list |
|
|
308
|
+
| `manual` | `boolean` | `false` | Disable auto-connect |
|
|
309
|
+
| `auto-reconnect` | `boolean` | `false` | Enable automatic reconnection |
|
|
310
|
+
| `reconnect-interval` | `number` | `3000` | Reconnection delay in ms |
|
|
311
|
+
| `max-reconnects` | `number` | `Infinity` | Maximum reconnection attempts |
|
|
312
|
+
|
|
313
|
+
| Property | Type | Description |
|
|
314
|
+
|----------|------|-------------|
|
|
315
|
+
| `message` | `any` | Latest received message (JSON auto-parsed) |
|
|
316
|
+
| `connected` | `boolean` | `true` while WebSocket is open |
|
|
317
|
+
| `loading` | `boolean` | `true` while connecting |
|
|
318
|
+
| `error` | `WcsWsError \| Event \| null` | Error info |
|
|
319
|
+
| `readyState` | `number` | WebSocket readyState constant |
|
|
320
|
+
| `trigger` | `boolean` | Set to `true` to open connection |
|
|
321
|
+
| `send` | `any` | Set to transmit data |
|
|
322
|
+
|
|
323
|
+
| Method | Description |
|
|
324
|
+
|--------|-------------|
|
|
325
|
+
| `connect()` | Open the WebSocket connection |
|
|
326
|
+
| `sendMessage(data)` | Send data over the connection |
|
|
327
|
+
| `close(code?, reason?)` | Close the connection |
|
|
328
|
+
|
|
329
|
+
## wc-bindable-protocol
|
|
330
|
+
|
|
331
|
+
Both `WebSocketCore` and `<wcs-ws>` declare `wc-bindable-protocol` compliance, making them interoperable with any framework or component that supports the protocol.
|
|
332
|
+
|
|
333
|
+
### Core (`WebSocketCore`)
|
|
334
|
+
|
|
335
|
+
`WebSocketCore` declares the bindable async state that any runtime can subscribe to:
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
static wcBindable = {
|
|
339
|
+
protocol: "wc-bindable",
|
|
340
|
+
version: 1,
|
|
341
|
+
properties: [
|
|
342
|
+
{ name: "message", event: "wcs-ws:message" },
|
|
343
|
+
{ name: "connected", event: "wcs-ws:connected-changed" },
|
|
344
|
+
{ name: "loading", event: "wcs-ws:loading-changed" },
|
|
345
|
+
{ name: "error", event: "wcs-ws:error" },
|
|
346
|
+
{ name: "readyState", event: "wcs-ws:readystate-changed" },
|
|
347
|
+
],
|
|
348
|
+
};
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Headless consumers call `core.connect(url)` directly — no `trigger` needed.
|
|
352
|
+
|
|
353
|
+
### Shell (`<wcs-ws>`)
|
|
354
|
+
|
|
355
|
+
The Shell extends the Core declaration with `trigger` and `send` so binding systems can control the connection declaratively:
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
static wcBindable = {
|
|
359
|
+
...WebSocketCore.wcBindable,
|
|
360
|
+
properties: [
|
|
361
|
+
...WebSocketCore.wcBindable.properties,
|
|
362
|
+
{ name: "trigger", event: "wcs-ws:trigger-changed" },
|
|
363
|
+
{ name: "send", event: "wcs-ws:send-changed" },
|
|
364
|
+
],
|
|
365
|
+
};
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## TypeScript Types
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
import type {
|
|
372
|
+
WcsWsError, WcsWsCoreValues, WcsWsValues
|
|
373
|
+
} from "@wcstack/websocket";
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
// WebSocket error
|
|
378
|
+
interface WcsWsError {
|
|
379
|
+
code?: number;
|
|
380
|
+
reason?: string;
|
|
381
|
+
message?: string;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Core (headless) — 5 async state properties
|
|
385
|
+
// T defaults to unknown; pass a type argument for typed `message`
|
|
386
|
+
interface WcsWsCoreValues<T = unknown> {
|
|
387
|
+
message: T;
|
|
388
|
+
connected: boolean;
|
|
389
|
+
loading: boolean;
|
|
390
|
+
error: WcsWsError | Event | null;
|
|
391
|
+
readyState: number;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Shell (<wcs-ws>) — extends Core with trigger and send
|
|
395
|
+
interface WcsWsValues<T = unknown> extends WcsWsCoreValues<T> {
|
|
396
|
+
trigger: boolean;
|
|
397
|
+
send: unknown;
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## Why this works well with `@wcstack/state`
|
|
402
|
+
|
|
403
|
+
`@wcstack/state` uses path strings as the only contract between UI and state.
|
|
404
|
+
`<wcs-ws>` fits this model naturally:
|
|
405
|
+
|
|
406
|
+
- state determines the `url` or fires `trigger`
|
|
407
|
+
- `<wcs-ws>` opens and manages the connection
|
|
408
|
+
- incoming data arrives as `message`; status as `connected`, `loading`, `error`
|
|
409
|
+
- UI binds to those paths without writing WebSocket glue code
|
|
410
|
+
- outgoing data flows via the `send` property
|
|
411
|
+
|
|
412
|
+
This makes real-time communication look like ordinary state updates.
|
|
413
|
+
|
|
414
|
+
## Framework Integration
|
|
415
|
+
|
|
416
|
+
Since `<wcs-ws>` is HAWC + `wc-bindable-protocol`, it works with any framework through thin adapters from `@wc-bindable/*`.
|
|
417
|
+
|
|
418
|
+
### React
|
|
419
|
+
|
|
420
|
+
```tsx
|
|
421
|
+
import { useWcBindable } from "@wc-bindable/react";
|
|
422
|
+
import type { WcsWsValues } from "@wcstack/websocket";
|
|
423
|
+
|
|
424
|
+
interface ChatMessage { type: string; content: string; }
|
|
425
|
+
|
|
426
|
+
function Chat() {
|
|
427
|
+
const [ref, { message, connected, loading }] =
|
|
428
|
+
useWcBindable<HTMLElement, WcsWsValues<ChatMessage>>();
|
|
429
|
+
|
|
430
|
+
return (
|
|
431
|
+
<>
|
|
432
|
+
<wcs-ws ref={ref} url="wss://example.com/ws" auto-reconnect />
|
|
433
|
+
{loading && <p>Connecting...</p>}
|
|
434
|
+
{connected && <p>Connected</p>}
|
|
435
|
+
{message && <pre>{JSON.stringify(message)}</pre>}
|
|
436
|
+
</>
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Vue
|
|
442
|
+
|
|
443
|
+
```vue
|
|
444
|
+
<script setup lang="ts">
|
|
445
|
+
import { useWcBindable } from "@wc-bindable/vue";
|
|
446
|
+
import type { WcsWsValues } from "@wcstack/websocket";
|
|
447
|
+
|
|
448
|
+
interface ChatMessage { type: string; content: string; }
|
|
449
|
+
|
|
450
|
+
const { ref, values } = useWcBindable<HTMLElement, WcsWsValues<ChatMessage>>();
|
|
451
|
+
</script>
|
|
452
|
+
|
|
453
|
+
<template>
|
|
454
|
+
<wcs-ws :ref="ref" url="wss://example.com/ws" auto-reconnect />
|
|
455
|
+
<p v-if="values.loading">Connecting...</p>
|
|
456
|
+
<p v-else-if="values.connected">Connected</p>
|
|
457
|
+
<pre v-if="values.message">{{ values.message }}</pre>
|
|
458
|
+
</template>
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### Svelte
|
|
462
|
+
|
|
463
|
+
```svelte
|
|
464
|
+
<script>
|
|
465
|
+
import { wcBindable } from "@wc-bindable/svelte";
|
|
466
|
+
|
|
467
|
+
let message = $state(null);
|
|
468
|
+
let connected = $state(false);
|
|
469
|
+
</script>
|
|
470
|
+
|
|
471
|
+
<wcs-ws url="wss://example.com/ws" auto-reconnect
|
|
472
|
+
use:wcBindable={{ onUpdate: (name, v) => {
|
|
473
|
+
if (name === "message") message = v;
|
|
474
|
+
if (name === "connected") connected = v;
|
|
475
|
+
}}} />
|
|
476
|
+
|
|
477
|
+
<p>{connected ? "Connected" : "Disconnected"}</p>
|
|
478
|
+
{#if message}
|
|
479
|
+
<pre>{JSON.stringify(message)}</pre>
|
|
480
|
+
{/if}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Solid
|
|
484
|
+
|
|
485
|
+
```tsx
|
|
486
|
+
import { createWcBindable } from "@wc-bindable/solid";
|
|
487
|
+
import type { WcsWsValues } from "@wcstack/websocket";
|
|
488
|
+
|
|
489
|
+
interface ChatMessage { type: string; content: string; }
|
|
490
|
+
|
|
491
|
+
function Chat() {
|
|
492
|
+
const [values, directive] = createWcBindable<WcsWsValues<ChatMessage>>();
|
|
493
|
+
|
|
494
|
+
return (
|
|
495
|
+
<>
|
|
496
|
+
<wcs-ws ref={directive} url="wss://example.com/ws" auto-reconnect />
|
|
497
|
+
<Show when={values.connected} fallback={<p>Disconnected</p>}>
|
|
498
|
+
<p>Connected</p>
|
|
499
|
+
</Show>
|
|
500
|
+
<Show when={values.message}>
|
|
501
|
+
<pre>{JSON.stringify(values.message)}</pre>
|
|
502
|
+
</Show>
|
|
503
|
+
</>
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Vanilla — `bind()` directly
|
|
509
|
+
|
|
510
|
+
```javascript
|
|
511
|
+
import { bind } from "@wc-bindable/core";
|
|
512
|
+
|
|
513
|
+
const wsEl = document.querySelector("wcs-ws");
|
|
514
|
+
|
|
515
|
+
bind(wsEl, (name, value) => {
|
|
516
|
+
console.log(`${name} changed:`, value);
|
|
517
|
+
});
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
## Configuration
|
|
521
|
+
|
|
522
|
+
```javascript
|
|
523
|
+
import { bootstrapWebSocket } from "@wcstack/websocket";
|
|
524
|
+
|
|
525
|
+
bootstrapWebSocket({
|
|
526
|
+
autoTrigger: true,
|
|
527
|
+
triggerAttribute: "data-wstarget",
|
|
528
|
+
tagNames: {
|
|
529
|
+
ws: "wcs-ws",
|
|
530
|
+
},
|
|
531
|
+
});
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
## Design Notes
|
|
535
|
+
|
|
536
|
+
- `message`, `connected`, `loading`, `error`, and `readyState` are **output state**
|
|
537
|
+
- `url`, `trigger`, and `send` are **input / command surface**
|
|
538
|
+
- `trigger` is intentionally one-way: writing `true` connects, reset emits completion
|
|
539
|
+
- `send` transmits immediately and resets to `null` — set it each time you want to send
|
|
540
|
+
- JSON messages are automatically parsed on receive; objects are auto-stringified on send
|
|
541
|
+
- `manual` is useful when connection timing should be controlled explicitly
|
|
542
|
+
- Auto-reconnect only fires on abnormal close (code other than 1000)
|
|
543
|
+
|
|
544
|
+
## License
|
|
545
|
+
|
|
546
|
+
MIT
|
package/dist/auto.js
ADDED
package/dist/auto.min.js
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
interface ITagNames {
|
|
2
|
+
readonly ws: string;
|
|
3
|
+
}
|
|
4
|
+
interface IWritableTagNames {
|
|
5
|
+
ws?: string;
|
|
6
|
+
}
|
|
7
|
+
interface IConfig {
|
|
8
|
+
readonly autoTrigger: boolean;
|
|
9
|
+
readonly triggerAttribute: string;
|
|
10
|
+
readonly tagNames: ITagNames;
|
|
11
|
+
}
|
|
12
|
+
interface IWritableConfig {
|
|
13
|
+
autoTrigger?: boolean;
|
|
14
|
+
triggerAttribute?: string;
|
|
15
|
+
tagNames?: IWritableTagNames;
|
|
16
|
+
}
|
|
17
|
+
interface IWcBindableProperty {
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly event: string;
|
|
20
|
+
readonly getter?: (event: Event) => any;
|
|
21
|
+
}
|
|
22
|
+
interface IWcBindable {
|
|
23
|
+
readonly protocol: "wc-bindable";
|
|
24
|
+
readonly version: number;
|
|
25
|
+
readonly properties: IWcBindableProperty[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* WebSocket error object.
|
|
29
|
+
*/
|
|
30
|
+
interface WcsWsError {
|
|
31
|
+
code?: number;
|
|
32
|
+
reason?: string;
|
|
33
|
+
message?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Value types for WebSocketCore (headless) — the async state properties.
|
|
37
|
+
*/
|
|
38
|
+
interface WcsWsCoreValues<T = unknown> {
|
|
39
|
+
message: T;
|
|
40
|
+
connected: boolean;
|
|
41
|
+
loading: boolean;
|
|
42
|
+
error: WcsWsError | Event | null;
|
|
43
|
+
readyState: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Value types for the Shell (`<wcs-ws>`) — extends Core with `trigger` and `send`.
|
|
47
|
+
*/
|
|
48
|
+
interface WcsWsValues<T = unknown> extends WcsWsCoreValues<T> {
|
|
49
|
+
trigger: boolean;
|
|
50
|
+
send: unknown;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
declare function bootstrapWebSocket(userConfig?: IWritableConfig): void;
|
|
54
|
+
|
|
55
|
+
declare function getConfig(): IConfig;
|
|
56
|
+
|
|
57
|
+
interface WebSocketConnectOptions {
|
|
58
|
+
protocols?: string | string[];
|
|
59
|
+
autoReconnect?: boolean;
|
|
60
|
+
reconnectInterval?: number;
|
|
61
|
+
maxReconnects?: number;
|
|
62
|
+
}
|
|
63
|
+
declare class WebSocketCore extends EventTarget {
|
|
64
|
+
static wcBindable: IWcBindable;
|
|
65
|
+
private _target;
|
|
66
|
+
private _ws;
|
|
67
|
+
private _message;
|
|
68
|
+
private _connected;
|
|
69
|
+
private _loading;
|
|
70
|
+
private _error;
|
|
71
|
+
private _readyState;
|
|
72
|
+
private _autoReconnect;
|
|
73
|
+
private _reconnectInterval;
|
|
74
|
+
private _maxReconnects;
|
|
75
|
+
private _reconnectCount;
|
|
76
|
+
private _reconnectTimer;
|
|
77
|
+
private _url;
|
|
78
|
+
private _protocols;
|
|
79
|
+
private _intentionalClose;
|
|
80
|
+
constructor(target?: EventTarget);
|
|
81
|
+
get message(): any;
|
|
82
|
+
get connected(): boolean;
|
|
83
|
+
get loading(): boolean;
|
|
84
|
+
get error(): any;
|
|
85
|
+
get readyState(): number;
|
|
86
|
+
private _setMessage;
|
|
87
|
+
private _setConnected;
|
|
88
|
+
private _setLoading;
|
|
89
|
+
private _setError;
|
|
90
|
+
private _setReadyState;
|
|
91
|
+
connect(url: string, options?: WebSocketConnectOptions): void;
|
|
92
|
+
send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void;
|
|
93
|
+
close(code?: number, reason?: string): void;
|
|
94
|
+
private _doConnect;
|
|
95
|
+
private _onOpen;
|
|
96
|
+
private _onMessage;
|
|
97
|
+
private _onError;
|
|
98
|
+
private _onClose;
|
|
99
|
+
private _scheduleReconnect;
|
|
100
|
+
private _clearReconnectTimer;
|
|
101
|
+
private _removeListeners;
|
|
102
|
+
private _closeInternal;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
declare class WcsWebSocket extends HTMLElement {
|
|
106
|
+
static hasConnectedCallbackPromise: boolean;
|
|
107
|
+
static wcBindable: IWcBindable;
|
|
108
|
+
static get observedAttributes(): string[];
|
|
109
|
+
private _core;
|
|
110
|
+
private _trigger;
|
|
111
|
+
constructor();
|
|
112
|
+
get url(): string;
|
|
113
|
+
set url(value: string);
|
|
114
|
+
get protocols(): string;
|
|
115
|
+
set protocols(value: string);
|
|
116
|
+
get autoReconnect(): boolean;
|
|
117
|
+
set autoReconnect(value: boolean);
|
|
118
|
+
get reconnectInterval(): number;
|
|
119
|
+
set reconnectInterval(value: number);
|
|
120
|
+
get maxReconnects(): number;
|
|
121
|
+
set maxReconnects(value: number);
|
|
122
|
+
get manual(): boolean;
|
|
123
|
+
set manual(value: boolean);
|
|
124
|
+
get message(): any;
|
|
125
|
+
get connected(): boolean;
|
|
126
|
+
get loading(): boolean;
|
|
127
|
+
get error(): any;
|
|
128
|
+
get readyState(): number;
|
|
129
|
+
get trigger(): boolean;
|
|
130
|
+
set trigger(value: boolean);
|
|
131
|
+
set send(data: any);
|
|
132
|
+
connect(): void;
|
|
133
|
+
sendMessage(data: string | ArrayBufferLike | Blob | ArrayBufferView): void;
|
|
134
|
+
close(code?: number, reason?: string): void;
|
|
135
|
+
attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null): void;
|
|
136
|
+
connectedCallback(): void;
|
|
137
|
+
disconnectedCallback(): void;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export { WcsWebSocket, WebSocketCore, bootstrapWebSocket, getConfig };
|
|
141
|
+
export type { IWritableConfig, IWritableTagNames, WcsWsCoreValues, WcsWsError, WcsWsValues, WebSocketConnectOptions };
|