@aetherframework/websocket 1.0.1 → 1.0.3
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/examples/basic-server.js +95 -0
- package/examples/chat-server.js +416 -0
- package/examples/client-test.js +58 -0
- package/examples/realtime-api.js +98 -0
- package/examples//342/200/214stress-test-server.js +495 -0
- package/package.json +3 -1
- package/src/core/ConnectionManager.js +213 -0
- package/src/core/FrameParser.js +115 -0
- package/src/core/HandshakeHandler.js +93 -0
- package/src/core/ProtocolHandler.js +186 -0
- package/src/core/WebSocketFactory.js +293 -0
- package/src/drivers/http-driver.js +576 -0
- package/src/drivers/index.js +29 -0
- package/src/drivers/memory-driver.js +422 -0
- package/src/drivers/tcp-driver.js +471 -0
- package/src/drivers/tls-driver.js +502 -0
- package/src/middleware/auth-middleware.js +37 -0
- package/src/middleware/broadcast-manager.js +173 -0
- package/src/middleware/compression.js +194 -0
- package/src/middleware/message-logger.js +322 -0
- package/src/middleware/rate-limiter.js +142 -0
- package/src/utils/config-loader.js +183 -0
- package/src/utils/connection-pool.js +110 -0
- package/src/utils/error-handler.js +59 -0
- package/src/utils/frame-encoder.js +211 -0
- package/src/utils/heartbeat-manager.js +91 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license MIT
|
|
3
|
+
* Copyright (c) 2026-present AetherFramework Contributors.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
* @module @aetherframework/src/core/WebSocketFactory
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import EventEmitter from 'events';
|
|
9
|
+
import ConnectionManager from './ConnectionManager.js';
|
|
10
|
+
import FrameParser from './FrameParser.js';
|
|
11
|
+
import ProtocolHandler from './ProtocolHandler.js';
|
|
12
|
+
import ConfigLoader from '../utils/config-loader.js';
|
|
13
|
+
|
|
14
|
+
class WebSocketFactory extends EventEmitter {
|
|
15
|
+
/**
|
|
16
|
+
* Create a new WebSocketFactory instance
|
|
17
|
+
* @param {Object} options - Configuration options
|
|
18
|
+
*/
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
super();
|
|
21
|
+
|
|
22
|
+
// Load configuration from environment and options
|
|
23
|
+
this.config = ConfigLoader.load({
|
|
24
|
+
// Default configuration
|
|
25
|
+
driver: 'tcp',
|
|
26
|
+
port: 8080,
|
|
27
|
+
host: '0.0.0.0',
|
|
28
|
+
maxPayload: 1024 * 1024, // 1MB
|
|
29
|
+
pingInterval: 30000, // 30 seconds
|
|
30
|
+
pingTimeout: 10000, // 10 seconds
|
|
31
|
+
compression: false,
|
|
32
|
+
tls: false,
|
|
33
|
+
tlsOptions: {},
|
|
34
|
+
// Merge with user options
|
|
35
|
+
...options
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
this.connectionManager = new ConnectionManager();
|
|
39
|
+
this.frameParser = new FrameParser(this.config);
|
|
40
|
+
this.protocolHandler = new ProtocolHandler(this.config);
|
|
41
|
+
this.driver = null;
|
|
42
|
+
this.server = null;
|
|
43
|
+
this.middleware = [];
|
|
44
|
+
|
|
45
|
+
// Initialize driver
|
|
46
|
+
this._initializeDriver();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Initialize the selected transport driver
|
|
51
|
+
* @private
|
|
52
|
+
*/
|
|
53
|
+
async _initializeDriver() {
|
|
54
|
+
try {
|
|
55
|
+
const driverName = this.config.driver || 'tcp';
|
|
56
|
+
const driverModule = await import(`../drivers/${driverName}-driver.js`);
|
|
57
|
+
this.driver = new driverModule.default(this.config);
|
|
58
|
+
|
|
59
|
+
// Set up driver event forwarding
|
|
60
|
+
this._setupDriverEvents();
|
|
61
|
+
|
|
62
|
+
this.emit('driver:initialized', { driver: driverName });
|
|
63
|
+
} catch (error) {
|
|
64
|
+
this.emit('error', {
|
|
65
|
+
type: 'driver_initialization',
|
|
66
|
+
message: `Failed to initialize driver: ${error.message}`,
|
|
67
|
+
error
|
|
68
|
+
});
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Set up event forwarding from driver to factory
|
|
75
|
+
* @private
|
|
76
|
+
*/
|
|
77
|
+
_setupDriverEvents() {
|
|
78
|
+
if (!this.driver) return;
|
|
79
|
+
|
|
80
|
+
// Forward all driver events to factory
|
|
81
|
+
const eventsToForward = [
|
|
82
|
+
'server:listening',
|
|
83
|
+
'server:closed',
|
|
84
|
+
'server:error',
|
|
85
|
+
'connection',
|
|
86
|
+
'message',
|
|
87
|
+
'close',
|
|
88
|
+
'error',
|
|
89
|
+
'ping',
|
|
90
|
+
'pong'
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
eventsToForward.forEach(event => {
|
|
94
|
+
this.driver.on(event, (...args) => {
|
|
95
|
+
this.emit(event, ...args);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Create a WebSocket server
|
|
102
|
+
* @param {Object} options - Server-specific options
|
|
103
|
+
* @returns {Promise<Object>} Server instance
|
|
104
|
+
*/
|
|
105
|
+
async createServer(options = {}) {
|
|
106
|
+
if (!this.driver) {
|
|
107
|
+
await this._initializeDriver();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const serverOptions = {
|
|
111
|
+
...this.config,
|
|
112
|
+
...options
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
this.server = await this.driver.createServer(serverOptions);
|
|
117
|
+
|
|
118
|
+
// CRITICAL FIX: Ensure server:listening event is emitted
|
|
119
|
+
// Get server address information
|
|
120
|
+
let addressInfo;
|
|
121
|
+
if (this.server.address && typeof this.server.address === 'function') {
|
|
122
|
+
addressInfo = this.server.address();
|
|
123
|
+
} else if (this.server.address) {
|
|
124
|
+
addressInfo = this.server.address;
|
|
125
|
+
} else {
|
|
126
|
+
addressInfo = {
|
|
127
|
+
address: serverOptions.host || '0.0.0.0',
|
|
128
|
+
port: serverOptions.port || 8080,
|
|
129
|
+
family: 'IPv4'
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Emit server:listening event with address information
|
|
134
|
+
// Use setTimeout to ensure event listeners are registered
|
|
135
|
+
setTimeout(() => {
|
|
136
|
+
this.emit('server:listening', {
|
|
137
|
+
host: addressInfo.address,
|
|
138
|
+
port: addressInfo.port,
|
|
139
|
+
family: addressInfo.family || 'IPv4'
|
|
140
|
+
});
|
|
141
|
+
}, 0);
|
|
142
|
+
|
|
143
|
+
this.emit('server:created', this.server);
|
|
144
|
+
return this.server;
|
|
145
|
+
} catch (error) {
|
|
146
|
+
this.emit('error', {
|
|
147
|
+
type: 'server_creation',
|
|
148
|
+
message: `Failed to create server: ${error.message}`,
|
|
149
|
+
error
|
|
150
|
+
});
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Create a WebSocket client connection
|
|
157
|
+
* @param {string} url - WebSocket URL (ws:// or wss://)
|
|
158
|
+
* @param {Object} options - Client options
|
|
159
|
+
* @returns {Promise<Object>} Client connection
|
|
160
|
+
*/
|
|
161
|
+
async createClient(url, options = {}) {
|
|
162
|
+
if (!this.driver) {
|
|
163
|
+
await this._initializeDriver();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const clientOptions = {
|
|
167
|
+
...this.config,
|
|
168
|
+
...options
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const client = await this.driver.createClient(url, clientOptions);
|
|
173
|
+
this.emit('client:created', client);
|
|
174
|
+
return client;
|
|
175
|
+
} catch (error) {
|
|
176
|
+
this.emit('error', {
|
|
177
|
+
type: 'client_creation',
|
|
178
|
+
message: `Failed to create client: ${error.message}`,
|
|
179
|
+
error
|
|
180
|
+
});
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Add middleware to the WebSocket pipeline
|
|
187
|
+
* @param {Function} middleware - Middleware function
|
|
188
|
+
*/
|
|
189
|
+
use(middleware) {
|
|
190
|
+
this.middleware.push(middleware);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Broadcast message to all connected clients
|
|
195
|
+
* @param {string|Buffer} message - Message to broadcast
|
|
196
|
+
* @param {Function} filter - Optional filter function
|
|
197
|
+
*/
|
|
198
|
+
broadcast(message, filter = null) {
|
|
199
|
+
const connections = this.connectionManager.getAll();
|
|
200
|
+
|
|
201
|
+
connections.forEach(connection => {
|
|
202
|
+
if (!filter || filter(connection)) {
|
|
203
|
+
try {
|
|
204
|
+
connection.send(message);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
this.emit('error', {
|
|
207
|
+
type: 'broadcast',
|
|
208
|
+
message: `Failed to broadcast to connection ${connection.id}`,
|
|
209
|
+
error,
|
|
210
|
+
connection
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Send message to specific connection
|
|
219
|
+
* @param {string} connectionId - Connection ID
|
|
220
|
+
* @param {string|Buffer} message - Message to send
|
|
221
|
+
* @returns {boolean} Success status
|
|
222
|
+
*/
|
|
223
|
+
sendTo(connectionId, message) {
|
|
224
|
+
const connection = this.connectionManager.get(connectionId);
|
|
225
|
+
if (connection && connection.readyState === 1) { // OPEN
|
|
226
|
+
return connection.send(message);
|
|
227
|
+
}
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get connection by ID
|
|
233
|
+
* @param {string} connectionId - Connection ID
|
|
234
|
+
* @returns {Object|null} Connection object or null
|
|
235
|
+
*/
|
|
236
|
+
getConnection(connectionId) {
|
|
237
|
+
return this.connectionManager.get(connectionId);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Get all connections
|
|
242
|
+
* @returns {Array} Array of connection objects
|
|
243
|
+
*/
|
|
244
|
+
getConnections() {
|
|
245
|
+
return this.connectionManager.getAll();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get connection statistics
|
|
250
|
+
* @returns {Object} Statistics object
|
|
251
|
+
*/
|
|
252
|
+
getStats() {
|
|
253
|
+
const connections = this.connectionManager.getAll();
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
totalConnections: connections.length,
|
|
257
|
+
activeConnections: connections.filter(c => c.readyState === 1).length,
|
|
258
|
+
driver: this.config.driver,
|
|
259
|
+
uptime: this.server ? Date.now() - this.server.startTime : 0,
|
|
260
|
+
memoryUsage: process.memoryUsage(),
|
|
261
|
+
config: {
|
|
262
|
+
maxPayload: this.config.maxPayload,
|
|
263
|
+
pingInterval: this.config.pingInterval,
|
|
264
|
+
pingTimeout: this.config.pingTimeout,
|
|
265
|
+
compression: this.config.compression
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Close the WebSocket server
|
|
272
|
+
* @returns {Promise<void>}
|
|
273
|
+
*/
|
|
274
|
+
async close() {
|
|
275
|
+
if (this.server) {
|
|
276
|
+
await this.driver.closeServer(this.server);
|
|
277
|
+
this.emit('server:closed');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Close all connections
|
|
281
|
+
const connections = this.connectionManager.getAll();
|
|
282
|
+
connections.forEach(connection => {
|
|
283
|
+
if (connection.readyState === 1) {
|
|
284
|
+
connection.close(1000, 'Server shutting down');
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
this.connectionManager.clear();
|
|
289
|
+
this.emit('closed');
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export default WebSocketFactory;
|