@fuman/net 0.0.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.
Files changed (59) hide show
  1. package/LICENSE +8 -0
  2. package/errors.cjs +14 -0
  3. package/errors.d.ts +6 -0
  4. package/errors.js +14 -0
  5. package/fake.cjs +42 -0
  6. package/fake.d.ts +16 -0
  7. package/fake.js +42 -0
  8. package/index.cjs +30 -0
  9. package/index.d.ts +7 -0
  10. package/index.js +30 -0
  11. package/ip/bundle.cjs +19 -0
  12. package/ip/bundle.d.ts +3 -0
  13. package/ip/bundle.js +19 -0
  14. package/ip/index.d.ts +8 -0
  15. package/ip/parse.cjs +34 -0
  16. package/ip/parse.d.ts +5 -0
  17. package/ip/parse.js +34 -0
  18. package/ip/types.d.ts +10 -0
  19. package/ip/v4.bench.d.ts +1 -0
  20. package/ip/v4.cjs +55 -0
  21. package/ip/v4.d.ts +4 -0
  22. package/ip/v4.js +55 -0
  23. package/ip/v6.bench.d.ts +1 -0
  24. package/ip/v6.cjs +217 -0
  25. package/ip/v6.d.ts +18 -0
  26. package/ip/v6.js +217 -0
  27. package/package.json +30 -0
  28. package/proxy/http/_protocol.cjs +31 -0
  29. package/proxy/http/_protocol.d.ts +3 -0
  30. package/proxy/http/_protocol.js +31 -0
  31. package/proxy/http/connect.cjs +34 -0
  32. package/proxy/http/connect.d.ts +4 -0
  33. package/proxy/http/connect.js +34 -0
  34. package/proxy/http/index.cjs +15 -0
  35. package/proxy/http/index.d.ts +5 -0
  36. package/proxy/http/index.js +15 -0
  37. package/proxy/http/types.cjs +10 -0
  38. package/proxy/http/types.d.ts +32 -0
  39. package/proxy/http/types.js +10 -0
  40. package/proxy/index.d.ts +2 -0
  41. package/proxy/socks/_protocol.cjs +111 -0
  42. package/proxy/socks/_protocol.d.ts +7 -0
  43. package/proxy/socks/_protocol.js +111 -0
  44. package/proxy/socks/connect.cjs +78 -0
  45. package/proxy/socks/connect.d.ts +4 -0
  46. package/proxy/socks/connect.js +78 -0
  47. package/proxy/socks/index.cjs +15 -0
  48. package/proxy/socks/index.d.ts +5 -0
  49. package/proxy/socks/index.js +15 -0
  50. package/proxy/socks/types.cjs +10 -0
  51. package/proxy/socks/types.d.ts +34 -0
  52. package/proxy/socks/types.js +10 -0
  53. package/reconnection.cjs +151 -0
  54. package/reconnection.d.ts +82 -0
  55. package/reconnection.js +151 -0
  56. package/types.d.ts +41 -0
  57. package/websocket.cjs +157 -0
  58. package/websocket.d.ts +44 -0
  59. package/websocket.js +157 -0
