transport-io 0.8.1 → 0.9.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/dist/index.js +1 -1
- package/dist/protocol.d.ts +5 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +5 -0
- package/dist/protocol.js.map +1 -1
- package/dist/transport/fails.node.d.ts +0 -6
- package/dist/transport/fails.node.d.ts.map +1 -1
- package/dist/transport/fails.node.js +0 -10
- package/dist/transport/fails.node.js.map +1 -1
- package/dist/transport/websocket-connection.d.ts +52 -0
- package/dist/transport/websocket-connection.d.ts.map +1 -0
- package/dist/transport/websocket-connection.js +236 -0
- package/dist/transport/websocket-connection.js.map +1 -0
- package/dist/transport/websocket.d.ts +1 -53
- package/dist/transport/websocket.d.ts.map +1 -1
- package/dist/transport/websocket.js +5 -176
- package/dist/transport/websocket.js.map +1 -1
- package/dist/transport/websocket.node.js +1 -1
- package/dist/transport/websocket.node.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14,5 +14,5 @@ export { createServer, Server, } from './server.js';
|
|
|
14
14
|
* `changeset version` moves the manifest, so this moves in the same commit - which the test
|
|
15
15
|
* enforces rather than trusting anyone to remember.
|
|
16
16
|
*/
|
|
17
|
-
export const VERSION = '0.
|
|
17
|
+
export const VERSION = '0.9.0';
|
|
18
18
|
//# sourceMappingURL=index.js.map
|
package/dist/protocol.d.ts
CHANGED
|
@@ -24,6 +24,10 @@ export declare const FALLBACK_UNRELIABLE_LOW_WATER = 32;
|
|
|
24
24
|
export declare const WS_CLOSE_NORMAL = 1000;
|
|
25
25
|
export declare const WS_CLOSE_OFFSET = 3000;
|
|
26
26
|
export declare const WS_CLOSE_REASON_MAX_BYTES = 123;
|
|
27
|
+
/** PROTOCOL.md §3.3 - a peer that has sent nothing for this long sends an empty message. */
|
|
28
|
+
export declare const WS_KEEPALIVE_INTERVAL_MS = 15000;
|
|
29
|
+
/** PROTOCOL.md §3.3 - a peer that has received nothing for this long closes as WT_IDLE_TIMEOUT. */
|
|
30
|
+
export declare const WS_IDLE_TIMEOUT_MS = 45000;
|
|
27
31
|
/** PROTOCOL.md §5.2 */
|
|
28
32
|
export declare const FrameType: {
|
|
29
33
|
readonly HANDSHAKE: 1;
|
|
@@ -105,6 +109,7 @@ export declare const CloseCode: {
|
|
|
105
109
|
readonly WT_HANDSHAKE_TIMEOUT: 1002;
|
|
106
110
|
readonly WT_PEER_TOO_SLOW: 1003;
|
|
107
111
|
readonly WT_PROTOCOL_ERROR: 1004;
|
|
112
|
+
readonly WT_IDLE_TIMEOUT: 1005;
|
|
108
113
|
readonly WT_RELIABILITY_REFUSED: 1006;
|
|
109
114
|
};
|
|
110
115
|
export type CloseCode = (typeof CloseCode)[keyof typeof CloseCode];
|
package/dist/protocol.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,gFAAgF;AAChF,eAAO,MAAM,gBAAgB,IAAI,CAAA;AAEjC;;;;;;;GAOG;AACH,eAAO,MAAM,6BAA6B,KAAK,CAAA;AAE/C;;;;GAIG;AACH,eAAO,MAAM,eAAe,OAAO,CAAA;AACnC,eAAO,MAAM,eAAe,OAAO,CAAA;AACnC,eAAO,MAAM,yBAAyB,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
|
package/dist/protocol.js
CHANGED
|
@@ -24,6 +24,10 @@ export const FALLBACK_UNRELIABLE_LOW_WATER = 32;
|
|
|
24
24
|
export const WS_CLOSE_NORMAL = 1000;
|
|
25
25
|
export const WS_CLOSE_OFFSET = 3000;
|
|
26
26
|
export const WS_CLOSE_REASON_MAX_BYTES = 123;
|
|
27
|
+
/** PROTOCOL.md §3.3 - a peer that has sent nothing for this long sends an empty message. */
|
|
28
|
+
export const WS_KEEPALIVE_INTERVAL_MS = 15_000;
|
|
29
|
+
/** PROTOCOL.md §3.3 - a peer that has received nothing for this long closes as WT_IDLE_TIMEOUT. */
|
|
30
|
+
export const WS_IDLE_TIMEOUT_MS = 45_000;
|
|
27
31
|
/** PROTOCOL.md §5.2 */
|
|
28
32
|
export const FrameType = {
|
|
29
33
|
HANDSHAKE: 0x01,
|
|
@@ -103,6 +107,7 @@ export const CloseCode = {
|
|
|
103
107
|
WT_HANDSHAKE_TIMEOUT: 1002,
|
|
104
108
|
WT_PEER_TOO_SLOW: 1003,
|
|
105
109
|
WT_PROTOCOL_ERROR: 1004,
|
|
110
|
+
WT_IDLE_TIMEOUT: 1005,
|
|
106
111
|
WT_RELIABILITY_REFUSED: 1006,
|
|
107
112
|
};
|
|
108
113
|
//# sourceMappingURL=protocol.js.map
|
package/dist/protocol.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,gFAAgF;AAChF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAA;AAEjC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,EAAE,CAAA;AAE/C;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAA;AACnC,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAA;AACnC,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAA;
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,gFAAgF;AAChF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAA;AAEjC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,EAAE,CAAA;AAE/C;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAA;AACnC,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAA;AACnC,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAA;AAC5C,4FAA4F;AAC5F,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAA;AAC9C,mGAAmG;AACnG,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAA;AAExC,uBAAuB;AACvB,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,SAAS,EAAE,IAAI;IACf,IAAI,EAAE,IAAI;IACV,YAAY,EAAE,IAAI;IAClB,aAAa,EAAE,IAAI;IACnB,UAAU,EAAE,IAAI;IAChB,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,IAAI;IACX,WAAW,EAAE,IAAI;IACjB,2FAA2F;IAC3F,QAAQ,EAAE,IAAI;CACN,CAAA;AAGV,MAAM,WAAW,GAAwB,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAA;AAC1E,MAAM,UAAU,WAAW,CAAC,CAAS;IACnC,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;AAC3B,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAA;AACvC,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAA;AAEtC,6FAA6F;AAC7F,MAAM,CAAC,MAAM,KAAK,GAAG,EAAE,IAAI,EAAE,IAAI,EAAW,CAAA;AAG5C,4FAA4F;AAC5F,MAAM,CAAC,MAAM,uBAAuB,GAAG,UAAU,CAAA;AAEjD,oDAAoD;AACpD,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAA;AACpC,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAA;AACpC,MAAM,CAAC,MAAM,2BAA2B,GAAW,mBAAmB,GAAG,mBAAmB,CAAA;AAE5F,kFAAkF;AAClF,MAAM,CAAC,MAAM,UAAU,GAAW,mBAAmB,GAAG,CAAC,CAAA;AACzD,MAAM,CAAC,MAAM,sBAAsB,GAAG,SAAS,CAAA;AAC/C,MAAM,CAAC,MAAM,sBAAsB,GAAG,UAAU,CAAA;AAEhD,gDAAgD;AAChD,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAA;AACvC,8EAA8E;AAC9E,MAAM,CAAC,MAAM,2BAA2B,GAAG,IAAI,CAAA;AAC/C,MAAM,CAAC,MAAM,iCAAiC,GAC5C,2BAA2B,GAAG,qBAAqB,CAAA;AAErD,iEAAiE;AACjE,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAA;AACpC,MAAM,CAAC,MAAM,cAAc,GAAG,GAAG,CAAA;AACjC,wFAAwF;AACxF,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,CAAA;AAC9C,sFAAsF;AACtF,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAA;AAElC,6FAA6F;AAC7F,MAAM,CAAC,MAAM,2BAA2B,GAAG,MAAM,CAAA;AACjD,yFAAyF;AACzF,MAAM,CAAC,MAAM,oBAAoB,GAAG,OAAO,CAAA;AAC3C,wFAAwF;AACxF,MAAM,CAAC,MAAM,0BAA0B,GAAG,OAAO,CAAA;AACjD,qEAAqE;AACrE,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAA;AAErC,uBAAuB;AACvB,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAA;AACzC,wBAAwB;AACxB,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,CAAA;AAE1C,6FAA6F;AAC7F,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,WAAW,EAAE,CAAC;IACd,UAAU,EAAE,CAAC;IACb,mBAAmB,EAAE,CAAC;CACd,CAAA;AAGV,wBAAwB;AACxB,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,WAAW,EAAE,CAAC;IACd,4BAA4B,EAAE,IAAI;IAClC,oBAAoB,EAAE,IAAI;IAC1B,oBAAoB,EAAE,IAAI;IAC1B,gBAAgB,EAAE,IAAI;IACtB,iBAAiB,EAAE,IAAI;IACvB,eAAe,EAAE,IAAI;IACrB,sBAAsB,EAAE,IAAI;CACpB,CAAA"}
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { Client, type ClientOptions } from '../client.ts';
|
|
2
2
|
import type { AnyMap, Registered } from '../contract.ts';
|
|
3
3
|
import type { Connection } from './types.ts';
|
|
4
|
-
/**
|
|
5
|
-
* The reset code is only recoverable from the message text, because the error type omits
|
|
6
|
-
* the field the specification defines. Parsing lives in exactly this one function, with a
|
|
7
|
-
* test pinning the observed format, so the upstream defect stays contained.
|
|
8
|
-
*/
|
|
9
|
-
export declare function resetCodeFromError(error: unknown): number | undefined;
|
|
10
4
|
export interface Http3ServerOptions {
|
|
11
5
|
readonly port: number;
|
|
12
6
|
readonly host?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fails.node.d.ts","sourceRoot":"","sources":["../../src/transport/fails.node.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAIxD,OAAO,KAAK,EAAyB,UAAU,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"fails.node.d.ts","sourceRoot":"","sources":["../../src/transport/fails.node.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAIxD,OAAO,KAAK,EAAyB,UAAU,EAAE,MAAM,YAAY,CAAA;AAqHnE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,IAAI,aAAa,CAAC,UAAU,CAAC,CAAA;IACrC,IAAI,IAAI,IAAI,CAAA;CACb;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC,CAiClF;AAED;;;;;;GAMG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,aAAa,CAAC,CAYxD;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,eAAe,EAAE,UAAU,CAAA;IACpC,+FAA+F;IAC/F,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;CAChC;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,UAAU,CAAC,CAiCjF;AAED,sFAAsF;AACtF,MAAM,WAAW,kBACf,SAAQ,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,EACpC,mBAAmB;CAAG;AAE1B;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAAC,CAAC,SAAS,MAAM,GAAG,UAAU,EAC7D,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAIpB"}
|
|
@@ -18,16 +18,6 @@ import { Client } from '../client.js';
|
|
|
18
18
|
import { TransportError } from '../errors.js';
|
|
19
19
|
import { DATAGRAM_CONSERVATIVE_FLOOR } from '../protocol.js';
|
|
20
20
|
import { handshakeFailure, probe, probeTarget } from './probe.js';
|
|
21
|
-
/**
|
|
22
|
-
* The reset code is only recoverable from the message text, because the error type omits
|
|
23
|
-
* the field the specification defines. Parsing lives in exactly this one function, with a
|
|
24
|
-
* test pinning the observed format, so the upstream defect stays contained.
|
|
25
|
-
*/
|
|
26
|
-
export function resetCodeFromError(error) {
|
|
27
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
28
|
-
const m = /code:\s*(\d+)/i.exec(message);
|
|
29
|
-
return m?.[1] === undefined ? undefined : Number(m[1]);
|
|
30
|
-
}
|
|
31
21
|
class FailsConnection {
|
|
32
22
|
#session;
|
|
33
23
|
closed;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fails.node.js","sourceRoot":"","sources":["../../src/transport/fails.node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AACxF,OAAO,EAAE,MAAM,EAAsB,MAAM,cAAc,CAAA;AAEzD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAAE,2BAA2B,EAAE,MAAM,gBAAgB,CAAA;AAC5D,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAmBjE
|
|
1
|
+
{"version":3,"file":"fails.node.js","sourceRoot":"","sources":["../../src/transport/fails.node.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { BidiStream, CloseInfo, Connection } from './types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* What this needs from a socket: the WHATWG surface, which browsers, Node and `ws` share.
|
|
4
|
+
* `readyState` 1 is open on every one of them.
|
|
5
|
+
*/
|
|
6
|
+
export interface SocketLike {
|
|
7
|
+
binaryType: string;
|
|
8
|
+
readonly bufferedAmount: number;
|
|
9
|
+
readonly readyState: number;
|
|
10
|
+
send(data: Uint8Array): void;
|
|
11
|
+
close(code?: number, reason?: string): void;
|
|
12
|
+
addEventListener(type: 'message', listener: (ev: {
|
|
13
|
+
readonly data: unknown;
|
|
14
|
+
}) => void): void;
|
|
15
|
+
addEventListener(type: 'close', listener: (ev: {
|
|
16
|
+
readonly code: number;
|
|
17
|
+
readonly reason: string;
|
|
18
|
+
}) => void): void;
|
|
19
|
+
addEventListener(type: 'error', listener: () => void): void;
|
|
20
|
+
addEventListener(type: 'open', listener: () => void): void;
|
|
21
|
+
}
|
|
22
|
+
export interface WebSocketConnectionOptions {
|
|
23
|
+
/** For the test that proves the bound; production never sets it. */
|
|
24
|
+
readonly lowWaterBytes?: number;
|
|
25
|
+
/** For the tests that prove the deadline; production never sets them. */
|
|
26
|
+
readonly keepaliveIntervalMs?: number;
|
|
27
|
+
readonly idleTimeoutMs?: number;
|
|
28
|
+
}
|
|
29
|
+
export declare class WebSocketConnection implements Connection {
|
|
30
|
+
#private;
|
|
31
|
+
readonly closed: Promise<CloseInfo>;
|
|
32
|
+
constructor(socket: SocketLike, opts?: WebSocketConnectionOptions);
|
|
33
|
+
openEmitStream(): Promise<WritableStream<Uint8Array>>;
|
|
34
|
+
/** The socket is the peer's emit stream, so it is delivered at once rather than awaited. */
|
|
35
|
+
onEmitStream(cb: (readable: ReadableStream<Uint8Array>) => void): void;
|
|
36
|
+
openBidi(): Promise<BidiStream>;
|
|
37
|
+
onBidi(): void;
|
|
38
|
+
sendDatagram(): void;
|
|
39
|
+
onDatagram(): void;
|
|
40
|
+
/** The same ceiling as the native transport, so an application's limit never moves with it. */
|
|
41
|
+
maxDatagramSize(): number;
|
|
42
|
+
reliability(): 'reliable-only';
|
|
43
|
+
kind(): 'websocket';
|
|
44
|
+
close(code: number, reason: string): void;
|
|
45
|
+
}
|
|
46
|
+
/** §3.3: `WT_NO_ERROR` is a normal closure; every other session code rides in the private range. */
|
|
47
|
+
export declare function toWebSocketCloseCode(code: number): number;
|
|
48
|
+
/** The inverse, with the socket's own codes (a lost connection is 1006) passed through as they are. */
|
|
49
|
+
export declare function fromWebSocketCloseCode(code: number): number;
|
|
50
|
+
/** Whole characters only, so a cut never leaves half a code point on the wire. */
|
|
51
|
+
export declare function truncateCloseReason(reason: string): string;
|
|
52
|
+
//# sourceMappingURL=websocket-connection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket-connection.d.ts","sourceRoot":"","sources":["../../src/transport/websocket-connection.ts"],"names":[],"mappings":"AAqCA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEnE;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAA;IAC5B,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3C,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,GAAG,IAAI,CAAA;IAC3F,gBAAgB,CACd,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,CAAC,EAAE,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GACzE,IAAI,CAAA;IACP,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;IAC3D,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;CAC3D;AAcD,MAAM,WAAW,0BAA0B;IACzC,oEAAoE;IACpE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B,yEAAyE;IACzE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;IACrC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAChC;AAED,qBAAa,mBAAoB,YAAW,UAAU;;IAYpD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,SAAS,CAAC,CAAA;IAEnC,YAAY,MAAM,EAAE,UAAU,EAAE,IAAI,GAAE,0BAA+B,EAwCpE;IAEK,cAAc,IAAI,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CA0B1D;IAED,4FAA4F;IAC5F,YAAY,CAAC,EAAE,EAAE,CAAC,QAAQ,EAAE,cAAc,CAAC,UAAU,CAAC,KAAK,IAAI,GAAG,IAAI,CAErE;IAED,QAAQ,IAAI,OAAO,CAAC,UAAU,CAAC,CAE9B;IAED,MAAM,IAAI,IAAI,CAEb;IAED,YAAY,IAAI,IAAI,CAQnB;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,+FAA+F;IAC/F,eAAe,IAAI,MAAM,CAExB;IAED,WAAW,IAAI,eAAe,CAE7B;IAED,IAAI,IAAI,WAAW,CAElB;IAED,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAexC;CAkCF;AAkBD,oGAAoG;AACpG,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,uGAAuG;AACvG,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK3D;AAED,kFAAkF;AAClF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAS1D"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The emit lane over a WebSocket: the mapping, PROTOCOL.md §3.3. Internal (D127):
|
|
3
|
+
* `transport-io/websocket-transport` exports the connector alone and
|
|
4
|
+
* `transport-io/websocket-node-transport` the listener, and this class is what both hand to
|
|
5
|
+
* a session. The transport seam it implements is not exported either (D21).
|
|
6
|
+
*
|
|
7
|
+
* A WebSocket is one ordered, reliable pipe per direction, which is what the emit lane
|
|
8
|
+
* already is (D32), so this Connection is the socket itself: `openEmitStream` writes to it,
|
|
9
|
+
* `onEmitStream` reads from it, and the framer above runs untouched, treating each message
|
|
10
|
+
* as bytes of the same stream. Everything else on the seam refuses. There are no
|
|
11
|
+
* bidirectional streams, so a session on this transport carries no calls and no `stream()`,
|
|
12
|
+
* and there are no datagrams: an unreliable event whose contract declared a fallback travels
|
|
13
|
+
* as a `DATAGRAM` frame on the emit lane instead, wrapped and unwrapped by the session (D122).
|
|
14
|
+
*
|
|
15
|
+
* What the socket does not give for free is write completion. A browser exposes only
|
|
16
|
+
* `bufferedAmount`, with no event when it drains, so the sink polls it before resolving a
|
|
17
|
+
* write. Without that every write would resolve at once, and the emit queue's 256-frame
|
|
18
|
+
* bound would measure nothing: `writer.ready` on the reference binding in a new costume
|
|
19
|
+
* (D93). `websocket.node.test.ts` reaches the bound on a real socket with the peer paused,
|
|
20
|
+
* and shows it unreachable with the polling off.
|
|
21
|
+
*
|
|
22
|
+
* Nor does it give liveness. TCP reports a dead path late or never, and a WebSocket has no
|
|
23
|
+
* idle timeout of its own, so the mapping carries one (D126): an empty message after
|
|
24
|
+
* `WS_KEEPALIVE_INTERVAL_MS` with nothing sent, consumed by the receiver as no bytes at all,
|
|
25
|
+
* and a close as `WT_IDLE_TIMEOUT` after `WS_IDLE_TIMEOUT_MS` with nothing received.
|
|
26
|
+
*/
|
|
27
|
+
import { TransportError } from '../errors.js';
|
|
28
|
+
import { CloseCode, DATAGRAM_CONSERVATIVE_FLOOR, WS_CLOSE_NORMAL, WS_CLOSE_OFFSET, WS_CLOSE_REASON_MAX_BYTES, WS_IDLE_TIMEOUT_MS, WS_KEEPALIVE_INTERVAL_MS, } from '../protocol.js';
|
|
29
|
+
import { OwnedTimers } from '../timers.js';
|
|
30
|
+
const OPEN = 1;
|
|
31
|
+
/**
|
|
32
|
+
* Bytes a write may leave queued in the socket and still count as complete. Absolute. Above
|
|
33
|
+
* it the write parks, which is what lets the emit queue fill and its bound fire.
|
|
34
|
+
*/
|
|
35
|
+
const WS_SEND_LOW_WATER_BYTES = 65_536;
|
|
36
|
+
/** How long a parked write waits between looks at `bufferedAmount`. */
|
|
37
|
+
const POLL_MS = 4;
|
|
38
|
+
/** §3.3: the keepalive is an empty message, which carries no bytes of the stream. */
|
|
39
|
+
const KEEPALIVE = new Uint8Array(0);
|
|
40
|
+
export class WebSocketConnection {
|
|
41
|
+
#socket;
|
|
42
|
+
#lowWater;
|
|
43
|
+
#keepaliveMs;
|
|
44
|
+
#idleMs;
|
|
45
|
+
#timers = new OwnedTimers();
|
|
46
|
+
#idleTimer;
|
|
47
|
+
#sentAt = Date.now();
|
|
48
|
+
#readable;
|
|
49
|
+
#inbound;
|
|
50
|
+
#closing = false;
|
|
51
|
+
#settleClosed;
|
|
52
|
+
closed;
|
|
53
|
+
constructor(socket, opts = {}) {
|
|
54
|
+
this.#socket = socket;
|
|
55
|
+
this.#lowWater = opts.lowWaterBytes ?? WS_SEND_LOW_WATER_BYTES;
|
|
56
|
+
this.#keepaliveMs = opts.keepaliveIntervalMs ?? WS_KEEPALIVE_INTERVAL_MS;
|
|
57
|
+
this.#idleMs = opts.idleTimeoutMs ?? WS_IDLE_TIMEOUT_MS;
|
|
58
|
+
socket.binaryType = 'arraybuffer';
|
|
59
|
+
this.#readable = new ReadableStream({
|
|
60
|
+
start: (controller) => {
|
|
61
|
+
this.#inbound = controller;
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
this.closed = new Promise((resolve) => {
|
|
65
|
+
this.#settleClosed = resolve;
|
|
66
|
+
});
|
|
67
|
+
socket.addEventListener('close', (ev) => {
|
|
68
|
+
this.#closing = true;
|
|
69
|
+
this.#timers.clearAll();
|
|
70
|
+
this.#endInbound();
|
|
71
|
+
this.#settleClosed({ code: fromWebSocketCloseCode(ev.code), reason: ev.reason });
|
|
72
|
+
});
|
|
73
|
+
socket.addEventListener('error', () => {
|
|
74
|
+
// A close event follows every error, and that is the one report `closed` makes.
|
|
75
|
+
});
|
|
76
|
+
socket.addEventListener('message', (ev) => {
|
|
77
|
+
const bytes = toBytes(ev.data);
|
|
78
|
+
if (bytes === undefined) {
|
|
79
|
+
// §3.3: binary messages only. A text frame is not a framing accident, it is another
|
|
80
|
+
// protocol, and the lane cannot recover from it any more than from a bad length.
|
|
81
|
+
this.close(CloseCode.WT_PROTOCOL_ERROR, 'a text message on the emit lane');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
// Anything from the peer, a keepalive included, is proof of life.
|
|
85
|
+
this.#armIdle();
|
|
86
|
+
// §3.3: an empty message is the keepalive, and carries no bytes of the stream.
|
|
87
|
+
if (bytes.byteLength === 0)
|
|
88
|
+
return;
|
|
89
|
+
this.#inbound?.enqueue(bytes);
|
|
90
|
+
});
|
|
91
|
+
// Neither timer holds a process open: the socket does that, for as long as it is open.
|
|
92
|
+
this.#timers.every(this.#keepaliveMs, () => this.#keepalive()).unref();
|
|
93
|
+
this.#armIdle();
|
|
94
|
+
}
|
|
95
|
+
async openEmitStream() {
|
|
96
|
+
const socket = this.#socket;
|
|
97
|
+
const lowWater = this.#lowWater;
|
|
98
|
+
const gone = () => new TransportError('WT_SESSION_CLOSED', 'the WebSocket closed with a write pending', 'Connect again. A frame handed to a closed socket was never sent.');
|
|
99
|
+
return new WritableStream({
|
|
100
|
+
write: async (chunk) => {
|
|
101
|
+
if (socket.readyState !== OPEN)
|
|
102
|
+
throw gone();
|
|
103
|
+
socket.send(chunk);
|
|
104
|
+
this.#sentAt = Date.now();
|
|
105
|
+
// The bound above this sink counts frames whose write has not completed. A socket
|
|
106
|
+
// reports completion through `bufferedAmount` alone, so this is where a write waits
|
|
107
|
+
// while the peer is slow, and where a resolve-at-once would hide the whole backlog.
|
|
108
|
+
while (socket.bufferedAmount > lowWater) {
|
|
109
|
+
if (socket.readyState !== OPEN)
|
|
110
|
+
throw gone();
|
|
111
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_MS));
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
abort: (reason) => {
|
|
115
|
+
this.close(CloseCode.WT_PROTOCOL_ERROR, String(reason));
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/** The socket is the peer's emit stream, so it is delivered at once rather than awaited. */
|
|
120
|
+
onEmitStream(cb) {
|
|
121
|
+
cb(this.#readable);
|
|
122
|
+
}
|
|
123
|
+
openBidi() {
|
|
124
|
+
return Promise.reject(laneUnavailable());
|
|
125
|
+
}
|
|
126
|
+
onBidi() {
|
|
127
|
+
// Nothing will ever arrive: the peer has no bidirectional streams to open either.
|
|
128
|
+
}
|
|
129
|
+
sendDatagram() {
|
|
130
|
+
// The session wraps unreliable frames on the emit lane on this transport, so a datagram
|
|
131
|
+
// reaching here is a bug in transport-io rather than a condition to absorb quietly.
|
|
132
|
+
throw new TransportError('WT_PROTOCOL_ERROR', 'a datagram reached the WebSocket connection', 'Unreliable frames travel as DATAGRAM frames on the emit lane here. Report this.');
|
|
133
|
+
}
|
|
134
|
+
onDatagram() {
|
|
135
|
+
// Datagrams arrive as DATAGRAM frames on the emit stream; see the session.
|
|
136
|
+
}
|
|
137
|
+
/** The same ceiling as the native transport, so an application's limit never moves with it. */
|
|
138
|
+
maxDatagramSize() {
|
|
139
|
+
return DATAGRAM_CONSERVATIVE_FLOOR;
|
|
140
|
+
}
|
|
141
|
+
reliability() {
|
|
142
|
+
return 'reliable-only';
|
|
143
|
+
}
|
|
144
|
+
kind() {
|
|
145
|
+
return 'websocket';
|
|
146
|
+
}
|
|
147
|
+
close(code, reason) {
|
|
148
|
+
if (this.#closing)
|
|
149
|
+
return;
|
|
150
|
+
this.#closing = true;
|
|
151
|
+
this.#timers.clearAll();
|
|
152
|
+
try {
|
|
153
|
+
this.#socket.close(toWebSocketCloseCode(code), truncateCloseReason(reason));
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// Already closing underneath us.
|
|
157
|
+
}
|
|
158
|
+
// Closed from this side is closed now, as it is on every other transport. The closing
|
|
159
|
+
// handshake continues underneath, and against a peer that has stopped reading it never
|
|
160
|
+
// completes: `ws` waits thirty seconds before giving up on it, and a session that has
|
|
161
|
+
// decided to disconnect a slow peer must not sit behind that peer's silence.
|
|
162
|
+
this.#endInbound();
|
|
163
|
+
this.#settleClosed({ code, reason });
|
|
164
|
+
}
|
|
165
|
+
#endInbound() {
|
|
166
|
+
try {
|
|
167
|
+
this.#inbound?.close();
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// Closed twice, which is the peer and the socket both saying the same thing.
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Sent when nothing else has been for an interval. The check runs on a timer of the same
|
|
175
|
+
* period, so the longest silence before a keepalive is two intervals, well inside the
|
|
176
|
+
* peer's deadline.
|
|
177
|
+
*/
|
|
178
|
+
#keepalive() {
|
|
179
|
+
if (this.#closing || this.#socket.readyState !== OPEN)
|
|
180
|
+
return;
|
|
181
|
+
if (Date.now() - this.#sentAt < this.#keepaliveMs)
|
|
182
|
+
return;
|
|
183
|
+
try {
|
|
184
|
+
this.#socket.send(KEEPALIVE);
|
|
185
|
+
this.#sentAt = Date.now();
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// Closing underneath us; the close event releases the timer.
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/** Re-armed by every message. When it fires, the path is dead or the peer sends no keepalive. */
|
|
192
|
+
#armIdle() {
|
|
193
|
+
this.#idleTimer?.cancel();
|
|
194
|
+
this.#idleTimer = this.#timers.after(this.#idleMs, () => {
|
|
195
|
+
this.close(CloseCode.WT_IDLE_TIMEOUT, `no message from the peer for ${this.#idleMs} ms`);
|
|
196
|
+
});
|
|
197
|
+
this.#idleTimer.unref();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function laneUnavailable() {
|
|
201
|
+
return new TransportError('WT_LANE_UNAVAILABLE', 'call() and stream() need a WebTransport session, and this one is on a WebSocket', 'Check client.native before calling: it is null on a fallback session. Emits, and unreliable events that declare a fallback, still work here.');
|
|
202
|
+
}
|
|
203
|
+
function toBytes(data) {
|
|
204
|
+
if (data instanceof ArrayBuffer)
|
|
205
|
+
return new Uint8Array(data);
|
|
206
|
+
if (ArrayBuffer.isView(data)) {
|
|
207
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
208
|
+
}
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
/** §3.3: `WT_NO_ERROR` is a normal closure; every other session code rides in the private range. */
|
|
212
|
+
export function toWebSocketCloseCode(code) {
|
|
213
|
+
return code === CloseCode.WT_NO_ERROR ? WS_CLOSE_NORMAL : WS_CLOSE_OFFSET + code;
|
|
214
|
+
}
|
|
215
|
+
/** The inverse, with the socket's own codes (a lost connection is 1006) passed through as they are. */
|
|
216
|
+
export function fromWebSocketCloseCode(code) {
|
|
217
|
+
if (code === WS_CLOSE_NORMAL)
|
|
218
|
+
return CloseCode.WT_NO_ERROR;
|
|
219
|
+
if (code >= WS_CLOSE_OFFSET + 1000 && code <= WS_CLOSE_OFFSET + 1999)
|
|
220
|
+
return code - WS_CLOSE_OFFSET;
|
|
221
|
+
return code;
|
|
222
|
+
}
|
|
223
|
+
/** Whole characters only, so a cut never leaves half a code point on the wire. */
|
|
224
|
+
export function truncateCloseReason(reason) {
|
|
225
|
+
const encoder = new TextEncoder();
|
|
226
|
+
if (encoder.encode(reason).byteLength <= WS_CLOSE_REASON_MAX_BYTES)
|
|
227
|
+
return reason;
|
|
228
|
+
let out = '';
|
|
229
|
+
for (const ch of reason) {
|
|
230
|
+
if (encoder.encode(out + ch).byteLength > WS_CLOSE_REASON_MAX_BYTES)
|
|
231
|
+
break;
|
|
232
|
+
out += ch;
|
|
233
|
+
}
|
|
234
|
+
return out;
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=websocket-connection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket-connection.js","sourceRoot":"","sources":["../../src/transport/websocket-connection.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,56 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
/**
|
|
3
|
-
* What this needs from a socket: the WHATWG surface, which browsers, Node and `ws` share.
|
|
4
|
-
* `readyState` 1 is open on every one of them.
|
|
5
|
-
*/
|
|
6
|
-
export interface SocketLike {
|
|
7
|
-
binaryType: string;
|
|
8
|
-
readonly bufferedAmount: number;
|
|
9
|
-
readonly readyState: number;
|
|
10
|
-
send(data: Uint8Array): void;
|
|
11
|
-
close(code?: number, reason?: string): void;
|
|
12
|
-
addEventListener(type: 'message', listener: (ev: {
|
|
13
|
-
readonly data: unknown;
|
|
14
|
-
}) => void): void;
|
|
15
|
-
addEventListener(type: 'close', listener: (ev: {
|
|
16
|
-
readonly code: number;
|
|
17
|
-
readonly reason: string;
|
|
18
|
-
}) => void): void;
|
|
19
|
-
addEventListener(type: 'error', listener: () => void): void;
|
|
20
|
-
addEventListener(type: 'open', listener: () => void): void;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Bytes a write may leave queued in the socket and still count as complete. Absolute. Above
|
|
24
|
-
* it the write parks, which is what lets the emit queue fill and its bound fire.
|
|
25
|
-
*/
|
|
26
|
-
export declare const WS_SEND_LOW_WATER_BYTES = 65536;
|
|
27
|
-
export interface WebSocketConnectionOptions {
|
|
28
|
-
/** For the test that proves the bound; production never sets it. */
|
|
29
|
-
readonly lowWaterBytes?: number;
|
|
30
|
-
}
|
|
31
|
-
export declare class WebSocketConnection implements Connection {
|
|
32
|
-
#private;
|
|
33
|
-
readonly closed: Promise<CloseInfo>;
|
|
34
|
-
constructor(socket: SocketLike, opts?: WebSocketConnectionOptions);
|
|
35
|
-
openEmitStream(): Promise<WritableStream<Uint8Array>>;
|
|
36
|
-
/** The socket is the peer's emit stream, so it is delivered at once rather than awaited. */
|
|
37
|
-
onEmitStream(cb: (readable: ReadableStream<Uint8Array>) => void): void;
|
|
38
|
-
openBidi(): Promise<BidiStream>;
|
|
39
|
-
onBidi(): void;
|
|
40
|
-
sendDatagram(): void;
|
|
41
|
-
onDatagram(): void;
|
|
42
|
-
/** The same ceiling as the native transport, so an application's limit never moves with it. */
|
|
43
|
-
maxDatagramSize(): number;
|
|
44
|
-
reliability(): 'reliable-only';
|
|
45
|
-
kind(): 'websocket';
|
|
46
|
-
close(code: number, reason: string): void;
|
|
47
|
-
}
|
|
48
|
-
/** §3.3: `WT_NO_ERROR` is a normal closure; every other session code rides in the private range. */
|
|
49
|
-
export declare function toWebSocketCloseCode(code: number): number;
|
|
50
|
-
/** The inverse, with the socket's own codes (a lost connection is 1006) passed through as they are. */
|
|
51
|
-
export declare function fromWebSocketCloseCode(code: number): number;
|
|
52
|
-
/** Whole characters only, so a cut never leaves half a code point on the wire. */
|
|
53
|
-
export declare function truncateCloseReason(reason: string): string;
|
|
1
|
+
import type { Connection } from './types.ts';
|
|
54
2
|
export interface WebSocketConnectOptions {
|
|
55
3
|
/** `ws://` or `wss://`. A `wss://` origin needs a certificate the platform trusts. */
|
|
56
4
|
readonly url: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../../src/transport/websocket.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../../src/transport/websocket.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAG5C,MAAM,WAAW,uBAAuB;IACtC,sFAAsF;IACtF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,uBAAuB,GAAG,OAAO,CAAC,UAAU,CAAC,CA2BzF"}
|
|
@@ -1,183 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* `transport-io/websocket-transport`: the fallback connector, for `withFallback`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* as bytes of the same stream. Everything else on the seam refuses. There are no
|
|
8
|
-
* bidirectional streams, so a session on this transport carries no calls and no `stream()`,
|
|
9
|
-
* and there are no datagrams: an unreliable event whose contract declared a fallback travels
|
|
10
|
-
* as a `DATAGRAM` frame on the emit lane instead, wrapped and unwrapped by the session (D122).
|
|
11
|
-
*
|
|
12
|
-
* What the socket does not give for free is write completion. A browser exposes only
|
|
13
|
-
* `bufferedAmount`, with no event when it drains, so the sink polls it before resolving a
|
|
14
|
-
* write. Without that every write would resolve at once, and the emit queue's 256-frame
|
|
15
|
-
* bound would measure nothing: `writer.ready` on the reference binding in a new costume
|
|
16
|
-
* (D93). `websocket.node.test.ts` reaches the bound on a real socket with the peer paused,
|
|
17
|
-
* and shows it unreachable with the polling off.
|
|
4
|
+
* This is the whole entry. The connection it returns, the socket interface, the close-code
|
|
5
|
+
* mapping and the sink's low-water mark live in `websocket-connection.ts` and are not
|
|
6
|
+
* exported from the package, as the transport seam is not (D21, D127).
|
|
18
7
|
*/
|
|
19
8
|
import { TransportError } from '../errors.js';
|
|
20
|
-
import {
|
|
21
|
-
const OPEN = 1;
|
|
22
|
-
/**
|
|
23
|
-
* Bytes a write may leave queued in the socket and still count as complete. Absolute. Above
|
|
24
|
-
* it the write parks, which is what lets the emit queue fill and its bound fire.
|
|
25
|
-
*/
|
|
26
|
-
export const WS_SEND_LOW_WATER_BYTES = 65_536;
|
|
27
|
-
/** How long a parked write waits between looks at `bufferedAmount`. */
|
|
28
|
-
const POLL_MS = 4;
|
|
29
|
-
export class WebSocketConnection {
|
|
30
|
-
#socket;
|
|
31
|
-
#lowWater;
|
|
32
|
-
#readable;
|
|
33
|
-
#inbound;
|
|
34
|
-
#closing = false;
|
|
35
|
-
#settleClosed;
|
|
36
|
-
closed;
|
|
37
|
-
constructor(socket, opts = {}) {
|
|
38
|
-
this.#socket = socket;
|
|
39
|
-
this.#lowWater = opts.lowWaterBytes ?? WS_SEND_LOW_WATER_BYTES;
|
|
40
|
-
socket.binaryType = 'arraybuffer';
|
|
41
|
-
this.#readable = new ReadableStream({
|
|
42
|
-
start: (controller) => {
|
|
43
|
-
this.#inbound = controller;
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
this.closed = new Promise((resolve) => {
|
|
47
|
-
this.#settleClosed = resolve;
|
|
48
|
-
});
|
|
49
|
-
socket.addEventListener('close', (ev) => {
|
|
50
|
-
this.#closing = true;
|
|
51
|
-
this.#endInbound();
|
|
52
|
-
this.#settleClosed({ code: fromWebSocketCloseCode(ev.code), reason: ev.reason });
|
|
53
|
-
});
|
|
54
|
-
socket.addEventListener('error', () => {
|
|
55
|
-
// A close event follows every error, and that is the one report `closed` makes.
|
|
56
|
-
});
|
|
57
|
-
socket.addEventListener('message', (ev) => {
|
|
58
|
-
const bytes = toBytes(ev.data);
|
|
59
|
-
if (bytes === undefined) {
|
|
60
|
-
// §3.3: binary messages only. A text frame is not a framing accident, it is another
|
|
61
|
-
// protocol, and the lane cannot recover from it any more than from a bad length.
|
|
62
|
-
this.close(CloseCode.WT_PROTOCOL_ERROR, 'a text message on the emit lane');
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
this.#inbound?.enqueue(bytes);
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
async openEmitStream() {
|
|
69
|
-
const socket = this.#socket;
|
|
70
|
-
const lowWater = this.#lowWater;
|
|
71
|
-
const gone = () => new TransportError('WT_SESSION_CLOSED', 'the WebSocket closed with a write pending', 'Connect again. A frame handed to a closed socket was never sent.');
|
|
72
|
-
return new WritableStream({
|
|
73
|
-
write: async (chunk) => {
|
|
74
|
-
if (socket.readyState !== OPEN)
|
|
75
|
-
throw gone();
|
|
76
|
-
socket.send(chunk);
|
|
77
|
-
// The bound above this sink counts frames whose write has not completed. A socket
|
|
78
|
-
// reports completion through `bufferedAmount` alone, so this is where a write waits
|
|
79
|
-
// while the peer is slow, and where a resolve-at-once would hide the whole backlog.
|
|
80
|
-
while (socket.bufferedAmount > lowWater) {
|
|
81
|
-
if (socket.readyState !== OPEN)
|
|
82
|
-
throw gone();
|
|
83
|
-
await new Promise((resolve) => setTimeout(resolve, POLL_MS));
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
|
-
abort: (reason) => {
|
|
87
|
-
this.close(CloseCode.WT_PROTOCOL_ERROR, String(reason));
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
/** The socket is the peer's emit stream, so it is delivered at once rather than awaited. */
|
|
92
|
-
onEmitStream(cb) {
|
|
93
|
-
cb(this.#readable);
|
|
94
|
-
}
|
|
95
|
-
openBidi() {
|
|
96
|
-
return Promise.reject(laneUnavailable());
|
|
97
|
-
}
|
|
98
|
-
onBidi() {
|
|
99
|
-
// Nothing will ever arrive: the peer has no bidirectional streams to open either.
|
|
100
|
-
}
|
|
101
|
-
sendDatagram() {
|
|
102
|
-
// The session wraps unreliable frames on the emit lane on this transport, so a datagram
|
|
103
|
-
// reaching here is a bug in transport-io rather than a condition to absorb quietly.
|
|
104
|
-
throw new TransportError('WT_PROTOCOL_ERROR', 'a datagram reached the WebSocket connection', 'Unreliable frames travel as DATAGRAM frames on the emit lane here. Report this.');
|
|
105
|
-
}
|
|
106
|
-
onDatagram() {
|
|
107
|
-
// Datagrams arrive as DATAGRAM frames on the emit stream; see the session.
|
|
108
|
-
}
|
|
109
|
-
/** The same ceiling as the native transport, so an application's limit never moves with it. */
|
|
110
|
-
maxDatagramSize() {
|
|
111
|
-
return DATAGRAM_CONSERVATIVE_FLOOR;
|
|
112
|
-
}
|
|
113
|
-
reliability() {
|
|
114
|
-
return 'reliable-only';
|
|
115
|
-
}
|
|
116
|
-
kind() {
|
|
117
|
-
return 'websocket';
|
|
118
|
-
}
|
|
119
|
-
close(code, reason) {
|
|
120
|
-
if (this.#closing)
|
|
121
|
-
return;
|
|
122
|
-
this.#closing = true;
|
|
123
|
-
try {
|
|
124
|
-
this.#socket.close(toWebSocketCloseCode(code), truncateCloseReason(reason));
|
|
125
|
-
}
|
|
126
|
-
catch {
|
|
127
|
-
// Already closing underneath us.
|
|
128
|
-
}
|
|
129
|
-
// Closed from this side is closed now, as it is on every other transport. The closing
|
|
130
|
-
// handshake continues underneath, and against a peer that has stopped reading it never
|
|
131
|
-
// completes: `ws` waits thirty seconds before giving up on it, and a session that has
|
|
132
|
-
// decided to disconnect a slow peer must not sit behind that peer's silence.
|
|
133
|
-
this.#endInbound();
|
|
134
|
-
this.#settleClosed({ code, reason });
|
|
135
|
-
}
|
|
136
|
-
#endInbound() {
|
|
137
|
-
try {
|
|
138
|
-
this.#inbound?.close();
|
|
139
|
-
}
|
|
140
|
-
catch {
|
|
141
|
-
// Closed twice, which is the peer and the socket both saying the same thing.
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
function laneUnavailable() {
|
|
146
|
-
return new TransportError('WT_LANE_UNAVAILABLE', 'call() and stream() need a WebTransport session, and this one is on a WebSocket', 'Check client.native before calling: it is null on a fallback session. Emits, and unreliable events that declare a fallback, still work here.');
|
|
147
|
-
}
|
|
148
|
-
function toBytes(data) {
|
|
149
|
-
if (data instanceof ArrayBuffer)
|
|
150
|
-
return new Uint8Array(data);
|
|
151
|
-
if (ArrayBuffer.isView(data)) {
|
|
152
|
-
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
153
|
-
}
|
|
154
|
-
return undefined;
|
|
155
|
-
}
|
|
156
|
-
/** §3.3: `WT_NO_ERROR` is a normal closure; every other session code rides in the private range. */
|
|
157
|
-
export function toWebSocketCloseCode(code) {
|
|
158
|
-
return code === CloseCode.WT_NO_ERROR ? WS_CLOSE_NORMAL : WS_CLOSE_OFFSET + code;
|
|
159
|
-
}
|
|
160
|
-
/** The inverse, with the socket's own codes (a lost connection is 1006) passed through as they are. */
|
|
161
|
-
export function fromWebSocketCloseCode(code) {
|
|
162
|
-
if (code === WS_CLOSE_NORMAL)
|
|
163
|
-
return CloseCode.WT_NO_ERROR;
|
|
164
|
-
if (code >= WS_CLOSE_OFFSET + 1000 && code <= WS_CLOSE_OFFSET + 1999)
|
|
165
|
-
return code - WS_CLOSE_OFFSET;
|
|
166
|
-
return code;
|
|
167
|
-
}
|
|
168
|
-
/** Whole characters only, so a cut never leaves half a code point on the wire. */
|
|
169
|
-
export function truncateCloseReason(reason) {
|
|
170
|
-
const encoder = new TextEncoder();
|
|
171
|
-
if (encoder.encode(reason).byteLength <= WS_CLOSE_REASON_MAX_BYTES)
|
|
172
|
-
return reason;
|
|
173
|
-
let out = '';
|
|
174
|
-
for (const ch of reason) {
|
|
175
|
-
if (encoder.encode(out + ch).byteLength > WS_CLOSE_REASON_MAX_BYTES)
|
|
176
|
-
break;
|
|
177
|
-
out += ch;
|
|
178
|
-
}
|
|
179
|
-
return out;
|
|
180
|
-
}
|
|
9
|
+
import { WebSocketConnection } from './websocket-connection.js';
|
|
181
10
|
/**
|
|
182
11
|
* The fallback connector, for `withFallback`. Runtime-neutral: it reads the global
|
|
183
12
|
* `WebSocket`, which browsers, Node and Bun all provide.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket.js","sourceRoot":"","sources":["../../src/transport/websocket.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"websocket.js","sourceRoot":"","sources":["../../src/transport/websocket.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAE7C,OAAO,EAAmB,mBAAmB,EAAE,MAAM,2BAA2B,CAAA;AAOhF;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAA6B;IAClE,MAAM,EAAE,GAAI,UAA8D,CAAC,SAAS,CAAA;IACpF,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;QACrB,MAAM,IAAI,cAAc,CACtB,eAAe,EACf,+BAA+B,EAC/B,0CAA0C,CAC3C,CAAA;IACH,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC/B,MAAM,CAAC,UAAU,GAAG,aAAa,CAAA;IACjC,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAA;QAChD,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE;YACtC,MAAM,CACJ,IAAI,cAAc,CAChB,qBAAqB,EACrB,8BAA8B,IAAI,CAAC,GAAG,uBAAuB,EAAE,CAAC,IAAI,EAAE,EACtE,iJAAiJ,CAClJ,CACF,CAAA;QACH,CAAC,CAAC,CAAA;QACF,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;YACpC,oFAAoF;QACtF,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IACF,OAAO,IAAI,mBAAmB,CAAC,MAAM,CAAC,CAAA;AACxC,CAAC"}
|
|
@@ -15,7 +15,7 @@ import { createServer as createHttpServer, } from 'node:http';
|
|
|
15
15
|
import { createServer as createHttpsServer } from 'node:https';
|
|
16
16
|
import { WebSocketServer } from 'ws';
|
|
17
17
|
import { PROBE_PATH } from './probe.js';
|
|
18
|
-
import { WebSocketConnection } from './websocket.js';
|
|
18
|
+
import { WebSocketConnection } from './websocket-connection.js';
|
|
19
19
|
function answer(req, res) {
|
|
20
20
|
// Any status is an answer to the probe. 204 at its path, 404 elsewhere, and nothing else
|
|
21
21
|
// is served: this is a transport listener, not a web server.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket.node.js","sourceRoot":"","sources":["../../src/transport/websocket.node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EACL,YAAY,IAAI,gBAAgB,GAGjC,MAAM,WAAW,CAAA;AAClB,OAAO,EAAE,YAAY,IAAI,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,IAAI,CAAA;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEvC,OAAO,EAAmB,mBAAmB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"websocket.node.js","sourceRoot":"","sources":["../../src/transport/websocket.node.ts"],"names":[],"mappings":"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"}
|