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 { #config = undefined; #server = undefined; #clients = 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) { this.#config = config; this.#server = null; } /** * Start the server with the provided server instance. * @param {Object} server - The server instance to start. */ start(server) { this.#server = server; } /** * 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); 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) { 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) { const clientId = Math.random().toString(36).substring(2); const success = this.#server.upgrade(req, { data: { clientId } }); if (success) { this.#clients.set(clientId, { id: clientId }); return undefined; } return Response.redirect('/', 302); } /** * Handle the opening of a WebSocket connection. * @param {WebSocket} ws - The WebSocket connection. */ handleOpen(ws) { const client = new SocketClient(ws.data.clientId, ws); this.#clients.set(client.id, client); const plugins = [...this.#plugins.keys()].filter(key => { return typeof(this.#plugins.get(key).plugin.actions) == 'function' }).map(key => { return { identifier: key, actions: this.#plugins.get(key).plugin.actions() }; }); if(plugins.length > 0) { client.send({ event: 'plugins', data: plugins }); } 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) { const client = this.getValidClient(ws); if (!client) return; 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) { // 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)); } } }