@aetherframework/websocket 1.0.0 โ†’ 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026-present, AetherFramework Contributors.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,95 @@
1
+ /**
2
+ * @file Basic WebSocket Server Example - Final Version
3
+ */
4
+
5
+ import { WebSocketFactory } from '../index.js';
6
+
7
+ async function main() {
8
+ try {
9
+ // Create WebSocket factory instance
10
+ const factory = new WebSocketFactory({
11
+ driver: 'http', // Use HTTP upgrade driver
12
+ port: process.env.WS_PORT || 8080,
13
+ host: process.env.WS_HOST || '0.0.0.0',
14
+ maxPayload: 1024 * 1024, // 1MB
15
+ pingInterval: 30000, // 30 seconds heartbeat
16
+ pingTimeout: 10000 // 10 seconds timeout
17
+ });
18
+
19
+ // Create server
20
+ const server = await factory.createServer();
21
+
22
+ console.log('โœ… WebSocket server starting...');
23
+
24
+ // Listen for connection events
25
+ factory.on('connection', (connection) => {
26
+ console.log(`๐Ÿ”— Client connected: ${connection.id} from ${connection.remoteAddress || 'unknown'}`);
27
+
28
+ // Send welcome message
29
+ connection.send(JSON.stringify({
30
+ type: 'welcome',
31
+ message: 'Connected to WebSocket server',
32
+ connectionId: connection.id,
33
+ timestamp: Date.now()
34
+ }));
35
+
36
+ // Listen for message events
37
+ connection.on('message', (data, isBinary) => {
38
+ const message = isBinary ? '[Binary Data]' : data.toString();
39
+ console.log(`๐Ÿ“จ Received from ${connection.id}: ${message}`);
40
+
41
+ // Echo back the message
42
+ connection.send(`Echo: ${message}`);
43
+ });
44
+
45
+ // Listen for close events
46
+ connection.on('close', (code, reason) => {
47
+ console.log(`โŒ Client disconnected: ${connection.id}, code: ${code}, reason: ${reason}`);
48
+ });
49
+
50
+ // Listen for error events
51
+ connection.on('error', (error) => {
52
+ console.error(`โš ๏ธ Connection error for ${connection.id}:`, error);
53
+ });
54
+ });
55
+
56
+ // Listen for server listening event
57
+ factory.on('server:listening', (address) => {
58
+ console.log(`๐Ÿš€ Server listening on ws://${address.host}:${address.port}`);
59
+ console.log('๐Ÿ“ก Waiting for connections...');
60
+ console.log('๐Ÿ›‘ Press Ctrl+C to stop the server');
61
+ });
62
+
63
+ // Listen for server error events
64
+ factory.on('error', (error) => {
65
+ console.error('๐Ÿ’ฅ Server error:', error);
66
+ });
67
+
68
+ // Graceful shutdown handling
69
+ process.on('SIGINT', async () => {
70
+ console.log('\n๐Ÿ”„ Shutting down WebSocket server...');
71
+ try {
72
+ await factory.close();
73
+ console.log('โœ… Server closed successfully');
74
+ process.exit(0);
75
+ } catch (error) {
76
+ console.error('โŒ Error closing server:', error);
77
+ process.exit(1);
78
+ }
79
+ });
80
+
81
+ // Keep process running
82
+ process.on('SIGTERM', async () => {
83
+ console.log('\n๐Ÿ”„ Received SIGTERM, shutting down...');
84
+ await factory.close();
85
+ process.exit(0);
86
+ });
87
+
88
+ } catch (error) {
89
+ console.error('โŒ Failed to start server:', error);
90
+ process.exit(1);
91
+ }
92
+ }
93
+
94
+ // Start the server
95
+ main();
@@ -0,0 +1,416 @@
1
+ /**
2
+ * @file Chat Server Example using Rooms
3
+ * @description Complete chat server with room-based broadcasting with event fixes
4
+ */
5
+
6
+ import { WebSocketFactory } from '../index.js';
7
+ import BroadcastManager from '../src/middleware/broadcast-manager.js';
8
+
9
+ async function main() {
10
+ try {
11
+ console.log('๐Ÿš€ Starting chat server...');
12
+ console.log(`๐Ÿ“ก Port: ${process.env.WS_PORT || 3000}`);
13
+ console.log(`๐Ÿ  Host: ${process.env.WS_HOST || '0.0.0.0'}`);
14
+
15
+ // Create WebSocket factory instance
16
+ const factory = new WebSocketFactory({
17
+ driver: 'http',
18
+ port: process.env.WS_PORT || 3000,
19
+ host: process.env.WS_HOST || '0.0.0.0',
20
+ maxPayload: 1024 * 1024,
21
+ pingInterval: 30000,
22
+ pingTimeout: 10000
23
+ });
24
+
25
+ // Create broadcast manager
26
+ const broadcastMgr = new BroadcastManager();
27
+
28
+ // Track if server:listening event has been fired
29
+ let serverListeningEventFired = false;
30
+
31
+ // Listen for server listening event
32
+ factory.on('server:listening', (address) => {
33
+ serverListeningEventFired = true;
34
+ console.log(`\n๐ŸŽ‰ ============================================`);
35
+ console.log(`๐Ÿš€ Chat server listening on ws://${address.host}:${address.port}`);
36
+ console.log(`๐Ÿ’ฌ Room-based chat server ready for connections`);
37
+ console.log(`๐Ÿ›‘ Press Ctrl+C to stop the server`);
38
+ console.log(`============================================\n`);
39
+ });
40
+
41
+ // Listen for driver initialization (with duplicate prevention)
42
+ let driverInitialized = false;
43
+ factory.on('driver:initialized', (info) => {
44
+ if (!driverInitialized) {
45
+ driverInitialized = true;
46
+ console.log(`๐Ÿ”ง Driver initialized: ${info.driver}`);
47
+ if (factory.connectionManager) {
48
+ console.log('๐Ÿ”— Connection manager initialized');
49
+ }
50
+ }
51
+ });
52
+
53
+ // Listen for connection events
54
+ factory.on('connection', (connection) => {
55
+ console.log(`๐Ÿ‘ค User connected: ${connection.id}`);
56
+
57
+ // Apply broadcast manager middleware
58
+ broadcastMgr.middleware()(connection, () => {
59
+ console.log(`โœ… Broadcast middleware applied for ${connection.id}`);
60
+ });
61
+
62
+ // Send welcome message
63
+ connection.send(JSON.stringify({
64
+ type: 'system',
65
+ message: 'Welcome to Chat Server!',
66
+ commands: {
67
+ join: 'Send {"type":"join","room":"room-name"} to join a room',
68
+ chat: 'Send {"type":"chat","room":"room-name","message":"your message"} to send message',
69
+ leave: 'Send {"type":"leave","room":"room-name"} to leave a room',
70
+ list: 'Send {"type":"list"} to list all rooms'
71
+ },
72
+ connectionId: connection.id,
73
+ timestamp: Date.now()
74
+ }));
75
+
76
+ // Listen for message events
77
+ connection.on('message', (data, isBinary) => {
78
+ if (isBinary) {
79
+ connection.send(JSON.stringify({
80
+ type: 'error',
81
+ message: 'Binary messages not supported in chat'
82
+ }));
83
+ return;
84
+ }
85
+
86
+ try {
87
+ const message = JSON.parse(data.toString());
88
+ console.log(`๐Ÿ“จ Message from ${connection.id}:`, message);
89
+
90
+ switch (message.type) {
91
+ case 'join':
92
+ handleJoinRoom(connection, message);
93
+ break;
94
+
95
+ case 'leave':
96
+ handleLeaveRoom(connection, message);
97
+ break;
98
+
99
+ case 'chat':
100
+ handleChatMessage(connection, message);
101
+ break;
102
+
103
+ case 'list':
104
+ handleListRooms(connection);
105
+ break;
106
+
107
+ case 'users':
108
+ handleListUsers(connection, message);
109
+ break;
110
+
111
+ default:
112
+ connection.send(JSON.stringify({
113
+ type: 'error',
114
+ message: 'Unknown message type',
115
+ validTypes: ['join', 'leave', 'chat', 'list', 'users']
116
+ }));
117
+ }
118
+ } catch (error) {
119
+ console.error(`โŒ JSON parse error from ${connection.id}:`, error.message);
120
+ connection.send(JSON.stringify({
121
+ type: 'error',
122
+ message: 'Invalid JSON format',
123
+ example: '{"type":"join","room":"general"}'
124
+ }));
125
+ }
126
+ });
127
+
128
+ // Listen for close events
129
+ connection.on('close', (code, reason) => {
130
+ console.log(`๐Ÿ‘‹ User disconnected: ${connection.id}, code: ${code}, reason: ${reason}`);
131
+
132
+ // Leave all rooms when user disconnects
133
+ const userRooms = connection.rooms ? connection.rooms() : [];
134
+ userRooms.forEach(room => {
135
+ connection.leave(room);
136
+ broadcastToRoom(room, {
137
+ type: 'system',
138
+ message: `User ${connection.id.substring(0, 8)} left the room`,
139
+ timestamp: Date.now()
140
+ });
141
+ });
142
+ });
143
+
144
+ // Listen for error events
145
+ connection.on('error', (error) => {
146
+ console.error(`โš ๏ธ Connection error for ${connection.id}:`, error);
147
+ });
148
+ });
149
+
150
+ // Listen for server error events
151
+ factory.on('error', (error) => {
152
+ console.error('๐Ÿ’ฅ Server error:', error);
153
+ });
154
+
155
+ // Create server
156
+ console.log('๐Ÿ”„ Creating server...');
157
+ const server = await factory.createServer();
158
+ console.log('โœ… Server instance created:', server);
159
+
160
+ // Fallback: If server:listening event doesn't fire within 500ms, show server info manually
161
+ setTimeout(() => {
162
+ if (!serverListeningEventFired) {
163
+ console.log('\nโš ๏ธ Server:listening event was not fired, checking server status manually...');
164
+
165
+ // Try to get server address directly
166
+ if (server && server.address) {
167
+ const address = typeof server.address === 'function' ? server.address() : server.address;
168
+ if (address && address.port) {
169
+ console.log(`๐ŸŽ‰ ============================================`);
170
+ console.log(`๐Ÿš€ Chat server listening on ws://${address.address || '0.0.0.0'}:${address.port}`);
171
+ console.log(`๐Ÿ’ฌ Room-based chat server ready for connections`);
172
+ console.log(`๐Ÿ›‘ Press Ctrl+C to stop the server`);
173
+ console.log(`============================================\n`);
174
+ } else {
175
+ console.log(`๐ŸŒ Server should be available at: ws://${process.env.WS_HOST || '0.0.0.0'}:${process.env.WS_PORT || 3000}`);
176
+ }
177
+ } else {
178
+ console.log(`๐ŸŒ Server should be available at: ws://${process.env.WS_HOST || '0.0.0.0'}:${process.env.WS_PORT || 3000}`);
179
+ }
180
+ }
181
+ }, 500);
182
+
183
+ // Display connection instructions
184
+ console.log('\n๐Ÿ“‹ How to connect:');
185
+ console.log('1. Open browser developer tools (F12)');
186
+ console.log('2. In Console tab, run:');
187
+ console.log(`
188
+ const ws = new WebSocket('ws://localhost:${process.env.WS_PORT || 3000}');
189
+ ws.onopen = () => {
190
+ console.log('Connected!');
191
+ ws.send(JSON.stringify({type:"join",room:"general"}));
192
+ ws.send(JSON.stringify({type:"chat",room:"general",message:"Hello!"}));
193
+ };
194
+ ws.onmessage = (e) => console.log('Server:', JSON.parse(e.data));
195
+ `);
196
+
197
+ // Room handling functions
198
+ function handleJoinRoom(connection, message) {
199
+ if (!message.room) {
200
+ connection.send(JSON.stringify({
201
+ type: 'error',
202
+ message: 'Room name is required',
203
+ example: '{"type":"join","room":"general"}'
204
+ }));
205
+ return;
206
+ }
207
+
208
+ const roomName = message.room.trim();
209
+ if (roomName.length === 0) {
210
+ connection.send(JSON.stringify({
211
+ type: 'error',
212
+ message: 'Room name cannot be empty'
213
+ }));
214
+ return;
215
+ }
216
+
217
+ // Join room
218
+ connection.join(roomName);
219
+
220
+ console.log(`๐Ÿ“ฅ ${connection.id} joined room: ${roomName}`);
221
+
222
+ // Send confirmation message
223
+ connection.send(JSON.stringify({
224
+ type: 'system',
225
+ message: `Joined room: ${roomName}`,
226
+ room: roomName,
227
+ timestamp: Date.now()
228
+ }));
229
+
230
+ // Broadcast to other users in the room
231
+ broadcastToRoom(roomName, {
232
+ type: 'system',
233
+ message: `New user ${connection.id.substring(0, 8)} joined the room`,
234
+ userCount: getRoomUserCount(roomName),
235
+ timestamp: Date.now()
236
+ }, connection.id); // Exclude self
237
+ }
238
+
239
+ function handleLeaveRoom(connection, message) {
240
+ if (!message.room) {
241
+ connection.send(JSON.stringify({
242
+ type: 'error',
243
+ message: 'Room name is required',
244
+ example: '{"type":"leave","room":"general"}'
245
+ }));
246
+ return;
247
+ }
248
+
249
+ const roomName = message.room.trim();
250
+ connection.leave(roomName);
251
+
252
+ console.log(`๐Ÿ“ค ${connection.id} left room: ${roomName}`);
253
+
254
+ connection.send(JSON.stringify({
255
+ type: 'system',
256
+ message: `Left room: ${roomName}`,
257
+ room: roomName,
258
+ timestamp: Date.now()
259
+ }));
260
+
261
+ // Notify other users in the room
262
+ broadcastToRoom(roomName, {
263
+ type: 'system',
264
+ message: `User ${connection.id.substring(0, 8)} left the room`,
265
+ userCount: getRoomUserCount(roomName),
266
+ timestamp: Date.now()
267
+ });
268
+ }
269
+
270
+ function handleChatMessage(connection, message) {
271
+ if (!message.room || !message.message) {
272
+ connection.send(JSON.stringify({
273
+ type: 'error',
274
+ message: 'Room and message are required',
275
+ example: '{"type":"chat","room":"general","message":"Hello!"}'
276
+ }));
277
+ return;
278
+ }
279
+
280
+ const roomName = message.room.trim();
281
+ const chatMessage = message.message.trim();
282
+
283
+ // Check if user is in the room
284
+ const userRooms = connection.rooms ? connection.rooms() : [];
285
+ if (!userRooms.includes(roomName)) {
286
+ connection.send(JSON.stringify({
287
+ type: 'error',
288
+ message: `You are not in room: ${roomName}`,
289
+ suggestion: 'Send {"type":"join","room":"room-name"} first'
290
+ }));
291
+ return;
292
+ }
293
+
294
+ console.log(`๐Ÿ’ฌ ${connection.id} -> ${roomName}: ${chatMessage}`);
295
+
296
+ // Broadcast message to room
297
+ broadcastToRoom(roomName, {
298
+ type: 'chat',
299
+ from: connection.id.substring(0, 8),
300
+ room: roomName,
301
+ message: chatMessage,
302
+ timestamp: Date.now()
303
+ }, connection.id); // Exclude sender
304
+
305
+ // Send confirmation to sender
306
+ connection.send(JSON.stringify({
307
+ type: 'sent',
308
+ room: roomName,
309
+ message: chatMessage,
310
+ timestamp: Date.now()
311
+ }));
312
+ }
313
+
314
+ function handleListRooms(connection) {
315
+ // Get all rooms (simplified implementation)
316
+ const roomInfo = {
317
+ type: 'rooms',
318
+ count: 0,
319
+ rooms: [],
320
+ timestamp: Date.now()
321
+ };
322
+
323
+ connection.send(JSON.stringify(roomInfo));
324
+ }
325
+
326
+ function handleListUsers(connection, message) {
327
+ if (!message.room) {
328
+ connection.send(JSON.stringify({
329
+ type: 'error',
330
+ message: 'Room name is required',
331
+ example: '{"type":"users","room":"general"}'
332
+ }));
333
+ return;
334
+ }
335
+
336
+ const roomName = message.room.trim();
337
+ const userInfo = {
338
+ type: 'users',
339
+ room: roomName,
340
+ count: 0,
341
+ users: [],
342
+ timestamp: Date.now()
343
+ };
344
+
345
+ connection.send(JSON.stringify(userInfo));
346
+ }
347
+
348
+ function broadcastToRoom(roomName, message, excludeConnectionId = null) {
349
+ // Simplified broadcast implementation
350
+ console.log(`๐Ÿ“ข Broadcasting to room ${roomName}:`, message);
351
+
352
+ // In a real implementation, use broadcast manager's room functionality
353
+ // For now, we'll use factory.broadcast with filtering
354
+ if (factory.broadcast) {
355
+ factory.broadcast(JSON.stringify(message), (conn) => {
356
+ // Exclude specific connection
357
+ if (excludeConnectionId && conn.id === excludeConnectionId) {
358
+ return false;
359
+ }
360
+
361
+ // Check if connection is in the room
362
+ const userRooms = conn.rooms ? conn.rooms() : [];
363
+ return userRooms.includes(roomName);
364
+ });
365
+ }
366
+ }
367
+
368
+ function getRoomUserCount(roomName) {
369
+ // Simplified implementation
370
+ return 1;
371
+ }
372
+
373
+ // Graceful shutdown handling
374
+ process.on('SIGINT', async () => {
375
+ console.log('\n๐Ÿ”„ Shutting down chat server...');
376
+ try {
377
+ // Notify all users about server shutdown
378
+ if (factory.broadcast) {
379
+ factory.broadcast(JSON.stringify({
380
+ type: 'system',
381
+ message: 'Server is shutting down. Goodbye!',
382
+ timestamp: Date.now()
383
+ }));
384
+ }
385
+
386
+ await factory.close();
387
+ console.log('โœ… Chat server closed successfully');
388
+ process.exit(0);
389
+ } catch (error) {
390
+ console.error('โŒ Error closing server:', error);
391
+ process.exit(1);
392
+ }
393
+ });
394
+
395
+ process.on('SIGTERM', async () => {
396
+ console.log('\n๐Ÿ”„ Received SIGTERM, shutting down...');
397
+ await factory.close();
398
+ process.exit(0);
399
+ });
400
+
401
+ // Keep the process running
402
+ console.log('โณ Server is running and waiting for connections...');
403
+
404
+ } catch (error) {
405
+ console.error('โŒ Failed to start chat server:', error);
406
+ console.error('๐Ÿ’ก Possible solutions:');
407
+ console.error('1. Check if port 3000 is already in use');
408
+ console.error('2. Try running as administrator (if using port < 1024)');
409
+ console.error('3. Check firewall settings');
410
+ console.error('4. Verify WebSocketFactory implementation');
411
+ process.exit(1);
412
+ }
413
+ }
414
+
415
+ // Start the chat server
416
+ main();
@@ -0,0 +1,58 @@
1
+ /**
2
+ * @file client-test.js - Native WebSocket Stress Test Client
3
+ * @description Uses Node.js built-in WebSocket (no 'ws' package required)
4
+ */
5
+
6
+ // No import needed! Node.js 18+ has global WebSocket
7
+ const URL = 'ws://localhost:8081';
8
+
9
+ console.log('๐Ÿš€ Starting stress test client using native WebSocket...');
10
+
11
+ const ws = new WebSocket(URL);
12
+
13
+ ws.onopen = () => {
14
+ console.log('โœ… Connected to server!');
15
+
16
+ // 1. Request initial stats
17
+ ws.send(JSON.stringify({ type: 'stats_request' }));
18
+
19
+ // 2. Start stress test sequence
20
+ // Sending 1000 messages with 512 bytes payload, no delay
21
+ console.log('๐Ÿ“ค Sending stress test command (1000 iterations)...');
22
+ ws.send(JSON.stringify({
23
+ type: 'stress_test',
24
+ iterations: 1000,
25
+ delay: 0,
26
+ payloadSize: 512
27
+ }));
28
+ };
29
+
30
+ ws.onmessage = (event) => {
31
+ const data = JSON.parse(event.data);
32
+
33
+ if (data.type === 'stats_response') {
34
+ console.log('๐Ÿ“Š Server Stats Received:', {
35
+ activeConnections: data.stats.connections.active,
36
+ totalMessages: data.stats.messages.received
37
+ });
38
+ }
39
+ else if (data.type === 'stress_test_complete') {
40
+ console.log('\n๐Ÿ ===== STRESS TEST COMPLETE =====');
41
+ console.log(`โšก Speed: ${data.messagesPerSecond} msgs/sec`);
42
+ console.log(`โฑ๏ธ Duration: ${data.duration} ms`);
43
+ console.log(`๐Ÿ“ฆ Total Iterations: ${data.iterations}`);
44
+ console.log('==================================\n');
45
+
46
+ // Close connection after test
47
+ ws.close();
48
+ }
49
+ };
50
+
51
+ ws.onerror = (error) => {
52
+ console.error('โŒ WebSocket Error:', error.message);
53
+ };
54
+
55
+ ws.onclose = () => {
56
+ console.log('๐Ÿ”Œ Connection closed.');
57
+ process.exit(0);
58
+ };
@@ -0,0 +1,98 @@
1
+ /**
2
+ * @file Realtime API Example with Auth and Rate Limiting
3
+ */
4
+
5
+ import { WebSocketFactory } from '../index.js';
6
+ import AuthMiddleware from '../src/middleware/auth-middleware.js';
7
+ import RateLimiter from '../src/middleware/rate-limiter.js';
8
+
9
+ async function main() {
10
+ // Create WebSocket factory instance with HTTP driver
11
+ const factory = new WebSocketFactory({
12
+ driver: 'http',
13
+ port: 4000
14
+ });
15
+
16
+ // Create authentication middleware instance
17
+ const auth = new AuthMiddleware({
18
+ verify: async (token) => {
19
+ // Mock token verification logic
20
+ // In production, replace with actual JWT or OAuth verification
21
+ return token === 'secret-token' ? { userId: 'user_123' } : null;
22
+ }
23
+ });
24
+
25
+ // Create rate limiter middleware instance
26
+ const limiter = new RateLimiter({
27
+ windowMs: 10000, // Time window in milliseconds (10 seconds)
28
+ max: 50 // Maximum requests per window
29
+ });
30
+
31
+ // Create WebSocket server
32
+ const server = await factory.createServer();
33
+
34
+ // Listen for connection events
35
+ factory.on('connection', (ws) => {
36
+ // Chain middlewares: auth -> rate limiter -> connection handler
37
+ auth.middleware()(ws, () => {
38
+ limiter.middleware()(ws, () => {
39
+ console.log(`Authenticated user connected: ${ws.user?.userId}`);
40
+
41
+ // Listen for message events
42
+ ws.on('message', (data) => {
43
+ // Echo back with status confirmation
44
+ ws.send(JSON.stringify({
45
+ status: 'ok',
46
+ received: data,
47
+ timestamp: Date.now()
48
+ }));
49
+ });
50
+
51
+ // Listen for close events
52
+ ws.on('close', (code, reason) => {
53
+ console.log(`User ${ws.user?.userId} disconnected: ${code} - ${reason}`);
54
+ });
55
+
56
+ // Listen for error events
57
+ ws.on('error', (error) => {
58
+ console.error(`Connection error for user ${ws.user?.userId}:`, error);
59
+ });
60
+ });
61
+ });
62
+ });
63
+
64
+ // Listen for server listening event
65
+ factory.on('server:listening', (address) => {
66
+ console.log(`Secure Realtime API listening on ws://${address.host}:${address.port}`);
67
+ console.log('๐Ÿ”’ Authentication required: Send token in connection header');
68
+ console.log('โฑ๏ธ Rate limiting: 50 requests per 10 seconds');
69
+ console.log('๐Ÿ›‘ Press Ctrl+C to stop the server');
70
+ });
71
+
72
+ // Listen for server error events
73
+ factory.on('error', (error) => {
74
+ console.error('Server error:', error);
75
+ });
76
+
77
+ // Graceful shutdown handling
78
+ process.on('SIGINT', async () => {
79
+ console.log('\nShutting down Realtime API server...');
80
+ try {
81
+ await factory.close();
82
+ console.log('Server closed successfully');
83
+ process.exit(0);
84
+ } catch (error) {
85
+ console.error('Error closing server:', error);
86
+ process.exit(1);
87
+ }
88
+ });
89
+
90
+ process.on('SIGTERM', async () => {
91
+ console.log('\nReceived SIGTERM, shutting down...');
92
+ await factory.close();
93
+ process.exit(0);
94
+ });
95
+ }
96
+
97
+ // Start the server
98
+ main().catch(console.error);
@@ -0,0 +1,495 @@
1
+ /**
2
+ * @file WebSocket Stress Test Server
3
+ * @description A WebSocket server designed for load testing and performance benchmarking
4
+ */
5
+
6
+ import { WebSocketFactory } from '../index.js';
7
+ import FrameEncoder from '../src/utils/frame-encoder.js'; // ๆทปๅŠ ่ฟ™่กŒๅฏผๅ…ฅ
8
+
9
+ // Configuration for stress testing
10
+ const STRESS_TEST_CONFIG = {
11
+ driver: 'http',
12
+ port: process.env.STRESS_TEST_PORT || 8081,
13
+ host: process.env.STRESS_TEST_HOST || '0.0.0.0',
14
+ maxPayload: 10 * 1024 * 1024, // 10MB for large message testing
15
+ pingInterval: 60000, // 60 seconds (reduced for stress testing)
16
+ pingTimeout: 30000, // 30 seconds timeout
17
+ maxConnections: 100000, // High connection limit for stress testing
18
+ socketTimeout: 0 // No timeout for long-running connections
19
+ };
20
+
21
+ // Statistics tracking
22
+ const stats = {
23
+ connections: {
24
+ total: 0,
25
+ active: 0,
26
+ peak: 0
27
+ },
28
+ messages: {
29
+ received: 0,
30
+ sent: 0,
31
+ bytesReceived: 0,
32
+ bytesSent: 0
33
+ },
34
+ errors: {
35
+ connection: 0,
36
+ message: 0,
37
+ other: 0
38
+ },
39
+ startTime: Date.now()
40
+ };
41
+
42
+ // Store active connections for message handling
43
+ const activeConnections = new Map();
44
+
45
+ async function main() {
46
+ try {
47
+ console.log('โšก Starting WebSocket Stress Test Server...');
48
+ console.log(`๐Ÿ“Š Port: ${STRESS_TEST_CONFIG.port}`);
49
+ console.log(`๐Ÿ  Host: ${STRESS_TEST_CONFIG.host}`);
50
+ console.log(`๐Ÿ”ง Max Connections: ${STRESS_TEST_CONFIG.maxConnections}`);
51
+ console.log(`๐Ÿ“ฆ Max Payload: ${STRESS_TEST_CONFIG.maxPayload / 1024 / 1024}MB`);
52
+
53
+ // Create WebSocket factory instance with stress test configuration
54
+ const factory = new WebSocketFactory(STRESS_TEST_CONFIG);
55
+
56
+ // Create server
57
+ const server = await factory.createServer();
58
+
59
+ // Listen for server listening event
60
+ factory.on('server:listening', (address) => {
61
+ console.log(`\n๐ŸŽฏ ============================================`);
62
+ console.log(`โšก Stress Test Server listening on ws://${address.host}:${address.port}`);
63
+ console.log(`๐Ÿ“ˆ Ready for load testing and performance benchmarking`);
64
+ console.log(`๐Ÿ›‘ Press Ctrl+C to stop the server`);
65
+ console.log(`============================================\n`);
66
+
67
+ // Start statistics logging
68
+ startStatisticsLogging();
69
+ });
70
+
71
+ // Listen for connection events
72
+ factory.on('connection', (connection) => {
73
+ // Update connection statistics
74
+ stats.connections.total++;
75
+ stats.connections.active++;
76
+ stats.connections.peak = Math.max(stats.connections.peak, stats.connections.active);
77
+
78
+ const connectionId = connection.id || `conn_${stats.connections.total}`;
79
+ const remoteAddress = connection.remoteAddress || 'unknown';
80
+
81
+ // Store connection for later use
82
+ activeConnections.set(connectionId, connection);
83
+
84
+ console.log(`๐Ÿ”— Client connected: ${connectionId} from ${remoteAddress}`);
85
+ console.log(`๐Ÿ“Š Active connections: ${stats.connections.active}/${stats.connections.total}`);
86
+
87
+ // Log connection object structure for debugging
88
+ console.log(`๐Ÿ” Connection object type: ${typeof connection}`);
89
+ console.log(`๐Ÿ” Connection object keys:`, Object.keys(connection));
90
+
91
+ // Send connection acknowledgment with server info
92
+ const ackMessage = JSON.stringify({
93
+ type: 'connection_ack',
94
+ connectionId: connectionId,
95
+ serverTime: Date.now(),
96
+ stats: {
97
+ activeConnections: stats.connections.active,
98
+ uptime: Date.now() - stats.startTime
99
+ }
100
+ });
101
+
102
+ // Send message through driver's socket
103
+ if (connection.socket && typeof connection.socket.write === 'function') {
104
+ try {
105
+ // Create WebSocket frame for the message
106
+ const frame = createWebSocketFrame(ackMessage);
107
+ connection.socket.write(frame);
108
+ stats.messages.sent++;
109
+ stats.messages.bytesSent += Buffer.byteLength(ackMessage);
110
+ } catch (error) {
111
+ console.error(`โŒ Error sending acknowledgment to ${connectionId}:`, error.message);
112
+ }
113
+ } else {
114
+ console.error(`โŒ No socket available for connection ${connectionId}`);
115
+ }
116
+ });
117
+
118
+ // Listen for message events from factory (not from connection object)
119
+ factory.on('message', (connection, data, isBinary) => {
120
+ const connectionId = connection.id || 'unknown';
121
+ const messageSize = Buffer.byteLength(data);
122
+ stats.messages.received++;
123
+ stats.messages.bytesReceived += messageSize;
124
+
125
+ // Log message statistics periodically
126
+ if (stats.messages.received % 1000 === 0) {
127
+ console.log(`๐Ÿ“จ Received ${stats.messages.received} messages (${formatBytes(stats.messages.bytesReceived)})`);
128
+ }
129
+
130
+ // Process different types of stress test messages
131
+ try {
132
+ if (isBinary) {
133
+ // Handle binary data (for binary stress testing)
134
+ handleBinaryMessage(connection, data, messageSize);
135
+ } else {
136
+ // Handle text data
137
+ const message = data.toString();
138
+ handleTextMessage(connection, message, messageSize);
139
+ }
140
+ } catch (error) {
141
+ stats.errors.message++;
142
+ console.error(`โŒ Message processing error for ${connectionId}:`, error.message);
143
+ }
144
+ });
145
+
146
+ // Listen for close events from factory
147
+ factory.on('close', (connection, code, reason) => {
148
+ const connectionId = connection.id || 'unknown';
149
+ stats.connections.active--;
150
+ console.log(`๐Ÿ‘‹ Client disconnected: ${connectionId}, code: ${code}, reason: ${reason}`);
151
+ console.log(`๐Ÿ“Š Active connections: ${stats.connections.active}/${stats.connections.total}`);
152
+
153
+ // Remove from active connections
154
+ activeConnections.delete(connectionId);
155
+ });
156
+
157
+ // Listen for error events from factory
158
+ factory.on('error', (error) => {
159
+ stats.errors.other++;
160
+ console.error('๐Ÿ’ฅ Server error:', error);
161
+ });
162
+
163
+ // Handle different types of stress test messages
164
+ function handleTextMessage(connection, message, messageSize) {
165
+ const connectionId = connection.id || 'unknown';
166
+
167
+ try {
168
+ const parsed = JSON.parse(message);
169
+
170
+ switch (parsed.type) {
171
+ case 'echo':
172
+ // Echo back the message (basic echo test)
173
+ const echoResponse = {
174
+ type: 'echo_response',
175
+ original: parsed.data,
176
+ timestamp: Date.now(),
177
+ messageSize: messageSize
178
+ };
179
+ sendToConnection(connection, JSON.stringify(echoResponse));
180
+ stats.messages.sent++;
181
+ stats.messages.bytesSent += Buffer.byteLength(JSON.stringify(echoResponse));
182
+ break;
183
+
184
+ case 'broadcast':
185
+ // Broadcast message to all connections (broadcast stress test)
186
+ const broadcastMessage = JSON.stringify({
187
+ type: 'broadcast',
188
+ from: connectionId,
189
+ data: parsed.data,
190
+ timestamp: Date.now()
191
+ });
192
+
193
+ // Broadcast to all active connections
194
+ broadcastToAll(broadcastMessage, connectionId);
195
+ break;
196
+
197
+ case 'stats_request':
198
+ // Return server statistics
199
+ const statsResponse = {
200
+ type: 'stats_response',
201
+ stats: getCurrentStats(),
202
+ timestamp: Date.now()
203
+ };
204
+ sendToConnection(connection, JSON.stringify(statsResponse));
205
+ stats.messages.sent++;
206
+ break;
207
+
208
+ case 'large_payload':
209
+ // Test large payload handling
210
+ const largeResponse = {
211
+ type: 'large_payload_response',
212
+ size: parsed.size || 1024,
213
+ data: 'x'.repeat(parsed.size || 1024),
214
+ timestamp: Date.now()
215
+ };
216
+ sendToConnection(connection, JSON.stringify(largeResponse));
217
+ stats.messages.sent++;
218
+ stats.messages.bytesSent += Buffer.byteLength(JSON.stringify(largeResponse));
219
+ break;
220
+
221
+ case 'stress_test':
222
+ // Run a stress test sequence
223
+ runStressTestSequence(connection, parsed);
224
+ break;
225
+
226
+ default:
227
+ // Default echo response
228
+ const defaultResponse = {
229
+ type: 'default_response',
230
+ received: parsed,
231
+ timestamp: Date.now()
232
+ };
233
+ sendToConnection(connection, JSON.stringify(defaultResponse));
234
+ stats.messages.sent++;
235
+ }
236
+ } catch (error) {
237
+ // If JSON parsing fails, echo back as text
238
+ const echoText = `Echo: ${message}`;
239
+ sendToConnection(connection, echoText);
240
+ stats.messages.sent++;
241
+ stats.messages.bytesSent += Buffer.byteLength(echoText);
242
+ }
243
+ }
244
+
245
+ function handleBinaryMessage(connection, data, messageSize) {
246
+ // For binary stress testing, echo back the binary data
247
+ sendToConnection(connection, data, true);
248
+ stats.messages.sent++;
249
+ stats.messages.bytesSent += messageSize;
250
+
251
+ // Log binary message statistics
252
+ if (stats.messages.received % 100 === 0) {
253
+ console.log(`๐Ÿ”ข Binary message: ${formatBytes(messageSize)} from ${connection.id || 'unknown'}`);
254
+ }
255
+ }
256
+
257
+ function sendToConnection(connection, data, isBinary = false) {
258
+ try {
259
+ if (connection.socket && typeof connection.socket.write === 'function') {
260
+ // Create WebSocket frame
261
+ const frame = createWebSocketFrame(data, isBinary);
262
+ return connection.socket.write(frame);
263
+ } else {
264
+ console.error(`โŒ No socket available for connection ${connection.id || 'unknown'}`);
265
+ return false;
266
+ }
267
+ } catch (error) {
268
+ console.error(`โŒ Error sending to connection ${connection.id || 'unknown'}:`, error.message);
269
+ return false;
270
+ }
271
+ }
272
+
273
+ function createWebSocketFrame(data, isBinary = false) {
274
+ try {
275
+ // ไฝฟ็”จ FrameEncoder ๅˆ›ๅปบๆญฃ็กฎ็š„ WebSocket ๅธง
276
+ const opcode = isBinary ? 0x2 : 0x1; // 0x1 = TEXT, 0x2 = BINARY
277
+ return FrameEncoder.encode(opcode, data, true, false); // fin=true, mask=false (ๆœๅŠกๅ™จๅˆฐๅฎขๆˆท็ซฏไธ้œ€่ฆๆŽฉ็ )
278
+ } catch (error) {
279
+ console.error('โŒ Error creating WebSocket frame:', error.message);
280
+
281
+ // ๅ›ž้€€ๅˆฐ็ฎ€ๅ•ๅฎž็Žฐ
282
+ const payload = Buffer.isBuffer(data) ? data : Buffer.from(data, 'utf8');
283
+ const payloadLength = payload.length;
284
+
285
+ let frame;
286
+ if (payloadLength <= 125) {
287
+ frame = Buffer.alloc(2 + payloadLength);
288
+ frame = isBinary ? 0x82 : 0x81; // FIN + opcode
289
+ frame = payloadLength; // No mask
290
+ payload.copy(frame, 2);
291
+ } else if (payloadLength <= 65535) {
292
+ frame = Buffer.alloc(4 + payloadLength);
293
+ frame = isBinary ? 0x82 : 0x81;
294
+ frame = 126; // Extended payload length
295
+ frame.writeUInt16BE(payloadLength, 2);
296
+ payload.copy(frame, 4);
297
+ } else {
298
+ frame = Buffer.alloc(10 + payloadLength);
299
+ frame = isBinary ? 0x82 : 0x81;
300
+ frame = 127; // 64-bit payload length
301
+ frame.writeBigUInt64BE(BigInt(payloadLength), 2);
302
+ payload.copy(frame, 10);
303
+ }
304
+
305
+ return frame;
306
+ }
307
+ }
308
+
309
+ function broadcastToAll(message, excludeConnectionId = null) {
310
+ let sentCount = 0;
311
+
312
+ activeConnections.forEach((connection, id) => {
313
+ if (excludeConnectionId && id === excludeConnectionId) {
314
+ return; // Skip excluded connection
315
+ }
316
+
317
+ if (sendToConnection(connection, message)) {
318
+ sentCount++;
319
+ }
320
+ });
321
+
322
+ stats.messages.sent += sentCount;
323
+ return sentCount;
324
+ }
325
+
326
+ function runStressTestSequence(connection, testConfig) {
327
+ const { iterations = 100, delay = 10, payloadSize = 1024 } = testConfig;
328
+ const connectionId = connection.id || 'unknown';
329
+
330
+ console.log(`๐Ÿงช Starting stress test for ${connectionId}: ${iterations} iterations`);
331
+
332
+ let completed = 0;
333
+ const testStartTime = Date.now();
334
+
335
+ function sendTestMessage(index) {
336
+ if (index >= iterations) {
337
+ // Test complete
338
+ const testDuration = Date.now() - testStartTime;
339
+ const messagesPerSecond = iterations / (testDuration / 1000);
340
+
341
+ const completionMessage = JSON.stringify({
342
+ type: 'stress_test_complete',
343
+ iterations: iterations,
344
+ duration: testDuration,
345
+ messagesPerSecond: messagesPerSecond.toFixed(2),
346
+ timestamp: Date.now()
347
+ });
348
+
349
+ sendToConnection(connection, completionMessage);
350
+
351
+ console.log(`โœ… Stress test completed for ${connectionId}: ${messagesPerSecond.toFixed(2)} msg/sec`);
352
+ return;
353
+ }
354
+
355
+ const testMessage = {
356
+ type: 'stress_test_message',
357
+ index: index,
358
+ payload: 'x'.repeat(payloadSize),
359
+ timestamp: Date.now()
360
+ };
361
+
362
+ if (sendToConnection(connection, JSON.stringify(testMessage))) {
363
+ stats.messages.sent++;
364
+ stats.messages.bytesSent += Buffer.byteLength(JSON.stringify(testMessage));
365
+ }
366
+
367
+ setTimeout(() => sendTestMessage(index + 1), delay);
368
+ }
369
+
370
+ sendTestMessage(0);
371
+ }
372
+
373
+ function getCurrentStats() {
374
+ const uptime = Date.now() - stats.startTime;
375
+ const hours = Math.floor(uptime / 3600000);
376
+ const minutes = Math.floor((uptime % 3600000) / 60000);
377
+ const seconds = Math.floor((uptime % 60000) / 1000);
378
+
379
+ return {
380
+ connections: {
381
+ total: stats.connections.total,
382
+ active: stats.connections.active,
383
+ peak: stats.connections.peak
384
+ },
385
+ messages: {
386
+ received: stats.messages.received,
387
+ sent: stats.messages.sent,
388
+ bytesReceived: formatBytes(stats.messages.bytesReceived),
389
+ bytesSent: formatBytes(stats.messages.bytesSent),
390
+ receiveRate: (stats.messages.received / (uptime / 1000)).toFixed(2),
391
+ sendRate: (stats.messages.sent / (uptime / 1000)).toFixed(2)
392
+ },
393
+ errors: stats.errors,
394
+ uptime: `${hours}h ${minutes}m ${seconds}s`,
395
+ memory: process.memoryUsage()
396
+ };
397
+ }
398
+
399
+ function formatBytes(bytes) {
400
+ if (bytes === 0) return '0 Bytes';
401
+ const k = 1024;
402
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
403
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
404
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
405
+ }
406
+
407
+ function startStatisticsLogging() {
408
+ // Log statistics every 30 seconds
409
+ setInterval(() => {
410
+ const currentStats = getCurrentStats();
411
+ console.log('\n๐Ÿ“ˆ ========== SERVER STATISTICS ==========');
412
+ console.log(`โฑ๏ธ Uptime: ${currentStats.uptime}`);
413
+ console.log(`๐Ÿ‘ฅ Connections: ${currentStats.connections.active} active / ${currentStats.connections.total} total (Peak: ${currentStats.connections.peak})`);
414
+ console.log(`๐Ÿ“จ Messages: ${currentStats.messages.received} received, ${currentStats.messages.sent} sent`);
415
+ console.log(`๐Ÿ“Š Data: ${currentStats.messages.bytesReceived} received, ${currentStats.messages.bytesSent} sent`);
416
+ console.log(`โšก Rates: ${currentStats.messages.receiveRate} msg/sec received, ${currentStats.messages.sendRate} msg/sec sent`);
417
+ console.log(`โŒ Errors: ${currentStats.errors.connection} connection, ${currentStats.errors.message} message, ${currentStats.errors.other} other`);
418
+ console.log(`๐Ÿ’พ Memory: ${formatBytes(currentStats.memory.rss)} RSS, ${formatBytes(currentStats.memory.heapUsed)} heap used`);
419
+ console.log(`========================================\n`);
420
+ }, 30000);
421
+ }
422
+
423
+ // Graceful shutdown handling
424
+ process.on('SIGINT', async () => {
425
+ console.log('\n๐Ÿ”„ Shutting down stress test server...');
426
+ console.log('๐Ÿ“Š Final statistics:');
427
+ console.log(JSON.stringify(getCurrentStats(), null, 2));
428
+
429
+ try {
430
+ await factory.close();
431
+ console.log('โœ… Stress test server closed successfully');
432
+ process.exit(0);
433
+ } catch (error) {
434
+ console.error('โŒ Error closing server:', error);
435
+ process.exit(1);
436
+ }
437
+ });
438
+
439
+ process.on('SIGTERM', async () => {
440
+ console.log('\n๐Ÿ”„ Received SIGTERM, shutting down...');
441
+ await factory.close();
442
+ process.exit(0);
443
+ });
444
+
445
+ // Display test client instructions
446
+ console.log('\n๐Ÿงช ========== STRESS TEST CLIENT EXAMPLES ==========');
447
+ console.log('1. Basic Echo Test:');
448
+ console.log(`
449
+ const ws = new WebSocket('ws://localhost:${STRESS_TEST_CONFIG.port}');
450
+ ws.onopen = () => {
451
+ for(let i = 0; i < 1000; i++) {
452
+ ws.send(JSON.stringify({type: 'echo', data: 'Test message ' + i}));
453
+ }
454
+ };
455
+ `);
456
+
457
+ console.log('\n2. Large Payload Test:');
458
+ console.log(`
459
+ const ws = new WebSocket('ws://localhost:${STRESS_TEST_CONFIG.port}');
460
+ ws.onopen = () => {
461
+ ws.send(JSON.stringify({type: 'large_payload', size: 1000000}));
462
+ };
463
+ `);
464
+
465
+ console.log('\n3. Stress Test Sequence:');
466
+ console.log(`
467
+ const ws = new WebSocket('ws://localhost:${STRESS_TEST_CONFIG.port}');
468
+ ws.onopen = () => {
469
+ ws.send(JSON.stringify({
470
+ type: 'stress_test',
471
+ iterations: 1000,
472
+ delay: 0,
473
+ payloadSize: 1024
474
+ }));
475
+ };
476
+ `);
477
+
478
+ console.log('\n4. Request Statistics:');
479
+ console.log(`
480
+ const ws = new WebSocket('ws://localhost:${STRESS_TEST_CONFIG.port}');
481
+ ws.onopen = () => {
482
+ ws.send(JSON.stringify({type: 'stats_request'}));
483
+ ws.onmessage = (e) => console.log('Stats:', JSON.parse(e.data));
484
+ };
485
+ `);
486
+ console.log('==================================================\n');
487
+
488
+ } catch (error) {
489
+ console.error('โŒ Failed to start stress test server:', error);
490
+ process.exit(1);
491
+ }
492
+ }
493
+
494
+ // Start the stress test server
495
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aetherframework/websocket",
3
- "version": "1.0.0",
3
+ "version": "1.0.3",
4
4
  "description": "Zero-dependency WebSocket server implementation for AetherJS",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -43,7 +43,8 @@
43
43
  "files": [
44
44
  "index.js",
45
45
  "server.js",
46
- "src/",
46
+ "src",
47
+ "examples",
47
48
  "connection.js",
48
49
  "README.md",
49
50
  "LICENSE"