@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
JavaScript
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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