redweb 0.5.0 → 0.5.2
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 +58 -38
- package/index.js +2 -0
- package/package.json +1 -1
- package/src/http/BaseHttpServer.js +4 -2
- package/src/ws/BaseHandler.js +83 -0
- package/src/ws/BaseSocketServer.js +116 -31
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
|
|
66
|
+
* Base class for WebSocket handlers.
|
|
56
67
|
*/
|
|
57
|
-
export class
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
@@ -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
|
+
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('
|
|
6
|
-
* @property {number} [port=3000] - The port number
|
|
7
|
-
* @property {
|
|
8
|
-
* @property {
|
|
9
|
-
* @property {
|
|
10
|
-
* @property {
|
|
11
|
-
* @property {
|
|
12
|
-
* @property {
|
|
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 {
|
|
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
|
-
*
|
|
39
|
-
* @param {
|
|
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();
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
65
|
-
|
|
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
|
-
|
|
68
|
-
if (
|
|
69
|
-
this.
|
|
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
|
-
|
|
89
|
-
|
|
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 };
|