@aetherframework/websocket 1.0.0 → 1.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.
@@ -1,576 +0,0 @@
1
- /**
2
- * @license MIT
3
- * Copyright (c) 2026-present AetherFramework Contributors.
4
- * SPDX-License-Identifier: MIT
5
- * @module @aetherframework/src/drivers/http-driver
6
- */
7
-
8
-
9
- import http from "http";
10
- import crypto from "crypto";
11
- import EventEmitter from "events";
12
- import FrameEncoder from "../utils/frame-encoder.js"; // For encoding WebSocket frames
13
- import FrameParser from "../core/FrameParser.js"; // For parsing WebSocket frames
14
- import HandshakeHandler from "../core/HandshakeHandler.js";
15
-
16
- class HTTPDriver extends EventEmitter {
17
- /**
18
- * Constructor for HTTPDriver
19
- * @param {Object} config - Configuration object
20
- * @param {number} config.port - Server port (default: 80)
21
- * @param {string} config.host - Server host (default: "0.0.0.0")
22
- * @param {number} config.maxPayload - Maximum payload size in bytes (default: 1MB)
23
- * @param {number} config.pingInterval - Ping interval in milliseconds
24
- * @param {number} config.socketTimeout - Socket timeout in milliseconds
25
- * @param {boolean} config.compression - Enable WebSocket compression
26
- */
27
- constructor(config = {}) {
28
- super();
29
- this.config = {
30
- port: 80,
31
- host: "0.0.0.0",
32
- maxPayload: config.maxPayload || 1024 * 1024,
33
- ...config,
34
- };
35
-
36
- this.server = null;
37
- this.connections = new Map(); // Map of connectionId -> connection object
38
- }
39
-
40
- /**
41
- * Create an HTTP WebSocket server
42
- * @param {Object} options - Server options
43
- * @returns {Promise<Object>} Server instance with type, address, and close method
44
- */
45
- async createServer(options = {}) {
46
- return new Promise((resolve, reject) => {
47
- try {
48
- // Create HTTP server
49
- this.server = http.createServer((req, res) => {
50
- // Handle regular HTTP requests
51
- res.writeHead(200, { "Content-Type": "text/plain" });
52
- res.end("WebSocket server is running");
53
- });
54
-
55
- // Handle WebSocket upgrade requests
56
- this.server.on("upgrade", (req, socket, head) => {
57
- this._handleUpgrade(req, socket, head);
58
- });
59
-
60
- const serverOptions = {
61
- host: options.host || this.config.host,
62
- port: options.port || this.config.port,
63
- ...options,
64
- };
65
-
66
- // Start listening on specified port and host
67
- this.server.listen(serverOptions, () => {
68
- const address = this.server.address();
69
-
70
- // Emit server:listening event with server details
71
- this.emit("server:listening", {
72
- host: address.address,
73
- port: address.port,
74
- family: address.family,
75
- });
76
-
77
- resolve({
78
- type: "http",
79
- address: address,
80
- close: () => this.closeServer(),
81
- });
82
- });
83
-
84
- // Handle server errors
85
- this.server.on("error", (error) => {
86
- this.emit("error", {
87
- type: "server_error",
88
- message: "HTTP server error",
89
- error,
90
- });
91
- reject(error);
92
- });
93
- } catch (error) {
94
- reject(error);
95
- }
96
- });
97
- }
98
-
99
- /**
100
- * Handle HTTP upgrade to WebSocket
101
- * @param {http.IncomingMessage} req - HTTP request object
102
- * @param {net.Socket} socket - Raw TCP socket
103
- * @param {Buffer} head - First packet of the upgraded stream
104
- * @private
105
- */
106
- _handleUpgrade(req, socket, head) {
107
- // Validate WebSocket upgrade request
108
- if (!this._isValidWebSocketUpgrade(req)) {
109
- socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
110
- socket.destroy();
111
- return;
112
- }
113
-
114
- // Perform WebSocket handshake
115
- const key = req.headers["sec-websocket-key"];
116
- const acceptKey = crypto
117
- .createHash("sha1")
118
- .update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
119
- .digest("base64");
120
-
121
- const headers = [
122
- "HTTP/1.1 101 Switching Protocols",
123
- "Upgrade: websocket",
124
- "Connection: Upgrade",
125
- `Sec-WebSocket-Accept: ${acceptKey}`,
126
- ];
127
-
128
- // Add optional protocol header if requested
129
- if (req.headers["sec-websocket-protocol"]) {
130
- const protocol = req.headers["sec-websocket-protocol"];
131
- headers.push(`Sec-WebSocket-Protocol: ${protocol}`);
132
- }
133
-
134
- // Add optional extensions header if compression is enabled
135
- if (this.config.compression && req.headers["sec-websocket-extensions"]) {
136
- headers.push("Sec-WebSocket-Extensions: permessage-deflate");
137
- }
138
-
139
- const response = headers.join("\r\n") + "\r\n\r\n";
140
- socket.write(response);
141
-
142
- // Create connection object
143
- const connectionId = crypto.randomUUID();
144
- const connection = {
145
- id: connectionId,
146
- socket: socket,
147
- req: req,
148
- readyState: 1, // OPEN state
149
- protocol: req.headers["sec-websocket-protocol"] || null,
150
- extensions: req.headers["sec-websocket-extensions"] || null,
151
- remoteAddress: req.socket.remoteAddress,
152
- remotePort: req.socket.remotePort,
153
- headers: req.headers,
154
- url: req.url,
155
- };
156
-
157
- this.connections.set(connectionId, connection);
158
-
159
- // Configure socket options
160
- socket.setNoDelay(true);
161
- socket.setKeepAlive(true, 10000);
162
- socket.setTimeout(this.config.socketTimeout || 30000);
163
-
164
- // Process any buffered data from the upgrade
165
- if (head && head.length > 0) {
166
- this._handleWebSocketData(socket, head);
167
- }
168
-
169
- // Set up socket event handlers
170
- socket.on("data", (data) => {
171
- this._handleWebSocketData(socket, data);
172
- });
173
-
174
- socket.on("close", () => {
175
- this.emit("close", connection, 1006, "Connection closed");
176
- this._removeConnection(connectionId);
177
- });
178
-
179
- socket.on("error", (error) => {
180
- this.emit("error", {
181
- type: "socket_error",
182
- message: "HTTP socket error",
183
- error,
184
- connectionId,
185
- });
186
- this._removeConnection(connectionId);
187
- });
188
-
189
- socket.on("timeout", () => {
190
- socket.end();
191
- this._removeConnection(connectionId);
192
- });
193
-
194
- // Emit connection event
195
- this.emit("connection", connection);
196
-
197
- // Set up heartbeat/ping interval if configured
198
- if (this.config.pingInterval) {
199
- const pingInterval = setInterval(() => {
200
- if (connection.readyState === 1) {
201
- this._sendPing(socket);
202
- } else {
203
- clearInterval(pingInterval);
204
- }
205
- }, this.config.pingInterval);
206
-
207
- connection.pingInterval = pingInterval;
208
- }
209
- }
210
-
211
- /**
212
- * Validate WebSocket upgrade request
213
- * @param {http.IncomingMessage} req - HTTP request
214
- * @returns {boolean} Whether request is a valid WebSocket upgrade
215
- * @private
216
- */
217
- _isValidWebSocketUpgrade(req) {
218
- const upgrade = req.headers.upgrade;
219
- const connection = req.headers.connection;
220
- const key = req.headers["sec-websocket-key"];
221
- const version = req.headers["sec-websocket-version"];
222
-
223
- return (
224
- upgrade &&
225
- upgrade.toLowerCase() === "websocket" &&
226
- connection &&
227
- connection.toLowerCase().includes("upgrade") &&
228
- key &&
229
- key.length === 24 &&
230
- version === "13"
231
- );
232
- }
233
-
234
- /**
235
- * Handle WebSocket data frames
236
- * @param {net.Socket} socket - Raw socket
237
- * @param {Buffer} data - Data buffer containing WebSocket frames
238
- * @private
239
- */
240
- _handleWebSocketData(socket, data) {
241
- const connection = this.connections.get(socket);
242
- const connectionId = connection?.id || 'unknown';
243
-
244
- try {
245
- // Parse WebSocket frames from the data buffer
246
- const result = FrameParser.parse(data, this.config.maxPayload);
247
-
248
- // Process all parsed frames
249
- for (const frame of result.frames) {
250
- this._processWebSocketFrame(socket, frame, connection);
251
- }
252
-
253
- } catch (error) {
254
- console.error(`Frame parsing error for connection ${connectionId}:`, error);
255
-
256
- this.emit("error", {
257
- type: "frame_parse_error",
258
- message: "Failed to parse WebSocket frame",
259
- error,
260
- connectionId,
261
- });
262
-
263
- // Send close frame and close connection on protocol error
264
- this._sendCloseFrame(socket, 1002, "Protocol error");
265
- }
266
- }
267
-
268
- /**
269
- * Process a single WebSocket frame
270
- * @param {net.Socket} socket - Raw socket
271
- * @param {Object} frame - Parsed frame object
272
- * @param {Object} connection - Connection object
273
- * @private
274
- */
275
- _processWebSocketFrame(socket, frame, connection) {
276
- if (!connection) return;
277
-
278
- switch (frame.opcode) {
279
- case 0x1: // TEXT frame
280
- this.emit(
281
- "message",
282
- connection,
283
- frame.payload.toString("utf8"),
284
- false,
285
- );
286
- break;
287
- case 0x2: // BINARY frame
288
- this.emit(
289
- "message",
290
- connection,
291
- frame.payload,
292
- true,
293
- );
294
- break;
295
- case 0x8: // CLOSE frame
296
- this._handleCloseFrame(socket, frame);
297
- break;
298
- case 0x9: // PING frame
299
- this._handlePingFrame(socket, frame);
300
- break;
301
- case 0xa: // PONG frame
302
- this._handlePongFrame(socket, frame);
303
- break;
304
- case 0x0: // CONTINUATION frame
305
- this._handleContinuationFrame(socket, frame);
306
- break;
307
- default:
308
- console.warn(`Unknown WebSocket opcode: 0x${frame.opcode.toString(16)}`);
309
- }
310
- }
311
-
312
- /**
313
- * Handle Close frame
314
- * @param {net.Socket} socket - Raw socket
315
- * @param {Object} frame - Parsed frame object
316
- * @private
317
- */
318
- _handleCloseFrame(socket, frame) {
319
- const connection = this.connections.get(socket);
320
- if (!connection) return;
321
-
322
- let code = 1000; // Default close code
323
- let reason = "";
324
-
325
- // Parse close code and reason from payload
326
- if (frame.payload && frame.payload.length >= 2) {
327
- code = frame.payload.readUInt16BE(0);
328
- if (frame.payload.length > 2) {
329
- reason = frame.payload.slice(2).toString("utf8");
330
- }
331
- }
332
-
333
- // Send close frame response
334
- this._sendCloseFrame(socket, code, reason);
335
-
336
- // Update connection state
337
- connection.readyState = 3; // CLOSED
338
-
339
- // Emit close event
340
- this.emit("close", connection, code, reason);
341
-
342
- // Close socket
343
- socket.end();
344
-
345
- // Remove connection from active connections
346
- this._removeConnection(connection.id);
347
- }
348
-
349
- /**
350
- * Handle Ping frame
351
- * @param {net.Socket} socket - Raw socket
352
- * @param {Object} frame - Parsed frame object
353
- * @private
354
- */
355
- _handlePingFrame(socket, frame) {
356
- // Echo back the ping payload as pong (RFC 6455 requirement)
357
- this._sendPong(socket, frame.payload);
358
-
359
- // Emit ping event for monitoring
360
- const connection = this.connections.get(socket);
361
- if (connection) {
362
- this.emit("ping", connection, frame.payload);
363
- }
364
- }
365
-
366
- /**
367
- * Handle Pong frame
368
- * @param {net.Socket} socket - Raw socket
369
- * @param {Object} frame - Parsed frame object
370
- * @private
371
- */
372
- _handlePongFrame(socket, frame) {
373
- // Emit pong event for monitoring
374
- const connection = this.connections.get(socket);
375
- if (connection) {
376
- this.emit("pong", connection, frame.payload);
377
- }
378
- }
379
-
380
- /**
381
- * Handle Continuation frame (for fragmented messages)
382
- * @param {net.Socket} socket - Raw socket
383
- * @param {Object} frame - Parsed frame object
384
- * @private
385
- */
386
- _handleContinuationFrame(socket, frame) {
387
- const connection = this.connections.get(socket);
388
- if (!connection) return;
389
-
390
- // Store continuation frame data
391
- if (!connection.fragmentedData) {
392
- connection.fragmentedData = [];
393
- }
394
-
395
- connection.fragmentedData.push(frame.payload);
396
-
397
- // If this is the final fragment, combine and emit
398
- if (frame.fin) {
399
- const completeData = Buffer.concat(connection.fragmentedData);
400
- const isBinary = connection.fragmentedOpcode === 0x2;
401
-
402
- this.emit(
403
- "message",
404
- connection,
405
- isBinary ? completeData : completeData.toString("utf8"),
406
- isBinary,
407
- );
408
-
409
- // Clean up fragmented data storage
410
- delete connection.fragmentedData;
411
- delete connection.fragmentedOpcode;
412
- }
413
- }
414
-
415
- /**
416
- * Send ping frame to client
417
- * @param {net.Socket} socket - Raw socket
418
- * @private
419
- */
420
- _sendPing(socket) {
421
- const pingFrame = FrameEncoder.createPingFrame();
422
- socket.write(pingFrame);
423
- }
424
-
425
- /**
426
- * Send pong frame to client
427
- * @param {net.Socket} socket - Raw socket
428
- * @param {Buffer} payload - Ping payload to echo back
429
- * @private
430
- */
431
- _sendPong(socket, payload) {
432
- const pongFrame = FrameEncoder.createPongFrame(payload);
433
- socket.write(pongFrame);
434
- }
435
-
436
- /**
437
- * Send close frame to client
438
- * @param {net.Socket} socket - Raw socket
439
- * @param {number} code - Close status code
440
- * @param {string} reason - Close reason
441
- * @private
442
- */
443
- _sendCloseFrame(socket, code, reason) {
444
- const closeFrame = FrameEncoder.createCloseFrame(code, reason);
445
- socket.write(closeFrame);
446
- }
447
-
448
- /**
449
- * Remove connection from active connections
450
- * @param {string} connectionId - Connection ID
451
- * @private
452
- */
453
- _removeConnection(connectionId) {
454
- const connection = this.connections.get(connectionId);
455
- if (connection) {
456
- // Clear ping interval if exists
457
- if (connection.pingInterval) {
458
- clearInterval(connection.pingInterval);
459
- }
460
- this.connections.delete(connectionId);
461
- }
462
- }
463
-
464
- /**
465
- * Create a WebSocket client over HTTP
466
- * @param {string} url - WebSocket URL (ws://)
467
- * @param {Object} options - Client options
468
- * @returns {Promise<Object>} Client connection object
469
- */
470
- async createClient(url, options = {}) {
471
- const urlObj = new URL(url);
472
- const host = urlObj.hostname;
473
- const port = parseInt(urlObj.port) || 80;
474
- const path = urlObj.pathname + urlObj.search;
475
-
476
- return new Promise((resolve, reject) => {
477
- const req = http.request({
478
- hostname: host,
479
- port: port,
480
- path: path,
481
- method: "GET",
482
- headers: {
483
- Upgrade: "websocket",
484
- Connection: "Upgrade",
485
- "Sec-WebSocket-Key": crypto.randomBytes(16).toString("base64"),
486
- "Sec-WebSocket-Version": "13",
487
- ...options.headers,
488
- },
489
- });
490
-
491
- req.on("upgrade", (res, socket, head) => {
492
- const connection = {
493
- id: crypto.randomUUID(),
494
- socket: socket,
495
- readyState: 1,
496
- send: (data) => this._sendData(socket, data),
497
- close: (code, reason) => {
498
- this._sendCloseFrame(socket, code, reason);
499
- socket.end();
500
- },
501
- };
502
-
503
- // Handle incoming data from server
504
- socket.on("data", (data) => {
505
- this._handleWebSocketData(socket, data);
506
- });
507
-
508
- // Process any buffered data from the upgrade
509
- if (head && head.length > 0) {
510
- this._handleWebSocketData(socket, head);
511
- }
512
-
513
- resolve(connection);
514
- });
515
-
516
- req.on("error", reject);
517
- req.end();
518
- });
519
- }
520
-
521
- /**
522
- * Send data through socket as WebSocket frame
523
- * @param {net.Socket} socket - Raw socket
524
- * @param {string|Buffer} data - Data to send
525
- * @private
526
- */
527
- _sendData(socket, data) {
528
- const frame = FrameEncoder.encode(
529
- 0x1, // Text frame opcode
530
- Buffer.isBuffer(data) ? data : Buffer.from(data, "utf8"),
531
- );
532
- socket.write(frame);
533
- }
534
-
535
- /**
536
- * Close the HTTP server gracefully
537
- * @returns {Promise<void>}
538
- */
539
- async closeServer() {
540
- return new Promise((resolve) => {
541
- if (this.server) {
542
- // Close all active connections
543
- this.connections.forEach((connection) => {
544
- if (connection.readyState === 1) {
545
- this._sendCloseFrame(connection.socket, 1001, "Server going away");
546
- connection.socket.end();
547
- }
548
- });
549
-
550
- // Close the server
551
- this.server.close(() => {
552
- this.connections.clear();
553
- resolve();
554
- });
555
- } else {
556
- resolve();
557
- }
558
- });
559
- }
560
-
561
- /**
562
- * Get server statistics
563
- * @returns {Object} Statistics object containing connection count, uptime, etc.
564
- */
565
- getStats() {
566
- return {
567
- connections: this.connections.size,
568
- uptime: this.server ? Date.now() - this.server.startTime : 0,
569
- host: this.config.host,
570
- port: this.config.port,
571
- driver: "http",
572
- };
573
- }
574
- }
575
-
576
- export default HTTPDriver;
@@ -1,29 +0,0 @@
1
- /**
2
- * @license MIT
3
- * Copyright (c) 2026-present AetherFramework Contributors.
4
- * SPDX-License-Identifier: MIT
5
- * @module @aetherframework/src/drivers/index
6
- */
7
-
8
- const drivers = {
9
- tcp: () => import('./tcp-driver.js'),
10
- tls: () => import('./tls-driver.js'),
11
- http: () => import('./http-driver.js'),
12
- memory: () => import('./memory-driver.js')
13
- };
14
-
15
- /**
16
- * Get driver class by name
17
- * @param {string} name - Driver name
18
- * @returns {Promise<Class>} Driver class
19
- */
20
- export async function getDriver(name) {
21
- const loader = drivers[name];
22
- if (!loader) {
23
- throw new Error(`Unsupported driver: ${name}. Available: ${Object.keys(drivers).join(', ')}`);
24
- }
25
- const module = await loader();
26
- return module.default;
27
- }
28
-
29
- export default { getDriver };