@hla4ts/transport 0.1.0 → 0.1.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.
@@ -1,193 +1,193 @@
1
- /**
2
- * TLS Transport Implementation
3
- *
4
- * Provides a TLS-encrypted TCP connection for the Federate Protocol.
5
- * Uses Bun's built-in TLS support.
6
- */
7
-
8
- import { connect, type Socket, type SocketHandler, type TLSOptions } from "bun";
9
- import { FrameDecoder } from "./frame-decoder.ts";
10
- import { Ports, Protocol } from "./constants.ts";
11
- import type {
12
- Transport,
13
- TransportEvents,
14
- TlsOptions,
15
- ReceivedMessage,
16
- } from "./transport.ts";
17
-
18
- /**
19
- * TLS Transport for Federate Protocol
20
- *
21
- * @example
22
- * ```ts
23
- * const transport = new TlsTransport({
24
- * host: 'localhost',
25
- * port: 15165,
26
- * });
27
- *
28
- * transport.setEventHandlers({
29
- * onMessage: (msg) => console.log('Received:', msg.header.messageType),
30
- * onClose: () => console.log('Disconnected'),
31
- * onError: (err) => console.error('Error:', err),
32
- * });
33
- *
34
- * await transport.connect();
35
- * await transport.send(someData);
36
- * await transport.disconnect();
37
- * ```
38
- */
39
- export class TlsTransport implements Transport {
40
- private readonly options: Required<
41
- Pick<TlsOptions, "host" | "port" | "connectionTimeout" | "noDelay">
42
- > &
43
- Omit<TlsOptions, "host" | "port" | "connectionTimeout" | "noDelay">;
44
-
45
- private socket: Socket<{ decoder: FrameDecoder }> | null = null;
46
- private events: TransportEvents = {};
47
- private decoder: FrameDecoder;
48
- private connected = false;
49
-
50
- constructor(options: TlsOptions) {
51
- this.options = {
52
- host: options.host,
53
- port: options.port ?? Ports.TLS,
54
- connectionTimeout: options.connectionTimeout ?? 10000,
55
- noDelay: options.noDelay ?? true,
56
- ca: options.ca,
57
- cert: options.cert,
58
- key: options.key,
59
- rejectUnauthorized: options.rejectUnauthorized ?? true,
60
- serverName: options.serverName,
61
- };
62
-
63
- this.decoder = new FrameDecoder();
64
- this.decoder.onMessage = (msg) => this.handleMessage(msg);
65
- this.decoder.onError = (err) => this.handleError(err);
66
- }
67
-
68
- async connect(): Promise<void> {
69
- if (this.socket) {
70
- throw new Error("Already connected");
71
- }
72
-
73
- const decoder = this.decoder;
74
- const transport = this;
75
-
76
- return new Promise((resolve, reject) => {
77
- const timeoutId = setTimeout(() => {
78
- reject(new Error(`Connection timeout after ${this.options.connectionTimeout}ms`));
79
- }, this.options.connectionTimeout);
80
-
81
- const socketHandler: SocketHandler<{ decoder: FrameDecoder }> = {
82
- data(_socket, data) {
83
- decoder.push(new Uint8Array(data));
84
- },
85
- open(_socket) {
86
- clearTimeout(timeoutId);
87
- transport.connected = true;
88
- resolve();
89
- },
90
- close(_socket) {
91
- transport.connected = false;
92
- transport.socket = null;
93
- transport.events.onClose?.(false);
94
- },
95
- error(_socket, error) {
96
- clearTimeout(timeoutId);
97
- transport.connected = false;
98
- const err = error instanceof Error ? error : new Error(String(error));
99
- transport.events.onError?.(err);
100
- reject(err);
101
- },
102
- connectError(_socket, error) {
103
- clearTimeout(timeoutId);
104
- const err = error instanceof Error ? error : new Error(String(error));
105
- reject(err);
106
- },
107
- };
108
-
109
- // Build TLS options
110
- const tlsConfig: TLSOptions = {
111
- rejectUnauthorized: this.options.rejectUnauthorized,
112
- };
113
-
114
- // Add optional TLS settings
115
- if (this.options.ca) {
116
- tlsConfig.ca = Bun.file(this.options.ca);
117
- }
118
- if (this.options.cert) {
119
- tlsConfig.cert = Bun.file(this.options.cert);
120
- }
121
- if (this.options.key) {
122
- tlsConfig.key = Bun.file(this.options.key);
123
- }
124
- if (this.options.serverName) {
125
- tlsConfig.serverName = this.options.serverName;
126
- }
127
-
128
- connect({
129
- hostname: this.options.host,
130
- port: this.options.port,
131
- socket: socketHandler,
132
- data: { decoder },
133
- tls: tlsConfig,
134
- })
135
- .then((socket) => {
136
- this.socket = socket;
137
- })
138
- .catch(reject);
139
- });
140
- }
141
-
142
- async disconnect(): Promise<void> {
143
- if (this.socket) {
144
- this.socket.end();
145
- this.socket = null;
146
- this.connected = false;
147
- this.decoder.reset();
148
- }
149
- }
150
-
151
- isConnected(): boolean {
152
- return this.connected && this.socket !== null;
153
- }
154
-
155
- async send(data: Uint8Array): Promise<void> {
156
- if (!this.socket || !this.connected) {
157
- throw new Error("Not connected");
158
- }
159
-
160
- const written = this.socket.write(data);
161
- if (written < data.length) {
162
- // Bun's socket.write returns the number of bytes written
163
- // If not all bytes were written, we need to wait for drain
164
- // For simplicity, we'll throw an error here
165
- // In production, you'd want to buffer and retry
166
- throw new Error(
167
- `Failed to write all data: wrote ${written} of ${data.length} bytes`
168
- );
169
- }
170
- }
171
-
172
- setEventHandlers(handlers: TransportEvents): void {
173
- this.events = handlers;
174
- }
175
-
176
- getProtocolName(): string {
177
- return Protocol.TLS;
178
- }
179
-
180
- getRemoteAddress(): string | null {
181
- return this.socket
182
- ? `${this.options.host}:${this.options.port}`
183
- : null;
184
- }
185
-
186
- private handleMessage(message: ReceivedMessage): void {
187
- this.events.onMessage?.(message);
188
- }
189
-
190
- private handleError(error: Error): void {
191
- this.events.onError?.(error);
192
- }
193
- }
1
+ /**
2
+ * TLS Transport Implementation
3
+ *
4
+ * Provides a TLS-encrypted TCP connection for the Federate Protocol.
5
+ * Uses Bun's built-in TLS support.
6
+ */
7
+
8
+ import { connect, type Socket, type SocketHandler, type TLSOptions } from "bun";
9
+ import { FrameDecoder } from "./frame-decoder.ts";
10
+ import { Ports, Protocol } from "./constants.ts";
11
+ import type {
12
+ Transport,
13
+ TransportEvents,
14
+ TlsOptions,
15
+ ReceivedMessage,
16
+ } from "./transport.ts";
17
+
18
+ /**
19
+ * TLS Transport for Federate Protocol
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const transport = new TlsTransport({
24
+ * host: 'localhost',
25
+ * port: 15165,
26
+ * });
27
+ *
28
+ * transport.setEventHandlers({
29
+ * onMessage: (msg) => console.log('Received:', msg.header.messageType),
30
+ * onClose: () => console.log('Disconnected'),
31
+ * onError: (err) => console.error('Error:', err),
32
+ * });
33
+ *
34
+ * await transport.connect();
35
+ * await transport.send(someData);
36
+ * await transport.disconnect();
37
+ * ```
38
+ */
39
+ export class TlsTransport implements Transport {
40
+ private readonly options: Required<
41
+ Pick<TlsOptions, "host" | "port" | "connectionTimeout" | "noDelay">
42
+ > &
43
+ Omit<TlsOptions, "host" | "port" | "connectionTimeout" | "noDelay">;
44
+
45
+ private socket: Socket<{ decoder: FrameDecoder }> | null = null;
46
+ private events: TransportEvents = {};
47
+ private decoder: FrameDecoder;
48
+ private connected = false;
49
+
50
+ constructor(options: TlsOptions) {
51
+ this.options = {
52
+ host: options.host,
53
+ port: options.port ?? Ports.TLS,
54
+ connectionTimeout: options.connectionTimeout ?? 10000,
55
+ noDelay: options.noDelay ?? true,
56
+ ca: options.ca,
57
+ cert: options.cert,
58
+ key: options.key,
59
+ rejectUnauthorized: options.rejectUnauthorized ?? true,
60
+ serverName: options.serverName,
61
+ };
62
+
63
+ this.decoder = new FrameDecoder();
64
+ this.decoder.onMessage = (msg) => this.handleMessage(msg);
65
+ this.decoder.onError = (err) => this.handleError(err);
66
+ }
67
+
68
+ async connect(): Promise<void> {
69
+ if (this.socket) {
70
+ throw new Error("Already connected");
71
+ }
72
+
73
+ const decoder = this.decoder;
74
+ const transport = this;
75
+
76
+ return new Promise((resolve, reject) => {
77
+ const timeoutId = setTimeout(() => {
78
+ reject(new Error(`Connection timeout after ${this.options.connectionTimeout}ms`));
79
+ }, this.options.connectionTimeout);
80
+
81
+ const socketHandler: SocketHandler<{ decoder: FrameDecoder }> = {
82
+ data(_socket, data) {
83
+ decoder.push(new Uint8Array(data));
84
+ },
85
+ open(_socket) {
86
+ clearTimeout(timeoutId);
87
+ transport.connected = true;
88
+ resolve();
89
+ },
90
+ close(_socket) {
91
+ transport.connected = false;
92
+ transport.socket = null;
93
+ transport.events.onClose?.(false);
94
+ },
95
+ error(_socket, error) {
96
+ clearTimeout(timeoutId);
97
+ transport.connected = false;
98
+ const err = error instanceof Error ? error : new Error(String(error));
99
+ transport.events.onError?.(err);
100
+ reject(err);
101
+ },
102
+ connectError(_socket, error) {
103
+ clearTimeout(timeoutId);
104
+ const err = error instanceof Error ? error : new Error(String(error));
105
+ reject(err);
106
+ },
107
+ };
108
+
109
+ // Build TLS options
110
+ const tlsConfig: TLSOptions = {
111
+ rejectUnauthorized: this.options.rejectUnauthorized,
112
+ };
113
+
114
+ // Add optional TLS settings
115
+ if (this.options.ca) {
116
+ tlsConfig.ca = Bun.file(this.options.ca);
117
+ }
118
+ if (this.options.cert) {
119
+ tlsConfig.cert = Bun.file(this.options.cert);
120
+ }
121
+ if (this.options.key) {
122
+ tlsConfig.key = Bun.file(this.options.key);
123
+ }
124
+ if (this.options.serverName) {
125
+ tlsConfig.serverName = this.options.serverName;
126
+ }
127
+
128
+ connect({
129
+ hostname: this.options.host,
130
+ port: this.options.port,
131
+ socket: socketHandler,
132
+ data: { decoder },
133
+ tls: tlsConfig,
134
+ })
135
+ .then((socket) => {
136
+ this.socket = socket;
137
+ })
138
+ .catch(reject);
139
+ });
140
+ }
141
+
142
+ async disconnect(): Promise<void> {
143
+ if (this.socket) {
144
+ this.socket.end();
145
+ this.socket = null;
146
+ this.connected = false;
147
+ this.decoder.reset();
148
+ }
149
+ }
150
+
151
+ isConnected(): boolean {
152
+ return this.connected && this.socket !== null;
153
+ }
154
+
155
+ async send(data: Uint8Array): Promise<void> {
156
+ if (!this.socket || !this.connected) {
157
+ throw new Error("Not connected");
158
+ }
159
+
160
+ const written = this.socket.write(data);
161
+ if (written < data.length) {
162
+ // Bun's socket.write returns the number of bytes written
163
+ // If not all bytes were written, we need to wait for drain
164
+ // For simplicity, we'll throw an error here
165
+ // In production, you'd want to buffer and retry
166
+ throw new Error(
167
+ `Failed to write all data: wrote ${written} of ${data.length} bytes`
168
+ );
169
+ }
170
+ }
171
+
172
+ setEventHandlers(handlers: TransportEvents): void {
173
+ this.events = handlers;
174
+ }
175
+
176
+ getProtocolName(): string {
177
+ return Protocol.TLS;
178
+ }
179
+
180
+ getRemoteAddress(): string | null {
181
+ return this.socket
182
+ ? `${this.options.host}:${this.options.port}`
183
+ : null;
184
+ }
185
+
186
+ private handleMessage(message: ReceivedMessage): void {
187
+ this.events.onMessage?.(message);
188
+ }
189
+
190
+ private handleError(error: Error): void {
191
+ this.events.onError?.(error);
192
+ }
193
+ }
package/src/transport.ts CHANGED
@@ -1,99 +1,99 @@
1
- /**
2
- * Transport Interface
3
- *
4
- * Defines the contract for Federate Protocol transports (TCP, TLS, WebSocket).
5
- */
6
-
7
- import type { MessageHeader } from "./message-header.ts";
8
-
9
- /**
10
- * Transport connection options
11
- */
12
- export interface TransportOptions {
13
- /** Server hostname or IP address */
14
- host: string;
15
- /** Server port (defaults to Ports.TCP for TCP, Ports.TLS for TLS) */
16
- port?: number;
17
- /** Connection timeout in milliseconds */
18
- connectionTimeout?: number;
19
- /** Enable TCP_NODELAY (disable Nagle's algorithm) */
20
- noDelay?: boolean;
21
- }
22
-
23
- /**
24
- * TLS-specific options
25
- */
26
- export interface TlsOptions extends TransportOptions {
27
- /** Path to CA certificate file (for server verification) */
28
- ca?: string;
29
- /** Path to client certificate file (for mutual TLS) */
30
- cert?: string;
31
- /** Path to client private key file (for mutual TLS) */
32
- key?: string;
33
- /** Skip server certificate verification (INSECURE - for testing only) */
34
- rejectUnauthorized?: boolean;
35
- /** Server name for SNI */
36
- serverName?: string;
37
- }
38
-
39
- /**
40
- * Received message with header and payload
41
- */
42
- export interface ReceivedMessage {
43
- header: MessageHeader;
44
- payload: Uint8Array;
45
- }
46
-
47
- /**
48
- * Transport event handlers
49
- */
50
- export interface TransportEvents {
51
- /** Called when a complete message is received */
52
- onMessage?: (message: ReceivedMessage) => void;
53
- /** Called when the connection is closed */
54
- onClose?: (hadError: boolean) => void;
55
- /** Called when an error occurs */
56
- onError?: (error: Error) => void;
57
- }
58
-
59
- /**
60
- * Transport interface for Federate Protocol communication
61
- */
62
- export interface Transport {
63
- /**
64
- * Connect to the server
65
- * @throws Error if connection fails
66
- */
67
- connect(): Promise<void>;
68
-
69
- /**
70
- * Disconnect from the server
71
- */
72
- disconnect(): Promise<void>;
73
-
74
- /**
75
- * Check if the transport is connected
76
- */
77
- isConnected(): boolean;
78
-
79
- /**
80
- * Send raw bytes to the server
81
- * @param data The data to send
82
- */
83
- send(data: Uint8Array): Promise<void>;
84
-
85
- /**
86
- * Set event handlers
87
- */
88
- setEventHandlers(handlers: TransportEvents): void;
89
-
90
- /**
91
- * Get the protocol name (tcp, tls, websocket, websocketsecure)
92
- */
93
- getProtocolName(): string;
94
-
95
- /**
96
- * Get the remote address
97
- */
98
- getRemoteAddress(): string | null;
99
- }
1
+ /**
2
+ * Transport Interface
3
+ *
4
+ * Defines the contract for Federate Protocol transports (TCP, TLS, WebSocket).
5
+ */
6
+
7
+ import type { MessageHeader } from "./message-header.ts";
8
+
9
+ /**
10
+ * Transport connection options
11
+ */
12
+ export interface TransportOptions {
13
+ /** Server hostname or IP address */
14
+ host: string;
15
+ /** Server port (defaults to Ports.TCP for TCP, Ports.TLS for TLS) */
16
+ port?: number;
17
+ /** Connection timeout in milliseconds */
18
+ connectionTimeout?: number;
19
+ /** Enable TCP_NODELAY (disable Nagle's algorithm) */
20
+ noDelay?: boolean;
21
+ }
22
+
23
+ /**
24
+ * TLS-specific options
25
+ */
26
+ export interface TlsOptions extends TransportOptions {
27
+ /** Path to CA certificate file (for server verification) */
28
+ ca?: string;
29
+ /** Path to client certificate file (for mutual TLS) */
30
+ cert?: string;
31
+ /** Path to client private key file (for mutual TLS) */
32
+ key?: string;
33
+ /** Skip server certificate verification (INSECURE - for testing only) */
34
+ rejectUnauthorized?: boolean;
35
+ /** Server name for SNI */
36
+ serverName?: string;
37
+ }
38
+
39
+ /**
40
+ * Received message with header and payload
41
+ */
42
+ export interface ReceivedMessage {
43
+ header: MessageHeader;
44
+ payload: Uint8Array;
45
+ }
46
+
47
+ /**
48
+ * Transport event handlers
49
+ */
50
+ export interface TransportEvents {
51
+ /** Called when a complete message is received */
52
+ onMessage?: (message: ReceivedMessage) => void;
53
+ /** Called when the connection is closed */
54
+ onClose?: (hadError: boolean) => void;
55
+ /** Called when an error occurs */
56
+ onError?: (error: Error) => void;
57
+ }
58
+
59
+ /**
60
+ * Transport interface for Federate Protocol communication
61
+ */
62
+ export interface Transport {
63
+ /**
64
+ * Connect to the server
65
+ * @throws Error if connection fails
66
+ */
67
+ connect(): Promise<void>;
68
+
69
+ /**
70
+ * Disconnect from the server
71
+ */
72
+ disconnect(): Promise<void>;
73
+
74
+ /**
75
+ * Check if the transport is connected
76
+ */
77
+ isConnected(): boolean;
78
+
79
+ /**
80
+ * Send raw bytes to the server
81
+ * @param data The data to send
82
+ */
83
+ send(data: Uint8Array): Promise<void>;
84
+
85
+ /**
86
+ * Set event handlers
87
+ */
88
+ setEventHandlers(handlers: TransportEvents): void;
89
+
90
+ /**
91
+ * Get the protocol name (tcp, tls, websocket, websocketsecure)
92
+ */
93
+ getProtocolName(): string;
94
+
95
+ /**
96
+ * Get the remote address
97
+ */
98
+ getRemoteAddress(): string | null;
99
+ }