@underpostnet/underpost
Version:
Underpost Platform — end-to-end CI/CD and application-delivery toolchain CLI. Covers bare metal, Kubernetes, K3s, kubeadm, LXD, container/image orchestration, secrets, databases, cron jobs, monitoring, SSH, runners, PWA + Workbox delivery, and release orc
130 lines (117 loc) • 4.83 kB
JavaScript
/**
* Module for creating and managing WebSocket channels.
* @module src/ws/IoInterface.js
* @namespace SocketIoInterface
*/
import { loggerFactory } from '../server/logger.js';
import { Socket } from 'socket.io';
const logger = loggerFactory(import.meta);
/**
* Defines the structure for a WebSocket channel's behavior functions.
* @typedef {Object} ChannelInterface
* @property {string} channel - The name of the channel.
* @property {function(Socket, Object.<string, Socket>, string): Promise<void>} [connection] - Handler on client connection.
* @property {function(Socket, Object.<string, Socket>, any, string, any[]): Promise<void>} [controller] - Handler for incoming channel messages.
* @property {function(Socket, Object.<string, Socket>, string, string): Promise<void>} [disconnect] - Handler on client disconnection.
* @property {boolean} [stream=false] - Whether the channel should treat the message as a raw stream (no JSON parsing).
* @memberof SocketIoInterface
*/
/**
* Manages the logic, client map, and event listeners for a specific WebSocket channel,
* ensuring robust message handling and lifecycle management.
* @class IoChannel
* @memberof SocketIoInterface
*/
class IoChannel {
/**
* @method
* @type {ChannelInterface}
*/
#IoInterface;
/**
* Map of connected sockets for this channel, keyed by socket ID.
* @type {Object.<string, Socket>}
*/
client = {};
/**
* Creates an instance of IoChannel.
* @param {ChannelInterface} IoInterface - The interface object defining the channel's behavior.
*/
constructor(IoInterface) {
this.#IoInterface = {
channel: '',
connection: async (socket = {}, client = {}, hostKeyContext = '') => { },
controller: async (socket = {}, client = {}, payload = {}, hostKeyContext = '', args = []) => { },
disconnect: async (socket = {}, client = {}, reason = '', hostKeyContext = '') => { },
stream: false,
...IoInterface,
};
logger.debug(`Channel instance created for: ${this.channel}`);
}
/**
* Gets the name of the channel.
* @returns {string} The channel name.
*/
get channel() {
return this.#IoInterface.channel;
}
/**
* Handles a new socket connection for this channel.
* Sets up the listener for the channel message.
*
* @param {Socket} socket - The Socket.IO socket object.
* @param {string} hostKeyContext - Unique identifier for the WebSocket management context.
* @returns {Promise<void>}
*/
async connection(socket, hostKeyContext) {
try {
this.client[socket.id] = socket;
// Use bind/arrow function to maintain 'this' context for the controller
socket.on(this.channel, (...args) => this.controller(socket, args, hostKeyContext));
await this.#IoInterface.connection(socket, this.client, hostKeyContext);
logger.debug(`Socket ${socket.id} connected to channel ${this.channel}`);
} catch (error) {
logger.error(error, { channel: this.channel, hostKeyContext, stack: error.stack });
}
}
/**
* Handles incoming messages on the channel.
*
* @method
* @param {Socket} socket - The Socket.IO socket object.
* @param {any[]} args - The raw arguments received from the socket event.
* @param {string} hostKeyContext - Unique identifier for the WebSocket management context.
* @returns {Promise<void>}
*/
async controller(socket, args, hostKeyContext) {
try {
if (!args || args.length === 0) {
logger.warn(`No arguments received for channel: ${this.channel}`, { socketId: socket.id });
return;
}
// Determine if JSON parsing is needed based on the stream flag
const payload = this.#IoInterface.stream ? args[0] : JSON.parse(args[0]);
await this.#IoInterface.controller(socket, this.client, payload, hostKeyContext, args);
} catch (error) {
logger.error(error, { channel: this.channel, hostKeyContext, socketId: socket.id, args, stack: error.stack });
}
}
/**
* Handles a socket disconnection for this channel.
*
* @param {Socket} socket - The Socket.IO socket object.
* @param {string} reason - The reason for disconnection (e.g., 'client namespace disconnect').
* @param {string} hostKeyContext - Unique identifier for the WebSocket management context.
* @returns {Promise<void>}
*/
async disconnect(socket, reason, hostKeyContext) {
try {
await this.#IoInterface.disconnect(socket, this.client, reason, hostKeyContext);
delete this.client[socket.id];
logger.debug(`Socket ${socket.id} disconnected from channel ${this.channel}. Reason: ${reason}`);
} catch (error) {
logger.error(error, { channel: this.channel, hostKeyContext, reason, socketId: socket.id, stack: error.stack });
}
}
}
export { IoChannel };