UNPKG

@push.rocks/smartsocket

Version:

Provides easy and secure websocket communication mechanisms, including server and client implementation, function call routing, connection management, and tagging.

189 lines 14.8 kB
import * as plugins from './smartsocket.plugins.js'; import * as pluginsTyped from './smartsocket.pluginstyped.js'; // used in case no other server is supplied import { Smartsocket } from './smartsocket.classes.smartsocket.js'; import { logger } from './smartsocket.logging.js'; /** * class SocketServer * handles the WebSocket server in standalone mode, or provides hooks for smartserve integration */ export class SocketServer { constructor(smartSocketInstance) { this.httpServer = null; this.wsServer = null; /** * whether httpServer is standalone (created by us) */ this.standaloneServer = false; this.smartsocket = smartSocketInstance; } /** * Starts listening to incoming websocket connections (standalone mode). * If no port is specified, this is a no-op (hooks mode via smartserve). */ async start() { // If no port specified, we're in hooks mode - nothing to start if (!this.smartsocket.options.port) { return; } // Standalone mode - create our own HTTP server and WebSocket server const done = plugins.smartpromise.defer(); const httpModule = await this.smartsocket.smartenv.getSafeNodeModule('http'); const wsModule = await this.smartsocket.smartenv.getSafeNodeModule('ws'); const httpServer = httpModule.createServer(); this.httpServer = httpServer; this.standaloneServer = true; // Create WebSocket server attached to HTTP server const wsServer = new wsModule.WebSocketServer({ server: httpServer }); this.wsServer = wsServer; wsServer.on('connection', (ws) => { this.smartsocket.handleNewConnection(ws); }); httpServer.listen(this.smartsocket.options.port, () => { logger.log('success', `Server started in standalone mode on port ${this.smartsocket.options.port}`); done.resolve(); }); await done.promise; } /** * closes the server */ async stop() { const done = plugins.smartpromise.defer(); let resolved = false; if (this.wsServer) { // Close all WebSocket connections this.wsServer.clients.forEach((client) => { client.terminate(); }); this.wsServer.close(); this.wsServer = null; } if (this.httpServer && this.standaloneServer) { const resolveOnce = () => { if (!resolved) { resolved = true; this.httpServer = null; this.standaloneServer = false; done.resolve(); } }; this.httpServer.close(() => { resolveOnce(); }); // Add a timeout in case close callback doesn't fire const timeoutId = setTimeout(() => { resolveOnce(); }, 2000); // Ensure timeout doesn't keep process alive if (timeoutId.unref) { timeoutId.unref(); } } else { done.resolve(); } await done.promise; } /** * Returns WebSocket hooks for integration with smartserve. * Pass these hooks to SmartServe's websocket config. */ getSmartserveWebSocketHooks() { return { onOpen: async (peer) => { // Create a wrapper that adapts ISmartserveWebSocketPeer to WebSocket-like interface const wsLikeSocket = this.createWsLikeFromPeer(peer); await this.smartsocket.handleNewConnection(wsLikeSocket); }, onMessage: async (peer, message) => { // Dispatch message to the SocketConnection via the adapter const adapter = peer.data.get('smartsocket_adapter'); if (adapter) { let textData; if (message.type === 'text' && message.text) { textData = message.text; } else if (message.type === 'binary' && message.data) { // Convert binary to text (Buffer/Uint8Array to string) textData = new TextDecoder().decode(message.data); } if (textData) { adapter.dispatchMessage(textData); } } }, onClose: async (peer, code, reason) => { // Dispatch close to the SocketConnection via the adapter const adapter = peer.data.get('smartsocket_adapter'); if (adapter) { adapter.dispatchClose(); } }, onError: async (peer, error) => { // Dispatch error to the SocketConnection via the adapter const adapter = peer.data.get('smartsocket_adapter'); if (adapter) { adapter.dispatchError(); } }, }; } /** * Creates a WebSocket-like object from a smartserve peer * This allows our SocketConnection to work with both native WebSocket and smartserve peers */ createWsLikeFromPeer(peer) { const messageListeners = []; const closeListeners = []; const errorListeners = []; // Store the adapter on the peer for message routing peer.data.set('smartsocket_adapter', { dispatchMessage: (data) => { messageListeners.forEach((listener) => { listener({ data }); }); }, dispatchClose: () => { closeListeners.forEach((listener) => listener()); }, dispatchError: () => { errorListeners.forEach((listener) => listener()); }, }); return { get readyState() { return peer.readyState; }, send: (data) => peer.send(data), close: (code, reason) => peer.close(code, reason), addEventListener: (event, listener) => { if (event === 'message') { messageListeners.push(listener); } else if (event === 'close') { closeListeners.push(listener); } else if (event === 'error') { errorListeners.push(listener); } }, removeEventListener: (event, listener) => { if (event === 'message') { const idx = messageListeners.indexOf(listener); if (idx >= 0) messageListeners.splice(idx, 1); } else if (event === 'close') { const idx = closeListeners.indexOf(listener); if (idx >= 0) closeListeners.splice(idx, 1); } else if (event === 'error') { const idx = errorListeners.indexOf(listener); if (idx >= 0) errorListeners.splice(idx, 1); } }, }; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRzb2NrZXQuY2xhc3Nlcy5zb2NrZXRzZXJ2ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydHNvY2tldC5jbGFzc2VzLnNvY2tldHNlcnZlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLDBCQUEwQixDQUFDO0FBQ3BELE9BQU8sS0FBSyxZQUFZLE1BQU0sK0JBQStCLENBQUM7QUFFOUQsMkNBQTJDO0FBQzNDLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxzQ0FBc0MsQ0FBQztBQUNuRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFFbEQ7OztHQUdHO0FBQ0gsTUFBTSxPQUFPLFlBQVk7SUFVdkIsWUFBWSxtQkFBZ0M7UUFScEMsZUFBVSxHQUFnRSxJQUFJLENBQUM7UUFDL0UsYUFBUSxHQUEyQyxJQUFJLENBQUM7UUFFaEU7O1dBRUc7UUFDSyxxQkFBZ0IsR0FBRyxLQUFLLENBQUM7UUFHL0IsSUFBSSxDQUFDLFdBQVcsR0FBRyxtQkFBbUIsQ0FBQztJQUN6QyxDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksS0FBSyxDQUFDLEtBQUs7UUFDaEIsK0RBQStEO1FBQy9ELElBQUksQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUNuQyxPQUFPO1FBQ1QsQ0FBQztRQUVELG9FQUFvRTtRQUNwRSxNQUFNLElBQUksR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQzFDLE1BQU0sVUFBVSxHQUFHLE1BQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQyxRQUFRLENBQUMsaUJBQWlCLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDN0UsTUFBTSxRQUFRLEdBQUcsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUFDLFFBQVEsQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUV6RSxNQUFNLFVBQVUsR0FB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