@kyneta/websocket-transport 1.3.0 → 1.4.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 +28 -15
- package/dist/browser.d.ts +59 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +2 -0
- package/dist/bun.d.ts +22 -22
- package/dist/bun.d.ts.map +1 -0
- package/dist/bun.js +95 -43
- package/dist/bun.js.map +1 -1
- package/dist/client-transport-CKjXedwS.d.ts +134 -0
- package/dist/client-transport-CKjXedwS.d.ts.map +1 -0
- package/dist/client-transport-DIZ-LJxs.js +510 -0
- package/dist/client-transport-DIZ-LJxs.js.map +1 -0
- package/dist/server.d.ts +154 -115
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +298 -296
- package/dist/server.js.map +1 -1
- package/dist/types-CJAcr1Df.d.ts +199 -0
- package/dist/types-CJAcr1Df.d.ts.map +1 -0
- package/package.json +15 -15
- package/src/__tests__/client-transport.test.ts +167 -0
- package/src/{client.ts → browser.ts} +15 -10
- package/src/bun-websocket.ts +5 -5
- package/src/bun.ts +1 -1
- package/src/client-transport.ts +42 -67
- package/src/connection.ts +1 -1
- package/src/server-transport.ts +10 -10
- package/src/server.ts +22 -4
- package/src/service-client.ts +52 -0
- package/src/types.ts +74 -6
- package/dist/chunk-PSG3LLT5.js +0 -111
- package/dist/chunk-PSG3LLT5.js.map +0 -1
- package/dist/client.d.ts +0 -207
- package/dist/client.js +0 -508
- package/dist/client.js.map +0 -1
- package/dist/types-DdNb8cAz.d.ts +0 -149
package/src/client-transport.ts
CHANGED
|
@@ -36,6 +36,13 @@ import type {
|
|
|
36
36
|
WebsocketClientState,
|
|
37
37
|
WebsocketClientStateTransition,
|
|
38
38
|
} from "./types.js"
|
|
39
|
+
import {
|
|
40
|
+
READY_STATE,
|
|
41
|
+
type WebSocketCloseEvent,
|
|
42
|
+
type WebSocketConstructor,
|
|
43
|
+
type WebSocketLike,
|
|
44
|
+
type WebSocketMessageEvent,
|
|
45
|
+
} from "./types.js"
|
|
39
46
|
|
|
40
47
|
// Re-export state types for convenience
|
|
41
48
|
export type {
|
|
@@ -61,8 +68,23 @@ export interface WebsocketClientOptions {
|
|
|
61
68
|
/** Websocket URL to connect to. Can be a string or a function of peerId. */
|
|
62
69
|
url: string | ((peerId: PeerId) => string)
|
|
63
70
|
|
|
64
|
-
/**
|
|
65
|
-
|
|
71
|
+
/**
|
|
72
|
+
* WebSocket constructor — caller must provide explicitly.
|
|
73
|
+
*
|
|
74
|
+
* In browsers, pass the global `WebSocket`. In Node.js, pass `ws`'s
|
|
75
|
+
* `WebSocket`. In Bun, pass `globalThis.WebSocket`. The transport
|
|
76
|
+
* never probes `globalThis` on its own.
|
|
77
|
+
*/
|
|
78
|
+
WebSocket: WebSocketConstructor
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Headers to send during Websocket upgrade.
|
|
82
|
+
* Used for authentication in service-to-service communication.
|
|
83
|
+
*
|
|
84
|
+
* Note: Headers are a Bun/Node-specific extension. The browser WebSocket
|
|
85
|
+
* API does not support custom headers per the WHATWG spec.
|
|
86
|
+
*/
|
|
87
|
+
headers?: Record<string, string>
|
|
66
88
|
|
|
67
89
|
/** Reconnection options. */
|
|
68
90
|
reconnect?: {
|
|
@@ -106,21 +128,6 @@ export interface WebsocketClientLifecycleEvents {
|
|
|
106
128
|
onReady?: () => void
|
|
107
129
|
}
|
|
108
130
|
|
|
109
|
-
/**
|
|
110
|
-
* Options for service-to-service Websocket connections.
|
|
111
|
-
* Extends WebsocketClientOptions with header support for authentication.
|
|
112
|
-
*
|
|
113
|
-
* Note: Headers are a Bun/Node-specific extension. The browser WebSocket API
|
|
114
|
-
* does not support custom headers per the WHATWG spec.
|
|
115
|
-
*/
|
|
116
|
-
export interface ServiceWebsocketClientOptions extends WebsocketClientOptions {
|
|
117
|
-
/**
|
|
118
|
-
* Headers to send during Websocket upgrade.
|
|
119
|
-
* Used for authentication in service-to-service communication.
|
|
120
|
-
*/
|
|
121
|
-
headers?: Record<string, string>
|
|
122
|
-
}
|
|
123
|
-
|
|
124
131
|
// ---------------------------------------------------------------------------
|
|
125
132
|
// WebsocketClientTransport
|
|
126
133
|
// ---------------------------------------------------------------------------
|
|
@@ -136,19 +143,19 @@ export interface ServiceWebsocketClientOptions extends WebsocketClientOptions {
|
|
|
136
143
|
* This class is the imperative shell that interprets data effects as I/O.
|
|
137
144
|
*
|
|
138
145
|
* Prefer the factory functions for construction:
|
|
139
|
-
* - `createWebsocketClient()` — browser-to-server
|
|
140
|
-
* - `createServiceWebsocketClient()` — service-to-service
|
|
146
|
+
* - `createWebsocketClient()` — browser-to-server (from `./browser`)
|
|
147
|
+
* - `createServiceWebsocketClient()` — service-to-service with headers (from `./server`)
|
|
141
148
|
*/
|
|
142
149
|
export class WebsocketClientTransport extends Transport<void> {
|
|
143
150
|
#peerId?: PeerId
|
|
144
|
-
#options:
|
|
145
|
-
#WebSocketImpl:
|
|
151
|
+
#options: WebsocketClientOptions
|
|
152
|
+
#WebSocketImpl: WebSocketConstructor
|
|
146
153
|
|
|
147
154
|
// Observable program handle — created in constructor, drives all state
|
|
148
155
|
#handle: ObservableHandle<WsClientMsg, WebsocketClientState>
|
|
149
156
|
|
|
150
157
|
// Executor-local I/O state — not in the program model
|
|
151
|
-
#socket?:
|
|
158
|
+
#socket?: WebSocketLike
|
|
152
159
|
#serverChannel?: Channel
|
|
153
160
|
#keepaliveTimer?: ReturnType<typeof setInterval>
|
|
154
161
|
#reconnectTimer?: ReturnType<typeof setTimeout>
|
|
@@ -157,10 +164,10 @@ export class WebsocketClientTransport extends Transport<void> {
|
|
|
157
164
|
readonly #fragmentThreshold: number
|
|
158
165
|
readonly #reassembler: FragmentReassembler
|
|
159
166
|
|
|
160
|
-
constructor(options:
|
|
167
|
+
constructor(options: WebsocketClientOptions) {
|
|
161
168
|
super({ transportType: "websocket-client" })
|
|
162
169
|
this.#options = options
|
|
163
|
-
this.#WebSocketImpl = options.WebSocket
|
|
170
|
+
this.#WebSocketImpl = options.WebSocket
|
|
164
171
|
this.#fragmentThreshold =
|
|
165
172
|
options.fragmentThreshold ?? DEFAULT_FRAGMENT_THRESHOLD
|
|
166
173
|
this.#reassembler = new FragmentReassembler({
|
|
@@ -279,18 +286,14 @@ export class WebsocketClientTransport extends Transport<void> {
|
|
|
279
286
|
: this.#options.url
|
|
280
287
|
|
|
281
288
|
try {
|
|
282
|
-
// Create WebSocket
|
|
289
|
+
// Create WebSocket — pass headers as second arg when present.
|
|
290
|
+
// The structural WebSocketConstructor's `...rest: any[]` absorbs
|
|
291
|
+
// both the browser's protocols arg and backend's { headers } arg.
|
|
283
292
|
if (
|
|
284
293
|
this.#options.headers &&
|
|
285
294
|
Object.keys(this.#options.headers).length > 0
|
|
286
295
|
) {
|
|
287
|
-
|
|
288
|
-
url: string,
|
|
289
|
-
options: { headers: Record<string, string> },
|
|
290
|
-
) => WebSocket
|
|
291
|
-
const BunWebSocket = this
|
|
292
|
-
.#WebSocketImpl as unknown as BunWebSocketConstructor
|
|
293
|
-
this.#socket = new BunWebSocket(url, {
|
|
296
|
+
this.#socket = new this.#WebSocketImpl(url, {
|
|
294
297
|
headers: this.#options.headers,
|
|
295
298
|
})
|
|
296
299
|
} else {
|
|
@@ -302,7 +305,7 @@ export class WebsocketClientTransport extends Transport<void> {
|
|
|
302
305
|
|
|
303
306
|
// Set up message handler IMMEDIATELY to handle the "ready" race condition.
|
|
304
307
|
// The server may send "ready" before the open event fires.
|
|
305
|
-
socket.addEventListener("message", (event:
|
|
308
|
+
socket.addEventListener("message", (event: WebSocketMessageEvent) => {
|
|
306
309
|
this.#handleMessage(event, dispatch)
|
|
307
310
|
})
|
|
308
311
|
|
|
@@ -315,7 +318,7 @@ export class WebsocketClientTransport extends Transport<void> {
|
|
|
315
318
|
dispatch({ type: "socket-opened" })
|
|
316
319
|
|
|
317
320
|
// After open, set up permanent close handler for post-connection closes
|
|
318
|
-
socket.addEventListener("close", (event:
|
|
321
|
+
socket.addEventListener("close", (event: WebSocketCloseEvent) => {
|
|
319
322
|
dispatch({
|
|
320
323
|
type: "socket-closed",
|
|
321
324
|
code: event.code,
|
|
@@ -372,7 +375,7 @@ export class WebsocketClientTransport extends Transport<void> {
|
|
|
372
375
|
* Binary frames carry CBOR-encoded ChannelMsg.
|
|
373
376
|
*/
|
|
374
377
|
#handleMessage(
|
|
375
|
-
event:
|
|
378
|
+
event: WebSocketMessageEvent,
|
|
376
379
|
dispatch: (msg: WsClientMsg) => void,
|
|
377
380
|
): void {
|
|
378
381
|
const data = event.data
|
|
@@ -426,7 +429,7 @@ export class WebsocketClientTransport extends Transport<void> {
|
|
|
426
429
|
const interval = this.#options.keepaliveInterval ?? 30_000
|
|
427
430
|
|
|
428
431
|
this.#keepaliveTimer = setInterval(() => {
|
|
429
|
-
if (this.#socket?.readyState ===
|
|
432
|
+
if (this.#socket?.readyState === READY_STATE.OPEN) {
|
|
430
433
|
this.#socket.send("ping")
|
|
431
434
|
}
|
|
432
435
|
}, interval)
|
|
@@ -536,7 +539,7 @@ export class WebsocketClientTransport extends Transport<void> {
|
|
|
536
539
|
transportType: this.transportType,
|
|
537
540
|
send: (msg: ChannelMsg) => {
|
|
538
541
|
const socket = this.#socket
|
|
539
|
-
if (!socket || socket.readyState !==
|
|
542
|
+
if (!socket || socket.readyState !== READY_STATE.OPEN) {
|
|
540
543
|
return
|
|
541
544
|
}
|
|
542
545
|
|
|
@@ -581,11 +584,12 @@ export class WebsocketClientTransport extends Transport<void> {
|
|
|
581
584
|
*
|
|
582
585
|
* @example
|
|
583
586
|
* ```typescript
|
|
584
|
-
* import { createWebsocketClient } from "@kyneta/websocket-transport/
|
|
587
|
+
* import { createWebsocketClient } from "@kyneta/websocket-transport/browser"
|
|
585
588
|
*
|
|
586
589
|
* const exchange = new Exchange({
|
|
587
590
|
* transports: [createWebsocketClient({
|
|
588
591
|
* url: "ws://localhost:3000/ws",
|
|
592
|
+
* WebSocket,
|
|
589
593
|
* reconnect: { enabled: true },
|
|
590
594
|
* })],
|
|
591
595
|
* })
|
|
@@ -596,32 +600,3 @@ export function createWebsocketClient(
|
|
|
596
600
|
): TransportFactory {
|
|
597
601
|
return () => new WebsocketClientTransport(options)
|
|
598
602
|
}
|
|
599
|
-
|
|
600
|
-
/**
|
|
601
|
-
* Create a Websocket client transport for service-to-service connections.
|
|
602
|
-
*
|
|
603
|
-
* This factory is for backend environments (Bun, Node.js) where you need
|
|
604
|
-
* to pass authentication headers during the Websocket upgrade.
|
|
605
|
-
*
|
|
606
|
-
* Note: Headers are a Bun/Node-specific extension. The browser WebSocket API
|
|
607
|
-
* does not support custom headers. For browser clients, use
|
|
608
|
-
* `createWebsocketClient()` and authenticate via URL query parameters.
|
|
609
|
-
*
|
|
610
|
-
* @example
|
|
611
|
-
* ```typescript
|
|
612
|
-
* import { createServiceWebsocketClient } from "@kyneta/websocket-transport/client"
|
|
613
|
-
*
|
|
614
|
-
* const exchange = new Exchange({
|
|
615
|
-
* transports: [createServiceWebsocketClient({
|
|
616
|
-
* url: "ws://primary-server:3000/ws",
|
|
617
|
-
* headers: { Authorization: "Bearer token" },
|
|
618
|
-
* reconnect: { enabled: true },
|
|
619
|
-
* })],
|
|
620
|
-
* })
|
|
621
|
-
* ```
|
|
622
|
-
*/
|
|
623
|
-
export function createServiceWebsocketClient(
|
|
624
|
-
options: ServiceWebsocketClientOptions,
|
|
625
|
-
): TransportFactory {
|
|
626
|
-
return () => new WebsocketClientTransport(options)
|
|
627
|
-
}
|
package/src/connection.ts
CHANGED
|
@@ -133,7 +133,7 @@ export class WebsocketConnection {
|
|
|
133
133
|
*
|
|
134
134
|
* This is a transport-level text message that tells the client the
|
|
135
135
|
* server is ready to receive protocol messages. The client creates
|
|
136
|
-
* its channel and sends establish
|
|
136
|
+
* its channel and sends establish after receiving this.
|
|
137
137
|
*/
|
|
138
138
|
sendReady(): void {
|
|
139
139
|
if (this.#socket.readyState !== "open") {
|
package/src/server-transport.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// library through the Socket interface.
|
|
6
6
|
//
|
|
7
7
|
// Usage with Bun:
|
|
8
|
-
// import { WebsocketServerTransport } from "@kyneta/websocket-
|
|
9
|
-
// import { createBunWebsocketHandlers } from "@kyneta/websocket-
|
|
8
|
+
// import { WebsocketServerTransport } from "@kyneta/websocket-transport/server"
|
|
9
|
+
// import { createBunWebsocketHandlers } from "@kyneta/websocket-transport/bun"
|
|
10
10
|
//
|
|
11
11
|
// const serverAdapter = new WebsocketServerTransport()
|
|
12
12
|
// Bun.serve({
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// })
|
|
16
16
|
//
|
|
17
17
|
// Usage with Node.js `ws`:
|
|
18
|
-
// import { WebsocketServerTransport, wrapNodeWebsocket } from "@kyneta/websocket-
|
|
18
|
+
// import { WebsocketServerTransport, wrapNodeWebsocket } from "@kyneta/websocket-transport/server"
|
|
19
19
|
// import { WebSocketServer } from "ws"
|
|
20
20
|
//
|
|
21
21
|
// const serverAdapter = new WebsocketServerTransport()
|
|
@@ -88,12 +88,12 @@ function generatePeerId(): PeerId {
|
|
|
88
88
|
*
|
|
89
89
|
* The connection handshake follows a two-phase protocol:
|
|
90
90
|
* 1. Server sends text `"ready"` signal (transport-level)
|
|
91
|
-
* 2. Client sends `establish
|
|
92
|
-
* 3. Server
|
|
91
|
+
* 2. Client sends `establish` (protocol-level)
|
|
92
|
+
* 3. Server upgrades channel and sends present (handled by Synchronizer)
|
|
93
93
|
*
|
|
94
94
|
* The server does NOT call `establishChannel()` — it waits for the
|
|
95
|
-
* client's establish
|
|
96
|
-
* establish
|
|
95
|
+
* client's establish to avoid a race condition where the binary
|
|
96
|
+
* establish could arrive before the client has processed "ready".
|
|
97
97
|
*/
|
|
98
98
|
export class WebsocketServerTransport extends Transport<PeerId> {
|
|
99
99
|
#connections = new Map<PeerId, WebsocketConnection>()
|
|
@@ -219,11 +219,11 @@ export class WebsocketServerTransport extends Transport<PeerId> {
|
|
|
219
219
|
connection.sendReady()
|
|
220
220
|
|
|
221
221
|
// NOTE: We do NOT call establishChannel() here.
|
|
222
|
-
// The client will send establish
|
|
222
|
+
// The client will send establish after receiving "ready".
|
|
223
223
|
// Our channel gets established when the Synchronizer receives
|
|
224
|
-
// and processes that establish
|
|
224
|
+
// and processes that establish message.
|
|
225
225
|
//
|
|
226
|
-
// This prevents a race condition where our binary establish
|
|
226
|
+
// This prevents a race condition where our binary establish
|
|
227
227
|
// could arrive before the client has processed "ready" and created
|
|
228
228
|
// its channel.
|
|
229
229
|
},
|
package/src/server.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
// server — barrel export for @kyneta/websocket-
|
|
1
|
+
// server — barrel export for @kyneta/websocket-transport/server.
|
|
2
2
|
//
|
|
3
3
|
// This is the server-side entry point. It exports everything needed
|
|
4
|
-
// to create a Websocket server
|
|
4
|
+
// to create a Websocket server transport with any framework, plus
|
|
5
|
+
// the service-to-service client factory for backend connections.
|
|
5
6
|
|
|
6
7
|
// ---------------------------------------------------------------------------
|
|
7
|
-
// Server
|
|
8
|
+
// Server transport
|
|
8
9
|
// ---------------------------------------------------------------------------
|
|
9
10
|
|
|
10
11
|
export {
|
|
@@ -22,6 +23,15 @@ export {
|
|
|
22
23
|
type WebsocketConnectionConfig,
|
|
23
24
|
} from "./connection.js"
|
|
24
25
|
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Service-to-service client (backend connections with headers)
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
createServiceWebsocketClient,
|
|
32
|
+
type ServiceWebsocketClientOptions,
|
|
33
|
+
} from "./service-client.js"
|
|
34
|
+
|
|
25
35
|
// ---------------------------------------------------------------------------
|
|
26
36
|
// Shared types + wrappers
|
|
27
37
|
// ---------------------------------------------------------------------------
|
|
@@ -31,9 +41,17 @@ export type {
|
|
|
31
41
|
NodeWebsocketLike,
|
|
32
42
|
Socket,
|
|
33
43
|
SocketReadyState,
|
|
44
|
+
WebSocketCloseEvent,
|
|
45
|
+
WebSocketConstructor,
|
|
46
|
+
WebSocketLike,
|
|
47
|
+
WebSocketMessageEvent,
|
|
34
48
|
WebsocketConnectionHandle,
|
|
35
49
|
WebsocketConnectionOptions,
|
|
36
50
|
WebsocketConnectionResult,
|
|
37
51
|
} from "./types.js"
|
|
38
52
|
|
|
39
|
-
export {
|
|
53
|
+
export {
|
|
54
|
+
READY_STATE,
|
|
55
|
+
wrapNodeWebsocket,
|
|
56
|
+
wrapStandardWebsocket,
|
|
57
|
+
} from "./types.js"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// service-client — service-to-service WebSocket client factory.
|
|
2
|
+
//
|
|
3
|
+
// Extracted from client-transport.ts so that the service client factory
|
|
4
|
+
// lives in the `./server` entry point (where it belongs) rather than
|
|
5
|
+
// the `./browser` entry point. Backend code imports from `./server`;
|
|
6
|
+
// browser code imports from `./browser`.
|
|
7
|
+
|
|
8
|
+
import type { TransportFactory } from "@kyneta/transport"
|
|
9
|
+
import {
|
|
10
|
+
type WebsocketClientOptions,
|
|
11
|
+
WebsocketClientTransport,
|
|
12
|
+
} from "./client-transport.js"
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Options for service-to-service Websocket connections.
|
|
16
|
+
*
|
|
17
|
+
* Identical to `WebsocketClientOptions` — the `headers` field is always
|
|
18
|
+
* available on the base options. This alias exists for API clarity:
|
|
19
|
+
* importing `ServiceWebsocketClientOptions` from `./server` signals
|
|
20
|
+
* intent and pairs with `createServiceWebsocketClient`.
|
|
21
|
+
*/
|
|
22
|
+
export type ServiceWebsocketClientOptions = WebsocketClientOptions
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create a Websocket client transport for service-to-service connections.
|
|
26
|
+
*
|
|
27
|
+
* This factory is for backend environments (Bun, Node.js) where you need
|
|
28
|
+
* to pass authentication headers during the Websocket upgrade.
|
|
29
|
+
*
|
|
30
|
+
* Note: Headers are a Bun/Node-specific extension. The browser WebSocket API
|
|
31
|
+
* does not support custom headers. For browser clients, use
|
|
32
|
+
* `createWebsocketClient()` and authenticate via URL query parameters.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* import { createServiceWebsocketClient } from "@kyneta/websocket-transport/server"
|
|
37
|
+
*
|
|
38
|
+
* const exchange = new Exchange({
|
|
39
|
+
* transports: [createServiceWebsocketClient({
|
|
40
|
+
* url: "ws://primary-server:3000/ws",
|
|
41
|
+
* WebSocket,
|
|
42
|
+
* headers: { Authorization: "Bearer token" },
|
|
43
|
+
* reconnect: { enabled: true },
|
|
44
|
+
* })],
|
|
45
|
+
* })
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function createServiceWebsocketClient(
|
|
49
|
+
options: ServiceWebsocketClientOptions,
|
|
50
|
+
): TransportFactory {
|
|
51
|
+
return () => new WebsocketClientTransport(options)
|
|
52
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// types — framework-agnostic Websocket abstractions for @kyneta/websocket-
|
|
1
|
+
// types — framework-agnostic Websocket abstractions for @kyneta/websocket-transport.
|
|
2
2
|
//
|
|
3
3
|
// The `Socket` interface decouples the adapter from any specific Websocket
|
|
4
4
|
// library (browser WebSocket, Node `ws`, Bun ServerWebSocket). Platform-
|
|
@@ -15,7 +15,75 @@ import type {
|
|
|
15
15
|
} from "@kyneta/transport"
|
|
16
16
|
|
|
17
17
|
// ---------------------------------------------------------------------------
|
|
18
|
-
//
|
|
18
|
+
// WebSocket readyState constants (spec values, no global dependency)
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* WebSocket readyState constants per the WHATWG WebSocket spec.
|
|
23
|
+
* Replaces references to `WebSocket.CONNECTING`, `WebSocket.OPEN`, etc.
|
|
24
|
+
* so that shared code never depends on the browser global.
|
|
25
|
+
*/
|
|
26
|
+
export const READY_STATE = {
|
|
27
|
+
CONNECTING: 0,
|
|
28
|
+
OPEN: 1,
|
|
29
|
+
CLOSING: 2,
|
|
30
|
+
CLOSED: 3,
|
|
31
|
+
} as const
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Structural event types (replace DOM MessageEvent / CloseEvent)
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
/** Minimal message event — only the fields the transport accesses. */
|
|
38
|
+
export interface WebSocketMessageEvent {
|
|
39
|
+
readonly data: string | ArrayBuffer
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Minimal close event — only the fields the transport accesses. */
|
|
43
|
+
export interface WebSocketCloseEvent {
|
|
44
|
+
readonly code: number
|
|
45
|
+
readonly reason: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// WebSocket instance and constructor structural types
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Structural type for a constructed WebSocket instance.
|
|
54
|
+
*
|
|
55
|
+
* Covers the browser's `WebSocket`, the `ws` library's `WebSocket`,
|
|
56
|
+
* and Bun's client `WebSocket` — all satisfy this interface without casting.
|
|
57
|
+
*
|
|
58
|
+
* The client transport uses `addEventListener`/`removeEventListener` for
|
|
59
|
+
* one-shot connection handlers with explicit cleanup during the connect
|
|
60
|
+
* phase. This is why `WebSocketLike` exists alongside the server-side
|
|
61
|
+
* `Socket` interface (which uses single-callback registration).
|
|
62
|
+
*/
|
|
63
|
+
export interface WebSocketLike {
|
|
64
|
+
readonly readyState: number
|
|
65
|
+
binaryType: string
|
|
66
|
+
send(data: string | ArrayBuffer): void
|
|
67
|
+
close(code?: number, reason?: string): void
|
|
68
|
+
addEventListener(type: string, listener: (event: any) => void): void
|
|
69
|
+
removeEventListener(type: string, listener: (event: any) => void): void
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Structural type for a WebSocket constructor.
|
|
74
|
+
*
|
|
75
|
+
* Type safety for constructor arguments is intentionally at the options
|
|
76
|
+
* layer (`WebsocketClientOptions.headers`), not here. The `...rest: any[]`
|
|
77
|
+
* absorbs both the browser's `protocols` arg and backend's `{ headers }`
|
|
78
|
+
* arg without requiring the transport to know which runtime it's in.
|
|
79
|
+
*/
|
|
80
|
+
export type WebSocketConstructor = new (
|
|
81
|
+
url: string,
|
|
82
|
+
...rest: any[]
|
|
83
|
+
) => WebSocketLike
|
|
84
|
+
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Socket ready states (string enum for server-side Socket interface)
|
|
19
87
|
// ---------------------------------------------------------------------------
|
|
20
88
|
|
|
21
89
|
/**
|
|
@@ -205,13 +273,13 @@ export function wrapStandardWebsocket(ws: WebSocket): Socket {
|
|
|
205
273
|
|
|
206
274
|
get readyState(): SocketReadyState {
|
|
207
275
|
switch (ws.readyState) {
|
|
208
|
-
case
|
|
276
|
+
case READY_STATE.CONNECTING:
|
|
209
277
|
return "connecting"
|
|
210
|
-
case
|
|
278
|
+
case READY_STATE.OPEN:
|
|
211
279
|
return "open"
|
|
212
|
-
case
|
|
280
|
+
case READY_STATE.CLOSING:
|
|
213
281
|
return "closing"
|
|
214
|
-
case
|
|
282
|
+
case READY_STATE.CLOSED:
|
|
215
283
|
return "closed"
|
|
216
284
|
default:
|
|
217
285
|
return "closed"
|
package/dist/chunk-PSG3LLT5.js
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
// src/types.ts
|
|
2
|
-
function wrapStandardWebsocket(ws) {
|
|
3
|
-
return {
|
|
4
|
-
send(data) {
|
|
5
|
-
ws.send(
|
|
6
|
-
typeof data === "string" ? data : data
|
|
7
|
-
);
|
|
8
|
-
},
|
|
9
|
-
close(code, reason) {
|
|
10
|
-
ws.close(code, reason);
|
|
11
|
-
},
|
|
12
|
-
onMessage(handler) {
|
|
13
|
-
ws.addEventListener("message", (event) => {
|
|
14
|
-
if (event.data instanceof ArrayBuffer) {
|
|
15
|
-
handler(new Uint8Array(event.data));
|
|
16
|
-
} else if (typeof Blob !== "undefined" && event.data instanceof Blob) {
|
|
17
|
-
event.data.arrayBuffer().then((buffer) => {
|
|
18
|
-
handler(new Uint8Array(buffer));
|
|
19
|
-
});
|
|
20
|
-
} else {
|
|
21
|
-
handler(event.data);
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
},
|
|
25
|
-
onClose(handler) {
|
|
26
|
-
ws.addEventListener("close", (event) => {
|
|
27
|
-
handler(event.code, event.reason);
|
|
28
|
-
});
|
|
29
|
-
},
|
|
30
|
-
onError(handler) {
|
|
31
|
-
ws.addEventListener("error", (_event) => {
|
|
32
|
-
handler(new Error("WebSocket error"));
|
|
33
|
-
});
|
|
34
|
-
},
|
|
35
|
-
get readyState() {
|
|
36
|
-
switch (ws.readyState) {
|
|
37
|
-
case WebSocket.CONNECTING:
|
|
38
|
-
return "connecting";
|
|
39
|
-
case WebSocket.OPEN:
|
|
40
|
-
return "open";
|
|
41
|
-
case WebSocket.CLOSING:
|
|
42
|
-
return "closing";
|
|
43
|
-
case WebSocket.CLOSED:
|
|
44
|
-
return "closed";
|
|
45
|
-
default:
|
|
46
|
-
return "closed";
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
function wrapNodeWebsocket(ws) {
|
|
52
|
-
const CONNECTING = 0;
|
|
53
|
-
const OPEN = 1;
|
|
54
|
-
const CLOSING = 2;
|
|
55
|
-
return {
|
|
56
|
-
send(data) {
|
|
57
|
-
ws.send(data);
|
|
58
|
-
},
|
|
59
|
-
close(code, reason) {
|
|
60
|
-
ws.close(code, reason);
|
|
61
|
-
},
|
|
62
|
-
onMessage(handler) {
|
|
63
|
-
ws.on(
|
|
64
|
-
"message",
|
|
65
|
-
(data, isBinary) => {
|
|
66
|
-
if (isBinary) {
|
|
67
|
-
if (data instanceof ArrayBuffer) {
|
|
68
|
-
handler(new Uint8Array(data));
|
|
69
|
-
} else if (typeof Buffer !== "undefined" && Buffer.isBuffer(data)) {
|
|
70
|
-
handler(new Uint8Array(data));
|
|
71
|
-
} else {
|
|
72
|
-
handler(new Uint8Array(data));
|
|
73
|
-
}
|
|
74
|
-
} else {
|
|
75
|
-
if (typeof Buffer !== "undefined" && Buffer.isBuffer(data)) {
|
|
76
|
-
handler(data.toString("utf-8"));
|
|
77
|
-
} else {
|
|
78
|
-
handler(data);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
);
|
|
83
|
-
},
|
|
84
|
-
onClose(handler) {
|
|
85
|
-
ws.on("close", (code, reason) => {
|
|
86
|
-
handler(code, reason.toString());
|
|
87
|
-
});
|
|
88
|
-
},
|
|
89
|
-
onError(handler) {
|
|
90
|
-
ws.on("error", handler);
|
|
91
|
-
},
|
|
92
|
-
get readyState() {
|
|
93
|
-
switch (ws.readyState) {
|
|
94
|
-
case CONNECTING:
|
|
95
|
-
return "connecting";
|
|
96
|
-
case OPEN:
|
|
97
|
-
return "open";
|
|
98
|
-
case CLOSING:
|
|
99
|
-
return "closing";
|
|
100
|
-
default:
|
|
101
|
-
return "closed";
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export {
|
|
108
|
-
wrapStandardWebsocket,
|
|
109
|
-
wrapNodeWebsocket
|
|
110
|
-
};
|
|
111
|
-
//# sourceMappingURL=chunk-PSG3LLT5.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["// types — framework-agnostic Websocket abstractions for @kyneta/websocket-network-adapter.\n//\n// The `Socket` interface decouples the adapter from any specific Websocket\n// library (browser WebSocket, Node `ws`, Bun ServerWebSocket). Platform-\n// specific wrappers (`wrapStandardWebsocket`, `wrapNodeWebsocket`,\n// `wrapBunWebsocket`) adapt concrete implementations to this interface.\n//\n// Ported from @loro-extended/adapter-websocket's WsSocket with kyneta\n// naming conventions applied.\n\nimport type {\n TransitionListener as GenericTransitionListener,\n PeerId,\n StateTransition,\n} from \"@kyneta/transport\"\n\n// ---------------------------------------------------------------------------\n// Socket ready states\n// ---------------------------------------------------------------------------\n\n/**\n * Websocket ready states — mirrors the standard WebSocket readyState\n * values as human-readable strings.\n */\nexport type SocketReadyState = \"connecting\" | \"open\" | \"closing\" | \"closed\"\n\n// ---------------------------------------------------------------------------\n// Socket interface\n// ---------------------------------------------------------------------------\n\n/**\n * Framework-agnostic Websocket interface.\n *\n * This allows the adapter to work with any Websocket library:\n * - Browser `WebSocket` via `wrapStandardWebsocket()`\n * - Node.js `ws` library via `wrapNodeWebsocket()`\n * - Bun `ServerWebSocket` via `wrapBunWebsocket()`\n *\n * The interface is intentionally minimal — only the operations the\n * adapter needs are exposed.\n */\nexport interface Socket {\n /** Send binary or text data through the Websocket. */\n send(data: Uint8Array | string): void\n\n /** Close the Websocket connection. */\n close(code?: number, reason?: string): void\n\n /** Register a handler for incoming messages (binary or text). */\n onMessage(handler: (data: Uint8Array | string) => void): void\n\n /** Register a handler for connection close. */\n onClose(handler: (code: number, reason: string) => void): void\n\n /** Register a handler for errors. */\n onError(handler: (error: Error) => void): void\n\n /** The current ready state of the Websocket. */\n readonly readyState: SocketReadyState\n}\n\n// ---------------------------------------------------------------------------\n// Connection types — used by server adapter\n// ---------------------------------------------------------------------------\n\n/**\n * Options for handling a new Websocket connection on the server.\n */\nexport interface WebsocketConnectionOptions {\n /** The Websocket instance, wrapped in the Socket interface. */\n socket: Socket\n\n /** Optional peer ID extracted from the upgrade request. */\n peerId?: PeerId\n\n /** Optional authentication token from the upgrade request. */\n authToken?: string\n}\n\n/**\n * Handle for an active Websocket connection.\n */\nexport interface WebsocketConnectionHandle {\n /** The peer ID for this connection. */\n readonly peerId: PeerId\n\n /** The channel ID for this connection. */\n readonly channelId: number\n\n /** Close the connection. */\n close(code?: number, reason?: string): void\n}\n\n/**\n * Result of handling a Websocket connection on the server.\n */\nexport interface WebsocketConnectionResult {\n /** The connection handle for managing this peer. */\n connection: WebsocketConnectionHandle\n\n /** Call this to start processing messages. */\n start(): void\n}\n\n// ---------------------------------------------------------------------------\n// Disconnect reason\n// ---------------------------------------------------------------------------\n\n/**\n * Discriminated union describing why a Websocket connection was lost.\n */\nexport type DisconnectReason =\n | { type: \"intentional\" }\n | { type: \"error\"; error: Error }\n | { type: \"closed\"; code: number; reason: string }\n | { type: \"max-retries-exceeded\"; attempts: number }\n | { type: \"not-started\" }\n\n// ---------------------------------------------------------------------------\n// Connection state (for client adapter observability)\n// ---------------------------------------------------------------------------\n\n/**\n * All possible states of the Websocket client.\n *\n * State machine transitions:\n * ```\n * disconnected → connecting → connected → ready\n * ↓ ↓ ↓\n * reconnecting ← ─ ┴ ─ ─ ─ ─ ┘\n * ↓\n * connecting (retry)\n * ↓\n * disconnected (max retries)\n * ```\n */\nexport type WebsocketClientState =\n | { status: \"disconnected\"; reason?: DisconnectReason }\n | { status: \"connecting\"; attempt: number }\n | { status: \"connected\" }\n | { status: \"ready\" }\n | { status: \"reconnecting\"; attempt: number; nextAttemptMs: number }\n\n/**\n * A state transition event for websocket client states.\n * Specialized from the generic `StateTransition<S>`.\n */\nexport type WebsocketClientStateTransition =\n StateTransition<WebsocketClientState>\n\n/**\n * Listener for websocket client state transitions.\n * Specialized from the generic `TransitionListener<S>`.\n */\nexport type TransitionListener = GenericTransitionListener<WebsocketClientState>\n\n// ---------------------------------------------------------------------------\n// Socket wrapper — standard WebSocket API (browser + Node ws)\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap a standard `WebSocket` (browser or Node.js `ws` via `ws` package\n * in `WebSocket`-compatible mode) into the `Socket` interface.\n *\n * Handles `ArrayBuffer`, `Blob`, and string messages.\n */\nexport function wrapStandardWebsocket(ws: WebSocket): Socket {\n return {\n send(data: Uint8Array | string): void {\n ws.send(\n typeof data === \"string\" ? data : (data as Uint8Array<ArrayBuffer>),\n )\n },\n\n close(code?: number, reason?: string): void {\n ws.close(code, reason)\n },\n\n onMessage(handler: (data: Uint8Array | string) => void): void {\n ws.addEventListener(\"message\", event => {\n if (event.data instanceof ArrayBuffer) {\n handler(new Uint8Array(event.data))\n } else if (typeof Blob !== \"undefined\" && event.data instanceof Blob) {\n // Handle Blob data (browser)\n event.data.arrayBuffer().then(buffer => {\n handler(new Uint8Array(buffer))\n })\n } else {\n handler(event.data as string)\n }\n })\n },\n\n onClose(handler: (code: number, reason: string) => void): void {\n ws.addEventListener(\"close\", event => {\n handler(event.code, event.reason)\n })\n },\n\n onError(handler: (error: Error) => void): void {\n ws.addEventListener(\"error\", _event => {\n handler(new Error(\"WebSocket error\"))\n })\n },\n\n get readyState(): SocketReadyState {\n switch (ws.readyState) {\n case WebSocket.CONNECTING:\n return \"connecting\"\n case WebSocket.OPEN:\n return \"open\"\n case WebSocket.CLOSING:\n return \"closing\"\n case WebSocket.CLOSED:\n return \"closed\"\n default:\n return \"closed\"\n }\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Socket wrapper — Node.js `ws` library (raw API, not WebSocket-compat)\n// ---------------------------------------------------------------------------\n\n/**\n * The minimal interface we need from the Node.js `ws` library's `WebSocket`.\n *\n * Using a structural type rather than importing `ws` — consumers provide\n * the actual `ws` instance, we just need these methods.\n */\nexport interface NodeWebsocketLike {\n send(data: Uint8Array | string): void\n close(code?: number, reason?: string): void\n on(\n event: \"message\",\n handler: (data: Buffer | ArrayBuffer | string, isBinary: boolean) => void,\n ): void\n on(event: \"close\", handler: (code: number, reason: Buffer) => void): void\n on(event: \"error\", handler: (error: Error) => void): void\n readyState: number\n}\n\n/**\n * Wrap a Node.js `ws` library WebSocket into the `Socket` interface.\n *\n * Handles `Buffer` → `Uint8Array` conversion for binary messages.\n */\nexport function wrapNodeWebsocket(ws: NodeWebsocketLike): Socket {\n const CONNECTING = 0\n const OPEN = 1\n const CLOSING = 2\n\n return {\n send(data: Uint8Array | string): void {\n ws.send(data)\n },\n\n close(code?: number, reason?: string): void {\n ws.close(code, reason)\n },\n\n onMessage(handler: (data: Uint8Array | string) => void): void {\n ws.on(\n \"message\",\n (data: Buffer | ArrayBuffer | string, isBinary: boolean) => {\n if (isBinary) {\n if (data instanceof ArrayBuffer) {\n handler(new Uint8Array(data))\n } else if (typeof Buffer !== \"undefined\" && Buffer.isBuffer(data)) {\n handler(new Uint8Array(data))\n } else {\n handler(new Uint8Array(data as unknown as ArrayBuffer))\n }\n } else {\n if (typeof Buffer !== \"undefined\" && Buffer.isBuffer(data)) {\n handler(data.toString(\"utf-8\"))\n } else {\n handler(data as string)\n }\n }\n },\n )\n },\n\n onClose(handler: (code: number, reason: string) => void): void {\n ws.on(\"close\", (code: number, reason: Buffer) => {\n handler(code, reason.toString())\n })\n },\n\n onError(handler: (error: Error) => void): void {\n ws.on(\"error\", handler)\n },\n\n get readyState(): SocketReadyState {\n switch (ws.readyState) {\n case CONNECTING:\n return \"connecting\"\n case OPEN:\n return \"open\"\n case CLOSING:\n return \"closing\"\n default:\n return \"closed\"\n }\n },\n }\n}\n"],"mappings":";AAsKO,SAAS,sBAAsB,IAAuB;AAC3D,SAAO;AAAA,IACL,KAAK,MAAiC;AACpC,SAAG;AAAA,QACD,OAAO,SAAS,WAAW,OAAQ;AAAA,MACrC;AAAA,IACF;AAAA,IAEA,MAAM,MAAe,QAAuB;AAC1C,SAAG,MAAM,MAAM,MAAM;AAAA,IACvB;AAAA,IAEA,UAAU,SAAoD;AAC5D,SAAG,iBAAiB,WAAW,WAAS;AACtC,YAAI,MAAM,gBAAgB,aAAa;AACrC,kBAAQ,IAAI,WAAW,MAAM,IAAI,CAAC;AAAA,QACpC,WAAW,OAAO,SAAS,eAAe,MAAM,gBAAgB,MAAM;AAEpE,gBAAM,KAAK,YAAY,EAAE,KAAK,YAAU;AACtC,oBAAQ,IAAI,WAAW,MAAM,CAAC;AAAA,UAChC,CAAC;AAAA,QACH,OAAO;AACL,kBAAQ,MAAM,IAAc;AAAA,QAC9B;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,QAAQ,SAAuD;AAC7D,SAAG,iBAAiB,SAAS,WAAS;AACpC,gBAAQ,MAAM,MAAM,MAAM,MAAM;AAAA,MAClC,CAAC;AAAA,IACH;AAAA,IAEA,QAAQ,SAAuC;AAC7C,SAAG,iBAAiB,SAAS,YAAU;AACrC,gBAAQ,IAAI,MAAM,iBAAiB,CAAC;AAAA,MACtC,CAAC;AAAA,IACH;AAAA,IAEA,IAAI,aAA+B;AACjC,cAAQ,GAAG,YAAY;AAAA,QACrB,KAAK,UAAU;AACb,iBAAO;AAAA,QACT,KAAK,UAAU;AACb,iBAAO;AAAA,QACT,KAAK,UAAU;AACb,iBAAO;AAAA,QACT,KAAK,UAAU;AACb,iBAAO;AAAA,QACT;AACE,iBAAO;AAAA,MACX;AAAA,IACF;AAAA,EACF;AACF;AA6BO,SAAS,kBAAkB,IAA+B;AAC/D,QAAM,aAAa;AACnB,QAAM,OAAO;AACb,QAAM,UAAU;AAEhB,SAAO;AAAA,IACL,KAAK,MAAiC;AACpC,SAAG,KAAK,IAAI;AAAA,IACd;AAAA,IAEA,MAAM,MAAe,QAAuB;AAC1C,SAAG,MAAM,MAAM,MAAM;AAAA,IACvB;AAAA,IAEA,UAAU,SAAoD;AAC5D,SAAG;AAAA,QACD;AAAA,QACA,CAAC,MAAqC,aAAsB;AAC1D,cAAI,UAAU;AACZ,gBAAI,gBAAgB,aAAa;AAC/B,sBAAQ,IAAI,WAAW,IAAI,CAAC;AAAA,YAC9B,WAAW,OAAO,WAAW,eAAe,OAAO,SAAS,IAAI,GAAG;AACjE,sBAAQ,IAAI,WAAW,IAAI,CAAC;AAAA,YAC9B,OAAO;AACL,sBAAQ,IAAI,WAAW,IAA8B,CAAC;AAAA,YACxD;AAAA,UACF,OAAO;AACL,gBAAI,OAAO,WAAW,eAAe,OAAO,SAAS,IAAI,GAAG;AAC1D,sBAAQ,KAAK,SAAS,OAAO,CAAC;AAAA,YAChC,OAAO;AACL,sBAAQ,IAAc;AAAA,YACxB;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IAEA,QAAQ,SAAuD;AAC7D,SAAG,GAAG,SAAS,CAAC,MAAc,WAAmB;AAC/C,gBAAQ,MAAM,OAAO,SAAS,CAAC;AAAA,MACjC,CAAC;AAAA,IACH;AAAA,IAEA,QAAQ,SAAuC;AAC7C,SAAG,GAAG,SAAS,OAAO;AAAA,IACxB;AAAA,IAEA,IAAI,aAA+B;AACjC,cAAQ,GAAG,YAAY;AAAA,QACrB,KAAK;AACH,iBAAO;AAAA,QACT,KAAK;AACH,iBAAO;AAAA,QACT,KAAK;AACH,iBAAO;AAAA,QACT;AACE,iBAAO;AAAA,MACX;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|