@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,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);
|