redweb 0.5.1 → 0.5.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/index.d.ts CHANGED
@@ -34,6 +34,16 @@ declare module 'redweb' {
34
34
  };
35
35
  }
36
36
 
37
+ /**
38
+ * Handler configuration for WebSocket.
39
+ */
40
+ export interface HandlerConfig {
41
+ name: string;
42
+ handlers: {
43
+ [type: string]: (socket: WebSocket, data: any) => void;
44
+ };
45
+ }
46
+
37
47
  /**
38
48
  * Options for configuring a WebSocket server.
39
49
  */
@@ -49,78 +59,88 @@ declare module 'redweb' {
49
59
  key: string;
50
60
  cert: string;
51
61
  };
62
+ handlerConfig?: Array<new () => BaseHandler>;
52
63
  }
53
64
 
54
65
  /**
55
- * Base class for HTTP servers.
66
+ * Base class for WebSocket handlers.
56
67
  */
57
- export class BaseHttpServer {
58
- app: any;
59
- port: number;
60
- publicPaths: string[];
61
- services: Service[];
62
- listenCallback?: () => void;
63
- encoding: RedWebEncoding;
64
- ssl?: { key: string; cert: string; };
65
-
66
- constructor(options?: RedWebOptions);
68
+ export class BaseHandler {
69
+ /**
70
+ * The name of the handler (used to identify it in the server).
71
+ */
72
+ name: string;
73
+
74
+ /**
75
+ * Dictionary of message handlers for this handler.
76
+ */
77
+ messageHandlers: {
78
+ [type: string]: (socket: WebSocket, data: any) => void;
79
+ };
67
80
 
68
- // Additional methods can be added as needed.
81
+ /**
82
+ * List of active WebSocket connections managed by this handler.
83
+ */
84
+ connections: WebSocket[];
85
+
86
+ /**
87
+ * Creates a new handler instance.
88
+ * @param config - Configuration for the handler.
89
+ */
90
+ constructor(config: HandlerConfig);
91
+
92
+ /**
93
+ * Adds a new WebSocket connection and sets up message handling for this handler.
94
+ * @param socket - The WebSocket connection to add.
95
+ */
96
+ newConnection(socket: WebSocket): void;
97
+
98
+ /**
99
+ * Handles an incoming message and routes it to the appropriate handler function.
100
+ * @param socket - The WebSocket connection that sent the message.
101
+ * @param message - The incoming message in JSON string format.
102
+ */
103
+ handleMessage(socket: WebSocket, message: string): void;
104
+
105
+ /**
106
+ * Broadcasts a message to all connections managed by this handler.
107
+ * @param message - The message to broadcast.
108
+ */
109
+ broadcast(message: object): void;
69
110
  }
70
111
 
71
112
  /**
72
113
  * HTTP server class.
73
114
  */
74
- export class HttpServer extends BaseHttpServer {
115
+ export class HttpServer {
75
116
  constructor(options?: RedWebOptions);
76
117
  }
77
118
 
78
119
  /**
79
120
  * HTTPS server class.
80
121
  */
81
- export class HttpsServer extends BaseHttpServer {
122
+ export class HttpsServer {
82
123
  constructor(options?: RedWebOptions);
83
124
  }
84
125
 
85
- /**
86
- * Base class for WebSocket servers.
87
- */
88
- export class BaseSocketServer {
89
- wss: WebSocket.Server;
90
- clients: Map<string, WebSocket>;
91
- port: number;
92
- connectionOpenCallback?: (socket: WebSocket) => void;
93
- connectionCloseCallback?: (socket: WebSocket) => void;
94
- messageCallback?: (socket: WebSocket, message: string) => void;
95
- messageHandlers?: { [type: string]: (socket: WebSocket, data: any) => void; };
96
- ssl?: { key: string; cert: string; };
97
-
98
- constructor(server: HTTPServer | HTTPSServer, options?: SocketServerOptions);
99
-
100
- handleConnection(socket: WebSocket, req: Request): void;
101
- handleMessage(socket: WebSocket, message: string, ip: string): void;
102
- handleClose(socket: WebSocket, ip: string): void;
103
- handleError(socket: WebSocket, error: Error, ip: string): void;
104
- }
105
-
106
126
  /**
107
127
  * WebSocket server class.
108
128
  */
109
- export class SocketServer extends BaseSocketServer {
129
+ export class SocketServer {
110
130
  constructor(options?: SocketServerOptions);
111
131
  }
112
132
 
113
133
  /**
114
134
  * Secure WebSocket server class.
115
135
  */
116
- export class SecureSocketServer extends BaseSocketServer {
136
+ export class SecureSocketServer {
117
137
  constructor(options?: SocketServerOptions);
118
138
  }
119
139
 
120
140
  /**
121
141
  * SSL configuration loader.
122
142
  */
123
- export function loadSslConfig(sslOptions: { key: string; cert: string; }): { key: string; cert: string; };
143
+ export function loadSslConfig(sslOptions: { key: string; cert: string }): { key: string; cert: string };
124
144
 
125
145
  /**
126
146
  * Constants for encoding types.
package/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  const { HttpServer, HttpsServer, METHODS } = require('./src/http');
2
2
  const { SocketServer, SecureSocketServer, SOCKET_OPTIONS } = require('./src/ws');
3
+ const { BaseHandler } = require('./src/ws/BaseHandler');
3
4
  module.exports = {
4
5
  SocketServer,
5
6
  SecureSocketServer,
7
+ BaseHandler,
6
8
  HttpServer,
7
9
  HttpsServer,
8
10
  SOCKET_OPTIONS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "redweb",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "A way to quickly set up an express server",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -53,9 +53,11 @@ function BaseHttpServer(options = {}) {
53
53
  this.app.use(bodyParser.urlencoded({ extended: true }));
54
54
  }
55
55
  this.app.use(cors(this.options.corsOptions));
56
-
57
- this.services.forEach(service => this.app[service.method](service.serviceName, service.function));
58
56
  this.publicPaths.forEach(public_path => this.app.use(express.static(path.join(process.cwd(), public_path))));
57
+ const catchAll = this.services.find((service) => service.serviceName === '*');
58
+ if (catchAll) this.services.splice(this.services.indexOf(catchAll), 1);
59
+ this.services.forEach(service => this.app[service.method](service.serviceName, service.function));
60
+ if (catchAll) this.app[catchAll.method](catchAll.serviceName, catchAll.function);
59
61
  return this;
60
62
  }
61
63
 
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Represents the base class for a WebSocket message handler.
3
+ */
4
+ class BaseHandler {
5
+ /**
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.
10
+ */
11
+ constructor(config) {
12
+ /**
13
+ * The name of the handler (used to identify this handler in the server).
14
+ * @type {string}
15
+ */
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
+ /**
32
+ * Adds a new WebSocket connection and sets up message handling for this handler.
33
+ * @param {WebSocket} socket - The WebSocket connection to add.
34
+ */
35
+ newConnection(socket) {
36
+ this.connections.push(socket);
37
+
38
+ socket.on('message', (message) => this.handleMessage(socket, message));
39
+
40
+ socket.on('close', () => {
41
+ this.connections = this.connections.filter((conn) => conn !== socket);
42
+ console.log(`Connection closed for handler "${this.name}".`);
43
+ });
44
+
45
+ socket.isAssigned = true;
46
+ console.log(`New connection added to handler "${this.name}".`);
47
+ }
48
+
49
+ /**
50
+ * Handles an incoming message and routes it to the appropriate handler function.
51
+ * @param {WebSocket} socket - The WebSocket connection that sent the message.
52
+ * @param {string} message - The incoming message in JSON string format.
53
+ */
54
+ handleMessage(socket, message) {
55
+ try {
56
+ const parsedMessage = JSON.parse(message);
57
+ const { type, ...data } = parsedMessage;
58
+
59
+ if (this.messageHandlers[type]) {
60
+ this.messageHandlers[type](socket, data);
61
+ } else {
62
+ console.warn(`Unhandled message type: "${type}" in handler "${this.name}".`);
63
+ socket.close();
64
+ }
65
+ } catch (error) {
66
+ console.error(`Error processing message in handler "${this.name}":`, error);
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Broadcasts a message to all connections managed by this handler.
72
+ * @param {object} message - The message to broadcast.
73
+ */
74
+ broadcast(message) {
75
+ this.connections.forEach((socket) => {
76
+ if (socket.readyState === WebSocket.OPEN) {
77
+ socket.send(JSON.stringify(message));
78
+ }
79
+ });
80
+ }
81
+ }
82
+
83
+ module.exports = { BaseHandler };
@@ -2,15 +2,14 @@ const WebSocket = require('ws');
2
2
 
3
3
  /**
4
4
  * @typedef {Object} SocketServerOptions
5
- * @property {import('express').Application} [server] - The existing server instance.
6
- * @property {number} [port=3000] - The port number to bind the socket server.
7
- * @property {Function} [connectionOpenCallback] - Callback function to execute once a client connects.
8
- * @property {Function} [connectionCloseCallback] - Callback function to execute once a client disconnects.
9
- * @property {Function} [messageCallback] - Callback function to execute for every message received.
10
- * @property {Object} [messageHandlers] - Object containing message handlers based on message type.
11
- * @property {Object} [ssl] - SSL configuration for SecureSocketServer.
12
- * @property {string} [ssl.key] - Path to the SSL key file.
13
- * @property {string} [ssl.cert] - Path to the SSL certificate file.
5
+ * @property {import('http').Server} [server] - The HTTP server instance to bind the WebSocket server to.
6
+ * @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.
14
13
  */
15
14
 
16
15
  const SOCKET_OPTIONS = {
@@ -25,72 +24,158 @@ const SOCKET_OPTIONS = {
25
24
  };
26
25
 
27
26
  /**
28
- *
29
- * @param {BaseSocketServer} socketServer
30
- * @param {Object} message
27
+ * Broadcasts a message to all connected clients.
28
+ * @param {BaseSocketServer} socketServer - The WebSocket server instance.
29
+ * @param {object} message - The message to broadcast.
31
30
  */
32
31
  function broadcast(socketServer, message) {
33
32
  socketServer.clients.forEach(client => client.send(JSON.stringify(message)));
34
33
  }
35
34
 
35
+ /**
36
+ * Represents the base WebSocket server.
37
+ */
36
38
  class BaseSocketServer {
37
39
  /**
38
- * Base Socket Server
39
- * @param {import('express').Application} server - The express application instance.
40
- * @param {SocketServerOptions} options - Configuration options for SocketServer.
40
+ * @param {import('http').Server} server - The HTTP server instance to bind the WebSocket server to.
41
+ * @param {SocketServerOptions} options - The configuration options for the WebSocket server.
41
42
  */
42
43
  constructor(server, options = {}) {
43
44
  this.wss = new WebSocket.Server({ server });
44
- this.clients = new Map(); // Use a Map to store clients by their IP addresses
45
+ this.clients = new Map(); // Map of clients by their IP addresses.
45
46
  Object.assign(this, { ...SOCKET_OPTIONS, ...options });
46
47
 
48
+ this.handlers = this.initHandlers(options.handlerConfig || []);
47
49
  this.wss.on('connection', this.handleConnection.bind(this));
48
50
  }
49
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
+ * Handles a new WebSocket connection.
63
+ * @param {WebSocket} socket - The WebSocket connection instance.
64
+ * @param {import('http').IncomingMessage} req - The HTTP request object associated with the connection.
65
+ */
50
66
  handleConnection(socket, req) {
51
67
  const ip = req.socket.remoteAddress;
52
68
  console.log(`New client connected: ${ip}`);
53
69
  this.clients.set(ip, socket);
70
+ socket.isAssigned = false; // Tracks whether the socket has been assigned a handler.
54
71
 
55
72
  if (this.connectionOpenCallback) this.connectionOpenCallback(socket);
56
73
 
57
- socket.on('message', (message) => this.handleMessage(socket, message, ip));
74
+ socket.on('message', (message) => this.initialMessageHandler(socket, message, ip));
58
75
  socket.on('close', () => this.handleClose(socket, ip));
59
76
  socket.on('error', (error) => this.handleError(socket, error, ip));
60
77
  }
61
78
 
62
- handleMessage(socket, message, ip) {
79
+ /**
80
+ * Processes the initial message to determine if a handler assignment is required.
81
+ * @param {WebSocket} socket - The WebSocket connection instance.
82
+ * @param {string} message - The received message.
83
+ * @param {string} ip - The client's IP address.
84
+ */
85
+ initialMessageHandler(socket, message, ip) {
63
86
  try {
64
- console.info(`Received message from ${ip}: ${message}`);
65
- if (this.messageCallback) this.messageCallback(socket, message);
87
+ const parsedMessage = JSON.parse(message);
88
+
89
+ // If the first message is '__handlerConnect', attempt to assign a handler
90
+ if (parsedMessage.type === '__handlerConnect') {
91
+ this.assignToHandler(socket, parsedMessage);
92
+ return;
93
+ }
66
94
 
67
- const { type, ...data } = JSON.parse(message);
68
- if (this.messageHandlers[type]) {
69
- this.messageHandlers[type](socket, data);
95
+ // If no handler is assigned, use the default messageHandlers or fallback
96
+ if (!socket.isAssigned) {
97
+ this.handleMessage(socket, parsedMessage, ip);
98
+ } else {
99
+ throw new Error('Unexpected message after handler assignment');
70
100
  }
71
101
  } catch (error) {
72
- console.error(`Error handling message from ${ip}:`, error);
102
+ console.error(`Error handling initial message from ${ip}:`, error);
103
+ socket.close(); // Optionally close the socket for invalid behavior.
73
104
  }
74
105
  }
75
106
 
107
+ /**
108
+ * Assigns the socket to a specific handler.
109
+ * @param {WebSocket} socket - The WebSocket connection instance.
110
+ * @param {{ handlerName: string, key?: string }} data - Data containing the handler name and optional authentication key.
111
+ */
112
+ assignToHandler(socket, connectMessage) {
113
+ if (!connectMessage.data) {
114
+ console.warn(`No data provided for handlerConnect message.`);
115
+ socket.close();
116
+ return;
117
+ }
118
+ const { handlerName, data } = connectMessage.data;
119
+ const handler = this.handlers.find(h => h.name === handlerName);
120
+ if (!handler) {
121
+ console.warn(`Handler not found: ${handlerName}`);
122
+ socket.send(JSON.stringify({ error: `Handler '${handlerName}' not found` }));
123
+ socket.close();
124
+ return;
125
+ }
126
+
127
+ // Optional: Perform authentication with `key`
128
+ socket.removeAllListeners('message');
129
+ handler.newConnection(socket, data);
130
+ }
131
+
132
+ /**
133
+ * Handles a message using the default messageHandlers.
134
+ * @param {WebSocket} socket - The WebSocket connection instance.
135
+ * @param {{ type: string, [key: string]: any }} parsedMessage - The parsed message object.
136
+ * @param {string} ip - The client's IP address.
137
+ */
138
+ handleMessage(socket, parsedMessage, ip) {
139
+ const { type, ...data } = parsedMessage;
140
+
141
+ if (this.messageCallback) this.messageCallback(socket, parsedMessage);
142
+
143
+ if (this.messageHandlers[type]) {
144
+ this.messageHandlers[type](socket, data);
145
+ } else {
146
+ console.warn(`Unhandled message type from ${ip}: ${type}`);
147
+ socket.close();
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Handles socket disconnection.
153
+ * @param {WebSocket} socket - The WebSocket connection instance.
154
+ * @param {string} ip - The client's IP address.
155
+ */
76
156
  handleClose(socket, ip) {
77
157
  console.log(`Client disconnected: ${ip}`);
78
158
  this.clients.delete(ip);
79
159
  if (this.connectionCloseCallback) this.connectionCloseCallback(socket);
80
160
  }
81
161
 
162
+ /**
163
+ * Handles socket errors.
164
+ * @param {WebSocket} socket - The WebSocket connection instance.
165
+ * @param {Error} error - The error object.
166
+ * @param {string} ip - The client's IP address.
167
+ */
82
168
  handleError(socket, error, ip) {
83
169
  console.error(`Socket error from ${ip}:`, error);
84
170
  }
85
-
86
- /**
87
- *
88
- * This sends the same message to all clients.
89
- * @param {Object} message
90
- */
171
+
172
+ /**
173
+ * Broadcasts a message to all connected clients.
174
+ * @param {object} message - The message to broadcast.
175
+ */
91
176
  broadcast(message) {
92
177
  broadcast(this, message);
93
178
  }
94
179
  }
95
180
 
96
- module.exports = {BaseSocketServer, SOCKET_OPTIONS};
181
+ module.exports = { BaseSocketServer, SOCKET_OPTIONS };