redweb 0.6.4 → 0.6.5

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/README.md CHANGED
@@ -74,9 +74,9 @@ const options = {
74
74
  const app = new HttpsServer(options);
75
75
  ```
76
76
 
77
- ### WebSocket Server with Handlers
77
+ ### WebSocket Server with Routes and Handlers
78
78
 
79
- RedWeb supports **handler-based routing** for WebSocket connections, allowing you to modularize and secure your WebSocket message handling logic.
79
+ RedWeb uses **route-based architecture** for WebSocket connections, allowing you to modularize and secure your WebSocket message handling logic.
80
80
 
81
81
  #### Defining a Custom Handler
82
82
 
@@ -87,86 +87,85 @@ const { BaseHandler } = require('redweb');
87
87
 
88
88
  class ChatHandler extends BaseHandler {
89
89
  constructor() {
90
- super({
91
- name: 'ChatHandler',
92
- handlers: {
93
- chat: (socket, data) => {
94
- console.log(`Received chat message: ${data.message}`);
95
- socket.send(JSON.stringify({ type: 'chatResponse', message: 'Hello!' }));
96
- }
97
- }
98
- });
90
+ super('chat');
91
+ }
92
+
93
+ onMessage(socket, message) {
94
+ console.log(`Received chat message: ${message.text}`);
95
+ socket.send(JSON.stringify({ type: 'chatResponse', message: 'Hello!' }));
99
96
  }
100
97
  }
101
98
 
102
99
  module.exports = ChatHandler;
103
100
  ```
104
101
 
105
- #### Setting Up a WebSocket Server with Handlers
102
+ #### Defining a WebSocket Route
103
+
104
+ Routes group handlers and specify the WebSocket path.
106
105
 
107
106
  ```javascript
108
- const { SocketServer } = require('redweb');
107
+ const { SocketRoute } = require('redweb');
109
108
  const ChatHandler = require('./ChatHandler');
110
109
 
111
- const options = {
112
- port: 3000,
113
- handlerConfig: [ChatHandler],
114
- connectionOpenCallback: (socket) => {
115
- console.log('WebSocket client connected');
116
- },
117
- connectionCloseCallback: (socket) => {
118
- console.log('WebSocket client disconnected');
110
+ class ChatRoute extends SocketRoute {
111
+ constructor() {
112
+ super({
113
+ path: '/chat',
114
+ handlers: [ChatHandler]
115
+ });
119
116
  }
120
- };
117
+ }
121
118
 
122
- const socketServer = new SocketServer(options);
119
+ module.exports = ChatRoute;
123
120
  ```
124
121
 
125
- #### Adding Handlers Dynamically
122
+ #### Setting Up a WebSocket Server with Routes
126
123
 
127
- Handlers can be added after the WebSocket server has started using the `addHandler` method.
124
+ ```javascript
125
+ const { SocketServer } = require('redweb');
126
+ const ChatRoute = require('./ChatRoute');
127
+
128
+ new SocketServer({
129
+ port: 3000,
130
+ routes: [ChatRoute]
131
+ });
132
+ ```
133
+
134
+ ### Adding Routes Dynamically
135
+
136
+ Routes can be added to the WebSocket server after initialization.
128
137
 
129
138
  ```javascript
130
- class DynamicHandler {
131
- constructor() {
132
- this.name = 'DynamicHandler';
133
- this.handlers = {
134
- dynamicMessage: (socket, data) => {
135
- console.log(`Received dynamic message: ${data.message}`);
136
- socket.send(JSON.stringify({ type: 'dynamicResponse', message: 'Handled dynamically!' }));
137
- }
138
- };
139
- }
139
+ const { SocketServer, SocketRoute } = require('redweb');
140
+ const ChatHandler = require('./ChatHandler');
140
141
 
141
- newConnection(socket) {
142
- console.log('DynamicHandler: New connection established');
142
+ class ChatRoute extends SocketRoute {
143
+ constructor() {
144
+ super({
145
+ path: '/chat',
146
+ handlers: [ChatHandler]
147
+ });
143
148
  }
144
149
  }
145
150
 
146
- const { SocketServer } = require('redweb');
147
151
  const socketServer = new SocketServer({ port: 3000 });
148
152
 
149
- // Dynamically add a new handler
150
- socketServer.addHandler(DynamicHandler);
153
+ // Dynamically add a new route
154
+ const chatRoute = new ChatRoute();
155
+ socketServer.routes.push(chatRoute);
151
156
  ```
152
157
 
153
- #### Client Communication with a Handler
158
+ ### Client Communication with a Route
154
159
 
155
- The client must identify the handler during the initial connection with a `__handlerConnect` message.
160
+ The client connects to the WebSocket server using the specified route.
156
161
 
157
162
  ```javascript
158
163
  const WebSocket = require('ws');
159
164
 
160
- const ws = new WebSocket('ws://localhost:3000');
165
+ const ws = new WebSocket('ws://localhost:3000/chat');
161
166
 
162
167
  ws.on('open', () => {
163
- ws.send(JSON.stringify({
164
- type: '__handlerConnect',
165
- data: { handlerName: 'ChatHandler' }
166
- }));
167
-
168
- // Send a message to the handler
169
- ws.send(JSON.stringify({ type: 'chat', message: 'Hi there!' }));
168
+ ws.send(JSON.stringify({ type: 'chat', text: 'Hello there!' }));
170
169
  });
171
170
 
172
171
  ws.on('message', (message) => {
@@ -176,42 +175,25 @@ ws.on('message', (message) => {
176
175
 
177
176
  ### Managing Connected Clients
178
177
 
179
- RedWeb's WebSocket server maintains a list of connected clients by their IP addresses. This list is automatically updated when clients connect or disconnect.
178
+ RedWeb's WebSocket server maintains a list of connected clients by their IP addresses for each route. This list is automatically updated when clients connect or disconnect.
180
179
 
181
180
  ```javascript
182
- const { SocketServer } = require('redweb');
181
+ const { SocketRoute } = require('redweb');
183
182
 
184
- const socketServer = new SocketServer({
185
- port: 3000,
186
- connectionOpenCallback: (socket) => {
187
- console.log('WebSocket client connected');
188
- },
189
- connectionCloseCallback: (socket) => {
190
- console.log('WebSocket client disconnected');
183
+ class ChatRoute extends SocketRoute {
184
+ constructor() {
185
+ super({
186
+ path: '/chat',
187
+ handlers: []
188
+ });
191
189
  }
192
- });
193
190
 
194
- // Access the list of connected clients
195
- console.log(socketServer.clients); // Map of clients by their IP addresses
196
- ```
197
-
198
- ### Backward-Compatible Message Handlers
199
-
200
- If no handler is assigned during the initial connection, you can still use traditional `messageHandlers`.
201
-
202
- ```javascript
203
- const { SocketServer } = require('redweb');
204
-
205
- const options = {
206
- port: 3000,
207
- messageHandlers: {
208
- echo: (socket, data) => {
209
- socket.send(JSON.stringify({ type: 'echoResponse', message: data.message }));
210
- }
191
+ onConnection(socket) {
192
+ console.log('New client connected:', socket.remoteAddress);
211
193
  }
212
- };
194
+ }
213
195
 
214
- const socketServer = new SocketServer(options);
196
+ module.exports = ChatRoute;
215
197
  ```
216
198
 
217
199
  ## Options
@@ -226,14 +208,10 @@ const socketServer = new SocketServer(options);
226
208
  - **encoding**: Encoding type for request bodies (`'json'` or `'urlencoded'`).
227
209
  - **ssl**: SSL configuration for HTTPS server (`{ key: './path/to/key.pem', cert: './path/to/cert.pem' }`).
228
210
 
229
- ### SocketServer and SecureSocketServer Options
211
+ ### SocketServer Options
230
212
 
231
213
  - **port**: Port number (default: `3000`).
232
- - **connectionOpenCallback**: Function to execute once a client connects.
233
- - **connectionCloseCallback**: Function to execute once a client disconnects.
234
- - **messageCallback**: Function to execute for every message received.
235
- - **messageHandlers**: Object containing message handlers based on message type.
236
- - **handlerConfig**: Array of handler classes to support handler-based routing.
214
+ - **routes**: Array of `SocketRoute` classes to define WebSocket routes and handlers.
237
215
  - **ssl**: SSL configuration for SecureSocketServer (`{ key: './path/to/key.pem', cert: './path/to/cert.pem' }`).
238
216
 
239
217
  ## License
package/index.d.ts CHANGED
@@ -59,61 +59,103 @@ declare module 'redweb' {
59
59
  key: string;
60
60
  cert: string;
61
61
  };
62
- handlerConfig?: Array<new () => BaseHandler>;
62
+ routes?: Array<new () => SocketRoute>;
63
63
  }
64
64
 
65
65
  /**
66
- * Base class for WebSocket handlers.
66
+ * WebSocket route configuration.
67
67
  */
68
- export class BaseHandler {
68
+ export interface SocketRouteConfig {
69
+ path: string; // The WebSocket route path (e.g., "/chat").
70
+ handlers: Array<new () => BaseHandler>; // Array of handler classes for the route.
71
+ }
72
+
73
+ /**
74
+ * Represents a WebSocket route.
75
+ */
76
+ export class SocketRoute {
69
77
  /**
70
- * The name of the handler (used to identify it in the server).
78
+ * The path for the WebSocket route.
71
79
  */
72
- name: string;
80
+ path: string;
73
81
 
74
82
  /**
75
- * Dictionary of message handlers for this handler.
83
+ * Handlers associated with the route.
76
84
  */
77
- messageHandlers: {
78
- [type: string]: (socket: WebSocket, data: any) => void;
79
- };
85
+ handlers: BaseHandler[];
80
86
 
81
87
  /**
82
- * List of active WebSocket connections managed by this handler.
88
+ * Creates a new `SocketRoute` instance.
89
+ * @param config - Configuration options for the route.
83
90
  */
84
- connections: WebSocket[];
91
+ constructor(config: SocketRouteConfig);
85
92
 
86
93
  /**
87
- * Creates a new handler instance.
88
- * @param config - Configuration for the handler.
94
+ * Adds a new handler dynamically.
95
+ * @param HandlerClass - A class extending `BaseHandler`.
89
96
  */
90
- constructor(config: HandlerConfig);
97
+ addHandler(HandlerClass: new () => BaseHandler): void;
91
98
 
92
99
  /**
93
- * Adds a new WebSocket connection, processes initial data, and sets up message handling for this handler.
94
- * @param socket - The WebSocket connection to add.
95
- * @param data - Optional initial data sent during the connection handshake.
100
+ * Handles a new WebSocket connection.
101
+ * @param socket - The WebSocket connection instance.
102
+ * @param req - The HTTP request associated with the connection.
96
103
  */
97
- newConnection(socket: WebSocket, data?: any): void;
104
+ handleConnection(socket: WebSocket, req: import('http').IncomingMessage): void;
98
105
 
99
106
  /**
100
- * Method called when a WebSocket connection is closed. Can be overridden in subclasses.
101
- * @param socket - The WebSocket connection being closed.
107
+ * Handles incoming WebSocket messages.
108
+ * @param socket - The WebSocket connection instance.
109
+ * @param data - The message data.
102
110
  */
103
- onClose(socket: WebSocket): void;
111
+ handleMessage(socket: WebSocket, data: any): void;
112
+
113
+ /**
114
+ * Handles WebSocket disconnections.
115
+ * @param socket - The WebSocket connection instance.
116
+ */
117
+ handleClose(socket: WebSocket): void;
104
118
 
105
119
  /**
106
- * Handles an incoming message and routes it to the appropriate handler function.
120
+ * Handles WebSocket errors.
121
+ * @param socket - The WebSocket connection instance.
122
+ * @param error - The error object.
123
+ */
124
+ handleError(socket: WebSocket, error: Error): void;
125
+ }
126
+
127
+ /**
128
+ * Base class for WebSocket handlers.
129
+ */
130
+ export class BaseHandler {
131
+ /**
132
+ * The name of the handler (used to identify it in the server).
133
+ */
134
+ name: string;
135
+ /**
136
+ * Creates a new handler instance.
137
+ * @param name - The name of the handler.
138
+ */
139
+ constructor(name: string);
140
+
141
+ /**
142
+ * Handles an incoming message.
107
143
  * @param socket - The WebSocket connection that sent the message.
108
- * @param message - The incoming message in JSON string format.
144
+ * @param message - The message data.
109
145
  */
110
- handleMessage(socket: WebSocket, message: string): void;
146
+ onMessage(socket: WebSocket, message: Object): void;
111
147
 
112
148
  /**
113
- * Broadcasts a message to all connections managed by this handler.
114
- * @param message - The message to broadcast.
149
+ * Called during the first contact with a new WebSocket connection.
150
+ * @param socket - The WebSocket connection instance.
115
151
  */
116
- broadcast(message: object): void;
152
+ onInitialContact(socket: WebSocket): void;
153
+
154
+ /**
155
+ * Called when a WebSocket connection closes.
156
+ * @param socket - The WebSocket connection instance.
157
+ */
158
+ onClose(socket: WebSocket): void;
117
159
  }
118
160
 
119
161
  /**
@@ -121,22 +163,30 @@ declare module 'redweb' {
121
163
  */
122
164
  export class BaseSocketServer {
123
165
  /**
124
- * Map of connected WebSocket clients by their IP address.
166
+ * List of WebSocket routes.
125
167
  */
126
- clients: Map<string, WebSocket>;
168
+ routes: SocketRoute[];
127
169
 
128
170
  /**
129
- * List of handler instances currently registered with the server.
171
+ * Creates a new `BaseSocketServer`.
172
+ * @param server - The HTTP server instance.
173
+ * @param options - Configuration options.
130
174
  */
131
- handlers: BaseHandler[];
132
-
133
175
  constructor(server: HTTPServer | HTTPSServer, options?: SocketServerOptions);
134
176
 
135
177
  /**
136
- * Adds a new handler to the WebSocket server.
137
- * @param HandlerClass - A class extending `BaseHandler` to add to the server.
178
+ * Handles WebSocket upgrade requests.
179
+ * @param req - The incoming HTTP upgrade request.
180
+ * @param socket - The raw network socket.
181
+ * @param head - The initial data chunk.
138
182
  */
139
- addHandler(HandlerClass: new () => BaseHandler): void;
183
+ handleUpgrade(req: import('http').IncomingMessage, socket: import('net').Socket, head: Buffer): void;
184
+
185
+ /**
186
+ *
187
+ * @param route
188
+ */
189
+ addRoute(route: new () => SocketRoute);
140
190
  }
141
191
 
142
192
  /**
package/index.js CHANGED
@@ -1,12 +1,13 @@
1
- const { HttpServer, HttpsServer, METHODS } = require('./src/http');
2
- const { SocketServer, SecureSocketServer, SOCKET_OPTIONS } = require('./src/ws');
1
+ const { METHODS } = require('./src/http');
2
+ const { sendJson } = require('./src/ws/util');
3
+ const { SocketServer, SecureSocketServer, SOCKET_OPTIONS, SocketRoute } = require('./src/ws');
3
4
  const { BaseHandler } = require('./src/ws/BaseHandler');
4
5
  module.exports = {
5
6
  SocketServer,
6
7
  SecureSocketServer,
7
8
  BaseHandler,
8
- HttpServer,
9
- HttpsServer,
9
+ SocketRoute,
10
+ sendJson,
10
11
  SOCKET_OPTIONS,
11
12
  METHODS
12
13
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "redweb",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
4
4
  "description": "A way to quickly set up an express server",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -4,94 +4,35 @@
4
4
  class BaseHandler {
5
5
  /**
6
6
  * Creates a new handler instance.
7
- * @param {{ name: string, handlers: Record<string, (socket: WebSocket, data: object) => void> }} config
8
- * - `name`: The unique name of the handler.
9
- * - `handlers`: A dictionary of message types and their corresponding handler functions.
7
+ * @param {string} name - The name of the Handler, used in the client 'type' arg of request e.g {"type": "<handler-name>", ...}
10
8
  */
11
- constructor(config) {
9
+ constructor(name) {
12
10
  /**
13
- * The name of the handler (used to identify this handler in the server).
11
+ * he name of the Handler, used in the client 'type' arg of request e.g {"type": "<handler-name>", ...}.
14
12
  * @type {string}
15
13
  */
16
- this.name = config.name;
17
-
18
- /**
19
- * The dictionary of message handlers for this handler.
20
- * @type {Record<string, (socket: WebSocket, data: object) => void>}
21
- */
22
- this.messageHandlers = config.handlers || {};
23
-
24
- /**
25
- * The list of active WebSocket connections managed by this handler.
26
- * @type {WebSocket[]}
27
- */
28
- this.connections = [];
29
- }
30
-
31
- handleSocketMessages(socket) {
32
- socket.on('message', (message) => this.handleMessage(socket, message));
33
- }
34
-
35
- handleSocketClose(socket) {
36
- socket.on('close', () => {
37
- this.onClose(socket);
38
- this.connections = this.connections.filter((conn) => conn !== socket);
39
- console.log(`Connection closed for handler "${this.name}".`);
40
- });
41
- }
42
-
43
- handleNewConnection(socket) {
44
- this.connections.push(socket);
45
- this.handleSocketMessages(socket);
46
- this.handleSocketClose(socket);
47
- socket.isAssigned = true;
48
- console.log(`New connection added to handler "${this.name}".`);
14
+ this.name = name;
49
15
  }
50
-
51
16
  /**
52
- * Adds a new WebSocket connection and sets up message handling for this handler.
53
- * @param {WebSocket} socket - The WebSocket connection to add.
17
+ * Handles an incoming message and routes it to the appropriate handler function.
18
+ * @param {WebSocket & {sendJson: (message: Object) => void, broadcast: (message: Object) => void}} socket - The WebSocket connection that sent the message.
19
+ * @param {string} message - The incoming message in JSON string format.
54
20
  */
55
- newConnection(socket, data) {
56
- console.log(`Received newConnection data ${JSON.stringify(data)}`);
57
- this.handleNewConnection(socket);
58
- }
59
-
60
- onClose(socket) {
61
- console.log(`Closing socket ${socket.remoteAddress}`);
21
+ handleMessage(socket, message) {
22
+ this.onMessage(socket, message);
62
23
  }
63
24
 
64
25
  /**
65
- * Handles an incoming message and routes it to the appropriate handler function.
26
+ * Method to be overriden to process messages.
66
27
  * @param {WebSocket} socket - The WebSocket connection that sent the message.
67
28
  * @param {string} message - The incoming message in JSON string format.
68
29
  */
69
- handleMessage(socket, message) {
70
- try {
71
- const parsedMessage = JSON.parse(message);
72
- const { type, ...data } = parsedMessage;
73
-
74
- if (this.messageHandlers[type]) {
75
- this.messageHandlers[type](socket, data);
76
- } else {
77
- console.warn(`Unhandled message type: "${type}" in handler "${this.name}".`);
78
- socket.close();
79
- }
80
- } catch (error) {
81
- console.error(`Error processing message in handler "${this.name}":`, error);
82
- }
30
+ onMessage(socket, message) {
31
+ throw "Not yet implemented!";
83
32
  }
84
33
 
85
- /**
86
- * Broadcasts a message to all connections managed by this handler.
87
- * @param {object} message - The message to broadcast.
88
- */
89
- broadcast(message) {
90
- this.connections.forEach((socket) => {
91
- if (socket.readyState === WebSocket.OPEN) {
92
- socket.send(JSON.stringify(message));
93
- }
94
- });
34
+ onInitialContact(socket) {
35
+
95
36
  }
96
37
  }
97
38
 
@@ -1,26 +1,16 @@
1
- const WebSocket = require('ws');
2
-
3
1
  /**
4
2
  * @typedef {Object} SocketServerOptions
5
3
  * @property {import('http').Server} [server] - The HTTP server instance to bind the WebSocket server to.
6
4
  * @property {number} [port=3000] - The port number for the WebSocket server.
7
- * @property {(socket: WebSocket) => void} [connectionOpenCallback] - Callback executed when a client connects.
8
- * @property {(socket: WebSocket) => void} [connectionCloseCallback] - Callback executed when a client disconnects.
9
- * @property {(socket: WebSocket, message: object) => void} [messageCallback] - Callback executed when a message is received.
10
- * @property {Record<string, (socket: WebSocket, data: object) => void>} [messageHandlers] - A dictionary of message types and their corresponding handler functions.
11
- * @property {{ key: string, cert: string } | null} [ssl] - SSL configuration for a secure WebSocket server.
12
- * @property {Array<new () => BaseHandler>} [handlerConfig] - An array of handler classes to use for routing.
5
+ * @property {Array<new () => import('./SocketRoute').SocketRoute>} [routes] - An array of handler classes to use for routing.
13
6
  */
14
7
 
8
+ const DefaultRoute = require('./DefaultRoute');
9
+
15
10
  const SOCKET_OPTIONS = {
16
11
  port: 3000,
17
- connectionOpenCallback: undefined,
18
- connectionCloseCallback: undefined,
19
- messageCallback: undefined,
20
- messageHandlers: {
21
- ping: (socket, data) => socket.send(JSON.stringify({ type: 'pong' }))
22
- },
23
- ssl: null
12
+ ssl: null,
13
+ routes: []
24
14
  };
25
15
 
26
16
  /**
@@ -41,162 +31,26 @@ class BaseSocketServer {
41
31
  * @param {SocketServerOptions} options - The configuration options for the WebSocket server.
42
32
  */
43
33
  constructor(server, options = {}) {
44
- this.wss = new WebSocket.Server({ server });
45
34
  this.clients = new Map(); // Map of clients by their IP addresses.
46
35
  Object.assign(this, { ...SOCKET_OPTIONS, ...options });
47
-
48
- this.handlers = this.initHandlers(options.handlerConfig || []);
49
- this.wss.on('connection', this.handleConnection.bind(this));
50
- }
51
-
52
- /**
53
- * Initializes the handler classes.
54
- * @param {Array<new () => BaseHandler>} handlerConfig - Array of handler classes.
55
- * @returns {BaseHandler[]} - Array of handler instances.
56
- */
57
- initHandlers(handlerConfig) {
58
- return handlerConfig.map(HandlerClass => new HandlerClass());
59
- }
60
-
61
- /**
62
- * Adds a new handler to the WebSocket server.
63
- * @param {new () => BaseHandler} HandlerClass - The handler class to add.
64
- */
65
- addHandler(HandlerClass) {
66
- const newHandler = new HandlerClass();
67
- if (this.handlers.find(handler => handler.name === newHandler.name)) {
68
- console.warn(`Handler with name '${newHandler.name}' already exists.`);
69
- return;
70
- }
71
- this.handlers.push(newHandler);
72
- console.log(`Handler '${newHandler.name}' added successfully.`);
73
- }
74
-
75
- /**
76
- * Handles a new WebSocket connection.
77
- * @param {WebSocket} socket - The WebSocket connection instance.
78
- * @param {import('http').IncomingMessage} req - The HTTP request object associated with the connection.
79
- */
80
- handleConnection(socket, req) {
81
- const ip = req.socket.remoteAddress;
82
- console.log(`New client connected: ${ip}`);
83
- if (this.clients.get(ip) !== undefined) {
84
- const oldClient = this.clients.get(ip);
85
- console.warn(`Client ${ip} already connected, disconnecting existing connection.`);
86
- oldClient.send(
87
- JSON.stringify({ msg: 'You are being disconnected because a new client is connected with your IP address.' })
88
- );
89
- oldClient.close();
90
- }
91
- this.clients.set(ip, socket);
92
- socket.isAssigned = false; // Tracks whether the socket has been assigned a handler.
93
-
94
- if (this.connectionOpenCallback) this.connectionOpenCallback(socket);
95
-
96
- socket.on('message', message => this.initialMessageHandler(socket, message, ip));
97
- socket.on('close', () => this.handleClose(socket, ip));
98
- socket.on('error', error => this.handleError(socket, error, ip));
99
- }
100
-
101
- /**
102
- * Processes the initial message to determine if a handler assignment is required.
103
- * @param {WebSocket} socket - The WebSocket connection instance.
104
- * @param {string} message - The received message.
105
- * @param {string} ip - The client's IP address.
106
- */
107
- initialMessageHandler(socket, message, ip) {
108
- try {
109
- const parsedMessage = JSON.parse(message);
110
-
111
- // If the first message is '__handlerConnect', attempt to assign a handler
112
- if (parsedMessage.type === '__handlerConnect') {
113
- this.assignToHandler(socket, parsedMessage);
114
- return;
115
- }
116
-
117
- // If no handler is assigned, use the default messageHandlers or fallback
118
- if (!socket.isAssigned) {
119
- this.handleMessage(socket, parsedMessage, ip);
120
- } else {
121
- throw new Error('Unexpected message after handler assignment');
122
- }
123
- } catch (error) {
124
- console.error(`Error handling initial message from ${ip}:`, error);
125
- socket.close(); // Optionally close the socket for invalid behavior.
126
- }
127
- }
128
-
129
- /**
130
- * Assigns the socket to a specific handler.
131
- * @param {WebSocket} socket - The WebSocket connection instance.
132
- * @param {{ handlerName: string, key?: string }} data - Data containing the handler name and optional authentication key.
133
- */
134
- assignToHandler(socket, connectMessage) {
135
- if (!connectMessage.data) {
136
- console.warn(`No data provided for handlerConnect message.`);
137
- socket.close();
138
- return;
139
- }
140
- const { handlerName, data } = connectMessage.data;
141
- const handler = this.handlers.find(h => h.name === handlerName);
142
- if (!handler) {
143
- console.warn(`Handler not found: ${handlerName}`);
144
- socket.send(JSON.stringify({ error: `Handler '${handlerName}' not found` }));
145
- socket.close();
146
- return;
147
- }
148
-
149
- // Optional: Perform authentication with `key`
150
- socket.removeAllListeners('message');
151
- handler.newConnection(socket, connectMessage.data);
152
- }
153
-
154
- /**
155
- * Handles a message using the default messageHandlers.
156
- * @param {WebSocket} socket - The WebSocket connection instance.
157
- * @param {{ type: string, [key: string]: any }} parsedMessage - The parsed message object.
158
- * @param {string} ip - The client's IP address.
159
- */
160
- handleMessage(socket, parsedMessage, ip) {
161
- const { type, ...data } = parsedMessage;
162
-
163
- if (this.messageCallback) this.messageCallback(socket, parsedMessage);
164
-
165
- if (this.messageHandlers[type]) {
166
- this.messageHandlers[type](socket, data);
167
- } else {
168
- console.warn(`Unhandled message type from ${ip}: ${type}`);
169
- socket.close();
170
- }
171
- }
172
-
173
- /**
174
- * Handles socket disconnection.
175
- * @param {WebSocket} socket - The WebSocket connection instance.
176
- * @param {string} ip - The client's IP address.
177
- */
178
- handleClose(socket, ip) {
179
- console.log(`Client disconnected: ${ip}`);
180
- this.clients.delete(ip);
181
- if (this.connectionCloseCallback) this.connectionCloseCallback(socket);
36
+ this.server = server;
37
+ if (!options.routes?.length) options.routes = [ DefaultRoute ];
38
+ this.routes = options.routes.map((route) => new route(server));
39
+ this.server.on('upgrade', this.handleUpgrade.bind(this));
182
40
  }
183
41
 
184
- /**
185
- * Handles socket errors.
186
- * @param {WebSocket} socket - The WebSocket connection instance.
187
- * @param {Error} error - The error object.
188
- * @param {string} ip - The client's IP address.
189
- */
190
- handleError(socket, error, ip) {
191
- console.error(`Socket error from ${ip}:`, error);
42
+ handleUpgrade(req, sock, head) {
43
+ const route = this.routes.find(route => route.path == req.url);
44
+ if (!route) sock.destroy();
45
+ else route.server.handleUpgrade(req, sock, head, (s, r) => route.server.emit('connection', s, r));
192
46
  }
193
47
 
194
48
  /**
195
- * Broadcasts a message to all connected clients.
196
- * @param {object} message - The message to broadcast.
49
+ *
50
+ * @param {new () => import('./SocketRoute')} route
197
51
  */
198
- broadcast(message) {
199
- broadcast(this, message);
52
+ addRoute(route) {
53
+ this.routes.push(new route(this.server));
200
54
  }
201
55
  }
202
56
 
@@ -0,0 +1,14 @@
1
+ const { BaseHandler } = require("./BaseHandler");
2
+ const { sendJson } = require("./util");
3
+
4
+ class DefaultHandler extends BaseHandler {
5
+ constructor() {
6
+ super("DefaultHandler");
7
+ }
8
+
9
+ onMessage(socket, message) {
10
+ socket.send(sendJson(`I got your message of ${JSON.stringify(message)}`));
11
+ }
12
+ }
13
+
14
+ module.exports = DefaultHandler;
@@ -0,0 +1,14 @@
1
+ const SocketRoute = require("./SocketRoute");
2
+ const DefaultHandler = require('./DefaultHandler');
3
+
4
+ class DefaultRoute extends SocketRoute {
5
+ constructor(server) {
6
+ super({
7
+ server,
8
+ path: "/",
9
+ handlers: [DefaultHandler]
10
+ })
11
+ }
12
+ }
13
+
14
+ module.exports = DefaultRoute;
@@ -0,0 +1,114 @@
1
+ const { WebSocketServer } = require("ws");
2
+ const { sendJson, broadcast } = require("./util");
3
+
4
+ /**
5
+ * Represents a WebSocket route configuration.
6
+ * This class is used to define a specific WebSocket endpoint (`path`) and its associated handlers.
7
+ */
8
+ class SocketRoute {
9
+ /**
10
+ * Creates a new instance of `SocketRoute`.
11
+ * @param {Object} options - Configuration options for the WebSocket route.
12
+ * @param {string} options.path - The path of the WebSocket route (e.g., `/chat`, `/lobby`).
13
+ * @param {import('./BaseHandler').BaseHandler[]} options.handlers - An array of handler instances that manage connections and messages for this route.
14
+ */
15
+ constructor({path, handlers } = {}) {
16
+ if (!path) {
17
+ throw new Error('A `path` must be specified for the SocketRoute.');
18
+ }
19
+ if (!handlers || !Array.isArray(handlers) || handlers.length === 0) {
20
+ throw new Error('At least one handler must be specified for the SocketRoute.');
21
+ }
22
+ /**
23
+ * The path of the WebSocket route.
24
+ * This determines the endpoint that clients must connect to (e.g., `ws://localhost:3000/chat`).
25
+ * @type {string}
26
+ */
27
+ this.path = path;
28
+ /**
29
+ * The array of handler instances associated with this route.
30
+ * Each handler is responsible for managing WebSocket connections and message handling logic.
31
+ * @type {import('./BaseHandler').BaseHandler[]}
32
+ */
33
+ this.handlers = handlers.map(HandlerClass => new HandlerClass());
34
+ this.clients = new Map();
35
+ this.server = new WebSocketServer({ noServer: true, path });
36
+ this.server.on('connection', this.handleConnection.bind(this));
37
+ }
38
+ /**
39
+ * Adds a new handler to the WebSocket server.
40
+ * @param {new () => BaseHandler} HandlerClass - The handler class to add.
41
+ */
42
+ addHandler(HandlerClass) {
43
+ const newHandler = new HandlerClass();
44
+ if (this.handlers.find(handler => handler.name === newHandler.name)) {
45
+ console.warn(`Handler with name '${newHandler.name}' already exists.`);
46
+ return;
47
+ }
48
+ this.handlers.push(newHandler);
49
+ console.log(`Handler '${newHandler.name}' added successfully.`);
50
+ }
51
+ /**
52
+ * Handles a new WebSocket connection.
53
+ * @param {WebSocket} socket - The WebSocket connection instance.
54
+ * @param {import('http').IncomingMessage} req - The HTTP request object associated with the connection.
55
+ */
56
+ handleConnection(socket, req) {
57
+ const ip = req.socket.remoteAddress;
58
+ console.log(`New client connected: ${ip}`);
59
+ if (this.clients.get(ip) !== undefined) {
60
+ const oldClient = this.clients.get(ip);
61
+ console.warn(`Client ${ip} already connected, disconnecting existing connection.`);
62
+ oldClient.send(
63
+ JSON.stringify({ msg: 'You are being disconnected because a new client is connected with your IP address.' })
64
+ );
65
+ oldClient.close();
66
+ }
67
+ this.clients.set(ip, socket);
68
+ socket.isAssigned = false; // Tracks whether the socket has been assigned a handler.
69
+ socket.sendJson = (data) => sendJson(socket, data);
70
+ socket.broadcast = (data) => broadcast([...this.clients.values()], data);
71
+
72
+ this.connectionOpenCallback(socket);
73
+ socket.on('message', (message) => this.handleMessage(socket, JSON.parse(message)));
74
+ socket.on('close', this.handleClose.bind(this));
75
+ socket.on('error', this.handleError.bind(this));
76
+ }
77
+
78
+ connectionOpenCallback(socket) {
79
+ console.log(`Opening new connection: ${socket.remoteAddress}`);
80
+ }
81
+
82
+ handleMessage(sock, data) {
83
+ const handler = this.handlers.find((handler) => handler.name == data.type);
84
+ if (!handler) {
85
+ sendJson(sock, {error: `No such handler ${data.type}`});
86
+ sock.close();
87
+ } else {
88
+ handler.handleMessage(sock, data);
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Handles socket disconnection.
94
+ * @param {WebSocket} socket - The WebSocket connection instance.
95
+ * @param {string} ip - The client's IP address.
96
+ */
97
+ handleClose(socket, ip) {
98
+ console.log(`Client disconnected: ${ip}`);
99
+ this.clients.delete(ip);
100
+ if (this.connectionCloseCallback) this.connectionCloseCallback(socket);
101
+ }
102
+
103
+ /**
104
+ * Handles socket errors.
105
+ * @param {WebSocket} socket - The WebSocket connection instance.
106
+ * @param {Error} error - The error object.
107
+ * @param {string} ip - The client's IP address.
108
+ */
109
+ handleError(socket, error, ip) {
110
+ console.error(`Socket error from ${ip}:`, error);
111
+ }
112
+ }
113
+
114
+ module.exports = SocketRoute;
package/src/ws/index.js CHANGED
@@ -2,5 +2,6 @@ const { SOCKET_OPTIONS } = require('./BaseSocketServer');
2
2
  module.exports = {
3
3
  SecureSocketServer: require('./SecureSocketServer'),
4
4
  SocketServer: require('./SocketServer'),
5
+ SocketRoute: require('./SocketRoute'),
5
6
  SOCKET_OPTIONS
6
7
  }
package/src/ws/util.js ADDED
@@ -0,0 +1,9 @@
1
+ function sendJson(socket, data) {
2
+ socket.send(JSON.stringify(data));
3
+ }
4
+
5
+ function broadcast(sockets, data) {
6
+ sockets.forEach(socket => sendJson(socket, data));
7
+ }
8
+
9
+ module.exports = { sendJson, broadcast }