@@ -0,0 +1,82 @@
1
+ import { MaybePromise } from '@fuman/utils';
2
+ import { ConnectFunction, IConnection } from './types.js';
3
+ interface ReconnectionState {
4
+ readonly previousWait: number | null;
5
+ readonly consequentFails: number;
6
+ readonly lastError: Error | null;
7
+ }
8
+ /**
9
+ * Declares a strategy to handle reconnection.
10
+ * When a number is returned, that number of MS will be waited before trying to reconnect.
11
+ * When `false` is returned, connection is not reconnected
12
+ */
13
+ export type ReconnectionStrategy = (state: ReconnectionState) => number | false;
14
+ /**
15
+ * Declares an action to take when an error occurs.
16
+ *
17
+ * - `reconnect` - reconnect using the current strategy
18
+ * - `reconnect-now` - reconnect immediately, ignoring the current strategy
19
+ * - `close` - close the connection
20
+ */
21
+ export type OnErrorAction = 'reconnect' | 'reconnect-now' | 'close';
22
+ /**
23
+ * default reconnection strategy: first - immediate reconnection,
24
+ * then 1s with linear increase up to 5s (with 1s step)
25
+ */
26
+ export declare const defaultReconnectionStrategy: ReconnectionStrategy;
27
+ export declare class PersistentConnection<ConnectAddress, Connection extends IConnection<unknown>> {
28
+ #private;
29
+ readonly params: {
30
+ connect: ConnectFunction<ConnectAddress, Connection>;
31
+ strategy?: ReconnectionStrategy;
32
+ /**
33
+ * Function to call once the connection is open
34
+ *
35
+ * As soon as the promise is resolved the connection will be closed (and will *not*
36
+ * be re-opened automatically), so this is the best place to put your recv loop
37
+ */
38
+ onOpen: (connection: Connection) => Promise<void>;
39
+ onClose?: () => MaybePromise<void>;
40
+ onWait?: (wait: number) => void;
41
+ /**
42
+ * Function that will be called whenever an error happens while connecting
43
+ * (in which case `connection` will be null) or inside `onOpen`.
44
+ *
45
+ * @default `(err) => err instanceof ConnectionClosedError ? 'reconnect' : 'close'`
46
+ */
47
+ onError?: (error: Error, connection: Connection | null, state: ReconnectionState) => MaybePromise<OnErrorAction>;
48
+ };
49
+ constructor(params: {
50
+ connect: ConnectFunction<ConnectAddress, Connection>;
51
+ strategy?: ReconnectionStrategy;
52
+ /**
53
+ * Function to call once the connection is open
54
+ *
55
+ * As soon as the promise is resolved the connection will be closed (and will *not*
56
+ * be re-opened automatically), so this is the best place to put your recv loop
57
+ */
58
+ onOpen: (connection: Connection) => Promise<void>;
59
+ onClose?: () => MaybePromise<void>;
60
+ onWait?: (wait: number) => void;
61
+ /**
62
+ * Function that will be called whenever an error happens while connecting
63
+ * (in which case `connection` will be null) or inside `onOpen`.
64
+ *
65
+ * @default `(err) => err instanceof ConnectionClosedError ? 'reconnect' : 'close'`
66
+ */
67
+ onError?: (error: Error, connection: Connection | null, state: ReconnectionState) => MaybePromise<OnErrorAction>;
68
+ });
69
+ get isConnected(): boolean;
70
+ get isConnecting(): boolean;
71
+ get isWaiting(): boolean;
72
+ get connection(): Connection | null;
73
+ get state(): ReconnectionState;
74
+ connect(address: ConnectAddress): void;
75
+ /**
76
+ * @param force Whether to close the existing connection if there is one
77
+ */
78
+ reconnect(force: boolean): void;
79
+ close(): Promise<void>;
80
+ changeTransport(connect: ConnectFunction<ConnectAddress, Connection>): Promise<void>;
81
+ }
82
+ export {};
@@ -0,0 +1,151 @@
1
+ import { Deferred, timers } from "@fuman/utils";
2
+ import { ConnectionClosedError } from "./errors.js";
3
+ const defaultReconnectionStrategy = ({ previousWait }) => {
4
+ if (previousWait === null) return 0;
5
+ if (previousWait === 0) return 1e3;
6
+ if (previousWait >= 5e3) return 5e3;
7
+ return Math.min(5e3, previousWait + 1e3);
8
+ };
9
+ function defaultOnError(err) {
10
+ return err instanceof ConnectionClosedError ? "reconnect" : "close";
11
+ }
12
+ class PersistentConnection {
13
+ constructor(params) {
14
+ this.params = params;
15
+ this.#strategy = params.strategy ?? defaultReconnectionStrategy;
16
+ this.#connect = params.connect;
17
+ this.#onError = params.onError ?? defaultOnError;
18
+ }
19
+ #state = {
20
+ previousWait: null,
21
+ lastError: null,
22
+ consequentFails: 0
23
+ };
24
+ #connect;
25
+ #lastAddress;
26
+ #connection;
27
+ #connecting = false;
28
+ #strategy;
29
+ #onError;
30
+ // boolean represents whether the timer is clean
31
+ // true - resolved because timer is up
32
+ // false - resolved because .close()/.reconnect() was called
33
+ #sleep;
34
+ #closed;
35
+ get isConnected() {
36
+ return this.#connection !== void 0;
37
+ }
38
+ get isConnecting() {
39
+ return this.#connection === void 0 && this.#connecting;
40
+ }
41
+ get isWaiting() {
42
+ return this.#connection === void 0 && this.#lastAddress !== void 0 && !this.#connecting;
43
+ }
44
+ get connection() {
45
+ return this.#connection ?? null;
46
+ }
47
+ get state() {
48
+ return this.#state;
49
+ }
50
+ #resetState() {
51
+ this.#state.previousWait = null;
52
+ this.#state.lastError = null;
53
+ this.#state.consequentFails = 0;
54
+ this.#connecting = false;
55
+ }
56
+ async #loop() {
57
+ while (true) {
58
+ try {
59
+ this.#connecting = true;
60
+ this.#connection = await this.#connect(this.#lastAddress);
61
+ if (this.#closed) {
62
+ this.#closed.resolve();
63
+ break;
64
+ }
65
+ this.#resetState();
66
+ await this.params.onOpen?.(this.#connection);
67
+ this.#connection.close();
68
+ this.#connection = void 0;
69
+ break;
70
+ } catch (err) {
71
+ const oldConnection = this.#connection;
72
+ this.#connection = void 0;
73
+ await this.params.onClose?.();
74
+ if (this.#closed) {
75
+ this.#closed.resolve();
76
+ break;
77
+ }
78
+ const action = await this.#onError(err, oldConnection ?? null, this.#state);
79
+ if (action === "close") {
80
+ break;
81
+ }
82
+ const wait = action === "reconnect-now" ? 0 : this.#strategy(this.#state);
83
+ if (wait === false) {
84
+ break;
85
+ }
86
+ this.params.onWait?.(wait);
87
+ if (wait !== 0) {
88
+ this.#sleep = new Deferred();
89
+ const timer = timers.setTimeout(this.#sleep.resolve, wait, true);
90
+ const sleepResult = await this.#sleep.promise;
91
+ this.#sleep = void 0;
92
+ if (!sleepResult) {
93
+ timers.clearTimeout(timer);
94
+ if (this.#closed != null) {
95
+ this.#closed.resolve();
96
+ break;
97
+ } else {
98
+ continue;
99
+ }
100
+ }
101
+ }
102
+ this.#state.previousWait = wait;
103
+ this.#state.consequentFails = 1;
104
+ continue;
105
+ }
106
+ }
107
+ this.#lastAddress = void 0;
108
+ this.#resetState();
109
+ }
110
+ connect(address) {
111
+ if (this.#lastAddress !== void 0 && this.#lastAddress !== address) {
112
+ throw new Error("Connection is already open to another address");
113
+ }
114
+ this.#closed = void 0;
115
+ this.#lastAddress = address;
116
+ void this.#loop();
117
+ }
118
+ /**
119
+ * @param force Whether to close the existing connection if there is one
120
+ */
121
+ reconnect(force) {
122
+ if (this.#sleep) {
123
+ this.#sleep.resolve(false);
124
+ } else if (this.#connection && force) {
125
+ this.#connection.close();
126
+ }
127
+ }
128
+ async close() {
129
+ if (this.#closed) return this.#closed.promise;
130
+ if (this.#lastAddress == null) return;
131
+ this.#closed = new Deferred();
132
+ if (this.#sleep) {
133
+ this.#sleep.resolve(false);
134
+ } else if (this.#connection) {
135
+ this.#connection.close();
136
+ } else if (this.#connecting) ;
137
+ return this.#closed.promise;
138
+ }
139
+ async changeTransport(connect) {
140
+ this.#connect = connect;
141
+ const addr = this.#lastAddress;
142
+ await this.close();
143
+ if (addr != null) {
144
+ this.connect(addr);
145
+ }
146
+ }
147
+ }
148
+ export {
149
+ PersistentConnection,
150
+ defaultReconnectionStrategy
151
+ };
package/types.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ import { IClosable, IReadable, IWritable } from '@fuman/io';
2
+ export interface IConnection<Address, LocalAddress = Address> extends IReadable, IWritable, IClosable {
3
+ readonly localAddress: LocalAddress | null;
4
+ readonly remoteAddress: Address | null;
5
+ }
6
+ export interface TcpEndpoint {
7
+ readonly address: string;
8
+ readonly port: number;
9
+ }
10
+ export interface TlsOptions {
11
+ /**
12
+ * List of CA certificates to use.
13
+ * Will replace whatever is in the default platform CA store.
14
+ */
15
+ readonly caCerts?: string[];
16
+ /** List of ALPN protocols to accept/offer */
17
+ readonly alpnProtocols?: string[];
18
+ /** Hostname to use for SNI */
19
+ readonly sni?: string;
20
+ }
21
+ export interface TlsConnectOptions extends TcpEndpoint, TlsOptions {
22
+ }
23
+ export interface TlsListenOptions extends TcpEndpoint, TlsOptions {
24
+ readonly key?: string;
25
+ readonly cert?: string;
26
+ readonly hosts?: Omit<this, 'hosts' | 'address' | 'port'>[];
27
+ }
28
+ export interface ITcpConnection extends IConnection<TcpEndpoint> {
29
+ setNoDelay: (noDelay: boolean) => void;
30
+ setKeepAlive: (keepAlive: boolean) => void;
31
+ }
32
+ export interface ITlsConnection extends ITcpConnection {
33
+ getAlpnProtocol: () => string | null;
34
+ }
35
+ export interface IListener<Address, Connection extends IConnection<Address> = IConnection<Address>> extends IClosable {
36
+ readonly address: Address | null;
37
+ accept: () => Promise<Connection>;
38
+ }
39
+ export type ListenFunction<Options, Listener extends IListener<unknown>> = (options: Options) => Promise<Listener>;
40
+ export type ConnectFunction<Options, Connection extends IConnection<unknown>> = (options: Options) => Promise<Connection>;
41
+ export type TlsUpgradeFunction<Options, TcpConnection extends ITcpConnection, TlsConnection extends ITlsConnection> = (tcpConn: TcpConnection, options: Options) => Promise<TlsConnection>;
package/websocket.cjs ADDED
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const io = require("@fuman/io");
4
+ const utils = require("@fuman/utils");
5
+ const errors = require("./errors.cjs");
6
+ class WebSocketConnectionClosedError extends errors.ConnectionClosedError {
7
+ constructor(code, reason) {
8
+ super(`code ${code} (${reason})`);
9
+ this.code = code;
10
+ this.reason = reason;
11
+ }
12
+ }
13
+ function eventToError(event) {
14
+ if (event instanceof Error) {
15
+ return event;
16
+ }
17
+ return "error" in event ? event.error : new Error("unknown WebSocket error", { cause: event });
18
+ }
19
+ class WebSocketConnectionBase {
20
+ constructor(socket) {
21
+ this.socket = socket;
22
+ this._cv = new utils.ConditionVariable();
23
+ socket.addEventListener("message", (event) => {
24
+ this.onMessage(event);
25
+ this._cv.notify();
26
+ });
27
+ socket.addEventListener("close", (event) => {
28
+ this._error = new WebSocketConnectionClosedError(event.code, event.reason);
29
+ this._cv.notify();
30
+ });
31
+ socket.addEventListener("error", (event) => {
32
+ this._error = eventToError(event);
33
+ this._cv.notify();
34
+ });
35
+ }
36
+ _error = null;
37
+ _cv;
38
+ get remoteAddress() {
39
+ return this.socket.url;
40
+ }
41
+ get localAddress() {
42
+ throw new Error(".localAddress is not available for WebSockets");
43
+ }
44
+ close() {
45
+ this.socket?.close();
46
+ this._error = new errors.ConnectionClosedError();
47
+ }
48
+ closeWithCode(code, reason) {
49
+ this.socket.close(code, reason);
50
+ this._error = new errors.ConnectionClosedError();
51
+ }
52
+ }
53
+ class WebSocketConnection extends WebSocketConnectionBase {
54
+ #buffer;
55
+ constructor(socket) {
56
+ super(socket);
57
+ this.#buffer = io.Bytes.alloc(0);
58
+ }
59
+ onMessage(event) {
60
+ if (typeof event.data === "string") {
61
+ const buf = this.#buffer.writeSync(utils.utf8.encodedLength(event.data));
62
+ utils.utf8.encoder.encodeInto(event.data, buf);
63
+ } else {
64
+ const u8 = new Uint8Array(event.data);
65
+ this.#buffer.writeSync(u8.length).set(u8);
66
+ }
67
+ this.#buffer.disposeWriteSync();
68
+ }
69
+ async read(into) {
70
+ if (this.#buffer.available > 0) {
71
+ const size2 = Math.min(this.#buffer.available, into.length);
72
+ into.set(this.#buffer.readSync(size2));
73
+ this.#buffer.reclaim();
74
+ return size2;
75
+ }
76
+ if (this._error !== null) throw this._error;
77
+ await this._cv.wait();
78
+ if (this._error !== null) throw this._error;
79
+ const size = Math.min(this.#buffer.available, into.length);
80
+ into.set(this.#buffer.readSync(size));
81
+ this.#buffer.reclaim();
82
+ return size;
83
+ }
84
+ async write(bytes) {
85
+ if (this._error) throw this._error;
86
+ if (!bytes.length) return;
87
+ this.socket.send(bytes);
88
+ }
89
+ }
90
+ class WebSocketConnectionFramed extends WebSocketConnectionBase {
91
+ #buffer = new utils.Deque();
92
+ onMessage(event) {
93
+ if (typeof event.data === "string") {
94
+ this.#buffer.pushBack(event.data);
95
+ } else {
96
+ this.#buffer.pushBack(new Uint8Array(event.data));
97
+ }
98
+ }
99
+ async readFrame() {
100
+ if (!this.#buffer.isEmpty()) {
101
+ return this.#buffer.popFront();
102
+ }
103
+ if (this._error !== null) throw this._error;
104
+ await this._cv.wait();
105
+ if (this._error !== null) throw this._error;
106
+ return this.#buffer.popFront();
107
+ }
108
+ async writeFrame(data) {
109
+ if (this._error) throw this._error;
110
+ this.socket.send(data);
111
+ }
112
+ }
113
+ const connectWs = (endpoint) => {
114
+ const {
115
+ url,
116
+ implementation: WebSocketImpl = WebSocket,
117
+ protocols
118
+ } = endpoint;
119
+ return new Promise((resolve, reject) => {
120
+ const socket = new WebSocketImpl(url, protocols);
121
+ socket.binaryType = "arraybuffer";
122
+ const onError = (event) => {
123
+ socket.removeEventListener("error", onError);
124
+ reject(eventToError(event));
125
+ };
126
+ socket.addEventListener("error", onError);
127
+ socket.addEventListener("open", () => {
128
+ socket.removeEventListener("error", onError);
129
+ resolve(new WebSocketConnection(socket));
130
+ });
131
+ });
132
+ };
133
+ async function connectWsFramed(endpoint) {
134
+ const {
135
+ url,
136
+ implementation: WebSocketImpl = WebSocket,
137
+ protocols
138
+ } = endpoint;
139
+ return new Promise((resolve, reject) => {
140
+ const socket = new WebSocketImpl(url, protocols);
141
+ socket.binaryType = "arraybuffer";
142
+ const onError = (event) => {
143
+ socket.removeEventListener("error", onError);
144
+ reject(eventToError(event));
145
+ };
146
+ socket.addEventListener("error", onError);
147
+ socket.addEventListener("open", () => {
148
+ socket.removeEventListener("error", onError);
149
+ resolve(new WebSocketConnectionFramed(socket));
150
+ });
151
+ });
152
+ }
153
+ exports.WebSocketConnection = WebSocketConnection;
154
+ exports.WebSocketConnectionClosedError = WebSocketConnectionClosedError;
155
+ exports.WebSocketConnectionFramed = WebSocketConnectionFramed;
156
+ exports.connectWs = connectWs;
157
+ exports.connectWsFramed = connectWsFramed;
package/websocket.d.ts ADDED
@@ -0,0 +1,44 @@
1
+ import { IClosable } from '@fuman/io';
2
+ import { ConnectFunction, IConnection } from './types.js';
3
+ import { ConditionVariable } from '@fuman/utils';
4
+ import { ConnectionClosedError } from './errors.js';
5
+ export declare class WebSocketConnectionClosedError extends ConnectionClosedError {
6
+ readonly code: number;
7
+ readonly reason: string;
8
+ constructor(code: number, reason: string);
9
+ }
10
+ declare abstract class WebSocketConnectionBase implements IClosable {
11
+ readonly socket: WebSocket;
12
+ protected _error: Error | null;
13
+ protected _cv: ConditionVariable;
14
+ constructor(socket: WebSocket);
15
+ get remoteAddress(): string | null;
16
+ get localAddress(): never;
17
+ close(): void;
18
+ closeWithCode(code: number, reason?: string): void;
19
+ abstract onMessage(event: MessageEvent): void;
20
+ }
21
+ export declare class WebSocketConnection extends WebSocketConnectionBase implements IConnection<string, never> {
22
+ #private;
23
+ constructor(socket: WebSocket);
24
+ onMessage(event: MessageEvent): void;
25
+ read(into: Uint8Array): Promise<number>;
26
+ write(bytes: Uint8Array): Promise<void>;
27
+ }
28
+ export declare class WebSocketConnectionFramed extends WebSocketConnectionBase {
29
+ #private;
30
+ onMessage(event: MessageEvent): void;
31
+ readFrame(): Promise<Uint8Array | string>;
32
+ writeFrame(data: Uint8Array | string): Promise<void>;
33
+ }
34
+ export interface WebSocketConstructor {
35
+ new (url: string | URL, protocols?: string | string[]): WebSocket;
36
+ }
37
+ export interface WebSocketEndpoint {
38
+ readonly url: string | URL;
39
+ readonly implementation?: WebSocketConstructor;
40
+ readonly protocols?: string | string[];
41
+ }
42
+ export declare const connectWs: ConnectFunction<WebSocketEndpoint, WebSocketConnection>;
43
+ export declare function connectWsFramed(endpoint: WebSocketEndpoint): Promise<WebSocketConnectionFramed>;
44
+ export {};
package/websocket.js ADDED
@@ -0,0 +1,157 @@
1
+ import { Bytes } from "@fuman/io";
2
+ import { utf8, Deque, ConditionVariable } from "@fuman/utils";
3
+ import { ConnectionClosedError } from "./errors.js";
4
+ class WebSocketConnectionClosedError extends ConnectionClosedError {
5
+ constructor(code, reason) {
6
+ super(`code ${code} (${reason})`);
7
+ this.code = code;
8
+ this.reason = reason;
9
+ }
10
+ }
11
+ function eventToError(event) {
12
+ if (event instanceof Error) {
13
+ return event;
14
+ }
15
+ return "error" in event ? event.error : new Error("unknown WebSocket error", { cause: event });
16
+ }
17
+ class WebSocketConnectionBase {
18
+ constructor(socket) {
19
+ this.socket = socket;
20
+ this._cv = new ConditionVariable();
21
+ socket.addEventListener("message", (event) => {
22
+ this.onMessage(event);
23
+ this._cv.notify();
24
+ });
25
+ socket.addEventListener("close", (event) => {
26
+ this._error = new WebSocketConnectionClosedError(event.code, event.reason);
27
+ this._cv.notify();
28
+ });
29
+ socket.addEventListener("error", (event) => {
30
+ this._error = eventToError(event);
31
+ this._cv.notify();
32
+ });
33
+ }
34
+ _error = null;
35
+ _cv;
36
+ get remoteAddress() {
37
+ return this.socket.url;
38
+ }
39
+ get localAddress() {
40
+ throw new Error(".localAddress is not available for WebSockets");
41
+ }
42
+ close() {
43
+ this.socket?.close();
44
+ this._error = new ConnectionClosedError();
45
+ }
46
+ closeWithCode(code, reason) {
47
+ this.socket.close(code, reason);
48
+ this._error = new ConnectionClosedError();
49
+ }
50
+ }
51
+ class WebSocketConnection extends WebSocketConnectionBase {
52
+ #buffer;
53
+ constructor(socket) {
54
+ super(socket);
55
+ this.#buffer = Bytes.alloc(0);
56
+ }
57
+ onMessage(event) {
58
+ if (typeof event.data === "string") {
59
+ const buf = this.#buffer.writeSync(utf8.encodedLength(event.data));
60
+ utf8.encoder.encodeInto(event.data, buf);
61
+ } else {
62
+ const u8 = new Uint8Array(event.data);
63
+ this.#buffer.writeSync(u8.length).set(u8);
64
+ }
65
+ this.#buffer.disposeWriteSync();
66
+ }
67
+ async read(into) {
68
+ if (this.#buffer.available > 0) {
69
+ const size2 = Math.min(this.#buffer.available, into.length);
70
+ into.set(this.#buffer.readSync(size2));
71
+ this.#buffer.reclaim();
72
+ return size2;
73
+ }
74
+ if (this._error !== null) throw this._error;
75
+ await this._cv.wait();
76
+ if (this._error !== null) throw this._error;
77
+ const size = Math.min(this.#buffer.available, into.length);
78
+ into.set(this.#buffer.readSync(size));
79
+ this.#buffer.reclaim();
80
+ return size;
81
+ }
82
+ async write(bytes) {
83
+ if (this._error) throw this._error;
84
+ if (!bytes.length) return;
85
+ this.socket.send(bytes);
86
+ }
87
+ }
88
+ class WebSocketConnectionFramed extends WebSocketConnectionBase {
89
+ #buffer = new Deque();
90
+ onMessage(event) {
91
+ if (typeof event.data === "string") {
92
+ this.#buffer.pushBack(event.data);
93
+ } else {
94
+ this.#buffer.pushBack(new Uint8Array(event.data));
95
+ }
96
+ }
97
+ async readFrame() {
98
+ if (!this.#buffer.isEmpty()) {
99
+ return this.#buffer.popFront();
100
+ }
101
+ if (this._error !== null) throw this._error;
102
+ await this._cv.wait();
103
+ if (this._error !== null) throw this._error;
104
+ return this.#buffer.popFront();
105
+ }
106
+ async writeFrame(data) {
107
+ if (this._error) throw this._error;
108
+ this.socket.send(data);
109
+ }
110
+ }
111
+ const connectWs = (endpoint) => {
112
+ const {
113
+ url,
114
+ implementation: WebSocketImpl = WebSocket,
115
+ protocols
116
+ } = endpoint;
117
+ return new Promise((resolve, reject) => {
118
+ const socket = new WebSocketImpl(url, protocols);
119
+ socket.binaryType = "arraybuffer";
120
+ const onError = (event) => {
121
+ socket.removeEventListener("error", onError);
122
+ reject(eventToError(event));
123
+ };
124
+ socket.addEventListener("error", onError);
125
+ socket.addEventListener("open", () => {
126
+ socket.removeEventListener("error", onError);
127
+ resolve(new WebSocketConnection(socket));
128
+ });
129
+ });
130
+ };
131
+ async function connectWsFramed(endpoint) {
132
+ const {
133
+ url,
134
+ implementation: WebSocketImpl = WebSocket,
135
+ protocols
136
+ } = endpoint;
137
+ return new Promise((resolve, reject) => {
138
+ const socket = new WebSocketImpl(url, protocols);
139
+ socket.binaryType = "arraybuffer";
140
+ const onError = (event) => {
141
+ socket.removeEventListener("error", onError);
142
+ reject(eventToError(event));
143
+ };
144
+ socket.addEventListener("error", onError);
145
+ socket.addEventListener("open", () => {
146
+ socket.removeEventListener("error", onError);
147
+ resolve(new WebSocketConnectionFramed(socket));
148
+ });
149
+ });
150
+ }
151
+ export {
152
+ WebSocketConnection,
153
+ WebSocketConnectionClosedError,
154
+ WebSocketConnectionFramed,
155
+ connectWs,
156
+ connectWsFramed
157
+ };