imp(wetsock) plugin handling and implementation
This commit is contained in:
33
lib/WetSock/Message.js
Normal file
33
lib/WetSock/Message.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Represents a chat message.
|
||||||
|
*/
|
||||||
|
export default class ChatMessage {
|
||||||
|
#origin;
|
||||||
|
#data;
|
||||||
|
#message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of ChatMessage.
|
||||||
|
* @param {string} rawText - The raw text of the chat message.
|
||||||
|
*/
|
||||||
|
constructor(rawText) {
|
||||||
|
this.#origin = rawText;
|
||||||
|
try {
|
||||||
|
this.#data = JSON.parse(rawText.trim());
|
||||||
|
this.#message = this.#data.message;
|
||||||
|
} catch(err) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits the properties of the chat message through the provided WebSocket.
|
||||||
|
* @param {WebSocket} wetsock - The WebSocket instance to emit the properties through.
|
||||||
|
*/
|
||||||
|
emitProps(wetsock) {
|
||||||
|
if (!this.#data || typeof this.#data !== 'object') return;
|
||||||
|
for (const key in this.#data) {
|
||||||
|
if (typeof this.#data[key] === 'string' && this.#data[key].indexOf(' ') === -1) {
|
||||||
|
wetsock.emit(`message:${key}:${this.#data[key]}`, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,82 @@
|
|||||||
|
/**
|
||||||
|
* BasePlugin class represents the base class for WetSock plugins.
|
||||||
|
*/
|
||||||
export default class BasePlugin {
|
export default class BasePlugin {
|
||||||
#wetsock = undefined;
|
#wetsock = undefined;
|
||||||
constructor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance of BasePlugin.
|
||||||
|
*/
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the WetSock server instance.
|
||||||
|
* @returns {Object} The WetSock server instance.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
_getServer() {
|
_getServer() {
|
||||||
return this.#wetsock;
|
return this.#wetsock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of available actions.
|
||||||
|
* @returns {Array} The keys of available actions.
|
||||||
|
*/
|
||||||
|
actions() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the plugin with the WetSock server instance.
|
||||||
|
* @param {Object} wetsock - The WetSock server instance.
|
||||||
|
*/
|
||||||
initialize(wetsock) {
|
initialize(wetsock) {
|
||||||
this.#wetsock = wetsock;
|
this.#wetsock = wetsock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register an event listener.
|
||||||
|
* @param {string} event - The event name.
|
||||||
|
* @param {function} listener - The event listener function.
|
||||||
|
*/
|
||||||
|
on(event, listener) {
|
||||||
|
this.#wetsock.on(event, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an event listener.
|
||||||
|
* @param {string} event - The event name.
|
||||||
|
* @param {function} listener - The event listener function to remove.
|
||||||
|
*/
|
||||||
|
off(event, listener) {
|
||||||
|
this.#wetsock.off(event, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit an event.
|
||||||
|
* @param {string} event - The event name.
|
||||||
|
* @param {...*} args - The arguments to pass to the event listeners.
|
||||||
|
*/
|
||||||
|
emit(event, ...args) {
|
||||||
|
this.#wetsock.emit(event, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an incoming message from a client.
|
||||||
|
* @param {string} clientId - The ID of the client.
|
||||||
|
* @param {*} message - The message received from the client.
|
||||||
|
*/
|
||||||
onMessage(clientId, message) {}
|
onMessage(clientId, message) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a client connection.
|
||||||
|
* @param {string} clientId - The ID of the connected client.
|
||||||
|
*/
|
||||||
onOpen(clientId) {}
|
onOpen(clientId) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a client disconnection.
|
||||||
|
* @param {string} clientId - The ID of the disconnected client.
|
||||||
|
*/
|
||||||
onClose(clientId) {}
|
onClose(clientId) {}
|
||||||
}
|
}
|
||||||
|
|||||||
27
lib/WetSock/SocketClient.js
Normal file
27
lib/WetSock/SocketClient.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Represents a connected WebSocket client
|
||||||
|
*/
|
||||||
|
export default class SocketClient {
|
||||||
|
#id;
|
||||||
|
#ws;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new websocket server client instance.
|
||||||
|
* @param {string} id - The unique identifier of the client.
|
||||||
|
* @param {ServerWebSocket} - The Socket object for sending messages.
|
||||||
|
*/
|
||||||
|
constructor(id, ws) {
|
||||||
|
this.#id = id;
|
||||||
|
this.#ws = ws;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a message to the client
|
||||||
|
* @param {*} message - The data to be sent to the client. Will be converted to string if not a string.
|
||||||
|
*/
|
||||||
|
send(message) {
|
||||||
|
if(typeof(message) != 'string') message = JSON.stringify(message);
|
||||||
|
|
||||||
|
this.#ws.send(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,61 +1,182 @@
|
|||||||
|
import SocketClient from './SocketClient';
|
||||||
|
import Message from './Message';
|
||||||
|
/**
|
||||||
|
* WetSock class represents the main server class for handling WebSocket connections and plugins.
|
||||||
|
*/
|
||||||
export default class WetSock {
|
export default class WetSock {
|
||||||
#config = undefined;
|
#config = undefined;
|
||||||
#server = undefined;
|
#server = undefined;
|
||||||
#clients = new Map();
|
#clients = new Map();
|
||||||
#plugins = new Map();
|
#plugins = new Map();
|
||||||
|
#listeners = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance of WetSock.
|
||||||
|
* @param {Object} config - The configuration object for the server.
|
||||||
|
*/
|
||||||
constructor(config) {
|
constructor(config) {
|
||||||
this.#config = config;
|
this.#config = config;
|
||||||
this.#server = null;
|
this.#server = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the server with the provided server instance.
|
||||||
|
* @param {Object} server - The server instance to start.
|
||||||
|
*/
|
||||||
start(server) {
|
start(server) {
|
||||||
this.#server = server;
|
this.#server = server;
|
||||||
}
|
}
|
||||||
|
|
||||||
registerPlugin(identifier, plugin) {
|
/**
|
||||||
|
* Register a plugin with the specified identifier and priority.
|
||||||
|
* @param {string} identifier - The identifier for the plugin.
|
||||||
|
* @param {Object} plugin - The plugin instance to register.
|
||||||
|
* @param {number} priority - The priority of the plugin (default: 0).
|
||||||
|
*/
|
||||||
|
registerPlugin(identifier, plugin, priority = 0) {
|
||||||
plugin.initialize(this);
|
plugin.initialize(this);
|
||||||
this.#plugins.set(identifier, plugin);
|
this.#plugins.set(identifier, { plugin, priority });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the plugin with the specified identifier.
|
||||||
|
* @param {string} identifier - The identifier of the plugin.
|
||||||
|
* @returns {Object} The plugin instance.
|
||||||
|
*/
|
||||||
getPlugin(identifier) {
|
getPlugin(identifier) {
|
||||||
return this.#plugins.get(identifier);
|
return this.#plugins.get(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the WebSocket upgrade request.
|
||||||
|
* @param {Object} req - The upgrade request object.
|
||||||
|
* @returns {Response} The response to send back.
|
||||||
|
*/
|
||||||
handleUpgrade(req) {
|
handleUpgrade(req) {
|
||||||
const clientId = Math.random().toString(36).substring(2);
|
const clientId = Math.random().toString(36).substring(2);
|
||||||
const success = this.#server.upgrade(req, { data: { clientId } });
|
const success = this.#server.upgrade(req, { data: { clientId } });
|
||||||
if (success) {
|
if (success) {
|
||||||
|
this.#clients.set(clientId, { id: clientId });
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return Response.redirect('/', 302);
|
return Response.redirect('/', 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMessage(ws, message) {
|
/**
|
||||||
const client = this.#clients.get(ws.data.clientId);
|
* Handle the opening of a WebSocket connection.
|
||||||
|
* @param {WebSocket} ws - The WebSocket connection.
|
||||||
try {
|
*/
|
||||||
const messageData = JSON.parse(message);
|
|
||||||
if(typeof(messageData) == 'object' && messageData.constructor.name == 'Object') {
|
|
||||||
message = messageData;
|
|
||||||
}
|
|
||||||
} catch (err) {}
|
|
||||||
|
|
||||||
for(const plugin of this.#plugins.values()) {
|
|
||||||
if(plugin.onMessage(ws.data.clientId, message)) break; // plugin consumes message by returning a truthy value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handleOpen(ws) {
|
handleOpen(ws) {
|
||||||
const client = this.#clients.get(ws.data.clientId);
|
const client = new SocketClient(ws.data.clientId, ws);
|
||||||
this.#plugins.forEach((plugin) => plugin.onOpen(ws.data.clientId));
|
this.#clients.set(client.id, client);
|
||||||
|
client.send({
|
||||||
|
event: 'plugins',
|
||||||
|
data: [...this.#plugins.keys()].map(key => {
|
||||||
|
return {
|
||||||
|
identifier: key,
|
||||||
|
actions: this.#plugins.get(key).plugin.actions()
|
||||||
|
};
|
||||||
|
})
|
||||||
|
});
|
||||||
|
this.emit('open', client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an incoming message from a WebSocket client.
|
||||||
|
* @param {WebSocket} ws - The WebSocket connection.
|
||||||
|
* @param {string|Object} message - The message received from the client.
|
||||||
|
*/
|
||||||
|
handleMessage(ws, message) {
|
||||||
|
const client = this.getValidClient(ws);
|
||||||
|
if (!client) return;
|
||||||
|
ws.data.messages = Number(ws.data.messages) + 1;
|
||||||
|
const chatMessage = new Message(message);
|
||||||
|
chatMessage.emitProps(this);
|
||||||
|
this.emit('message', client, chatMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the closing of a WebSocket connection.
|
||||||
|
* @param {WebSocket} ws - The WebSocket connection.
|
||||||
|
*/
|
||||||
handleClose(ws) {
|
handleClose(ws) {
|
||||||
const client = this.#clients.get(ws.data.clientId);
|
const client = this.getValidClient(ws);
|
||||||
this.#plugins.forEach((plugin) => plugin.onClose(ws.data.clientId));
|
if (!client) return;
|
||||||
this.#clients.delete(ws.data.clientId);
|
this.emit('close', client, 'closed');
|
||||||
|
this.#clients.delete(client.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the valid client object associated with the WebSocket connection.
|
||||||
|
* @param {WebSocket} ws - The WebSocket connection.
|
||||||
|
* @returns {Object|null} The valid client object or null if the client is not valid.
|
||||||
|
*/
|
||||||
|
getValidClient(ws) {
|
||||||
|
const clientId = ws.data?.clientId;
|
||||||
|
if (!clientId || !this.#clients.has(clientId)) {
|
||||||
|
ws.close(1011, 'Invalid client');
|
||||||
|
this.emit('close', ws, 'invalid');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.#clients.get(clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the drain event of a WebSocket connection.
|
||||||
|
* @param {WebSocket} ws - The WebSocket connection.
|
||||||
|
*/
|
||||||
handleDrain(ws) {
|
handleDrain(ws) {
|
||||||
// Handle socket drain event
|
// Handle socket drain event
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message to a specific client.
|
||||||
|
* @param {string} clientId - The ID of the client to send the message to.
|
||||||
|
* @param {Object} message - The message to send.
|
||||||
|
*/
|
||||||
|
sendTo(clientId, message) {
|
||||||
|
const client = this.#clients.get(clientId);
|
||||||
|
if (client) {
|
||||||
|
client.send(JSON.stringify(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register an event listener.
|
||||||
|
* @param {string} event - The event name.
|
||||||
|
* @param {function} listener - The event listener function.
|
||||||
|
*/
|
||||||
|
on(event, listener) {
|
||||||
|
if (!this.#listeners.has(event)) {
|
||||||
|
this.#listeners.set(event, []);
|
||||||
|
}
|
||||||
|
this.#listeners.get(event).push(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an event listener.
|
||||||
|
* @param {string} event - The event name.
|
||||||
|
* @param {function} listener - The event listener function to remove.
|
||||||
|
*/
|
||||||
|
off(event, listener) {
|
||||||
|
if (this.#listeners.has(event)) {
|
||||||
|
const listeners = this.#listeners.get(event);
|
||||||
|
const index = listeners.indexOf(listener);
|
||||||
|
if (index !== -1) {
|
||||||
|
listeners.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit an event.
|
||||||
|
* @param {string} event - The event name.
|
||||||
|
* @param {...*} args - The arguments to pass to the event listeners.
|
||||||
|
*/
|
||||||
|
emit(event, ...args) {
|
||||||
|
if (this.#listeners.has(event)) {
|
||||||
|
this.#listeners.get(event).forEach((listener) => listener(...args));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user