UNPKG

@push.rocks/smartipc

Version:

A library for node inter process communication, providing an easy-to-use API for IPC.

427 lines 32.6 kB
import * as plugins from './smartipc.plugins.js'; import { IpcTransport, createTransport } from './classes.transports.js'; /** * IPC Channel with connection management, auto-reconnect, and typed messaging */ export class IpcChannel extends plugins.EventEmitter { constructor(options) { super(); this.pendingRequests = new Map(); this.messageHandlers = new Map(); this.reconnectAttempts = 0; this.lastHeartbeat = Date.now(); this.connectionStartTime = Date.now(); this.isReconnecting = false; this.isClosing = false; // Metrics this.metrics = { messagesSent: 0, messagesReceived: 0, bytesSent: 0, bytesReceived: 0, reconnects: 0, heartbeatTimeouts: 0, errors: 0, requestTimeouts: 0, connectedAt: 0 }; this.options = { autoReconnect: true, reconnectDelay: 1000, maxReconnectDelay: 30000, reconnectMultiplier: 1.5, maxReconnectAttempts: Infinity, heartbeat: true, heartbeatInterval: 5000, heartbeatTimeout: 10000, ...options }; // Normalize heartbeatThrowOnTimeout to boolean (defensive for JS consumers) const throwOnTimeout = this.options.heartbeatThrowOnTimeout; if (throwOnTimeout !== undefined) { if (throwOnTimeout === 'false') { this.options.heartbeatThrowOnTimeout = false; } else if (throwOnTimeout === 'true') { this.options.heartbeatThrowOnTimeout = true; } else if (typeof throwOnTimeout !== 'boolean') { this.options.heartbeatThrowOnTimeout = Boolean(throwOnTimeout); } } this.transport = createTransport(this.options); this.setupTransportHandlers(); } /** * Setup transport event handlers */ setupTransportHandlers() { this.transport.on('connect', () => { this.reconnectAttempts = 0; this.isReconnecting = false; this.metrics.connectedAt = Date.now(); this.startHeartbeat(); this.emit('connect'); }); this.transport.on('disconnect', (reason) => { this.stopHeartbeat(); this.clearPendingRequests(new Error(`Disconnected: ${reason || 'Unknown reason'}`)); this.emit('disconnect', reason); if (this.options.autoReconnect && !this.isClosing) { this.scheduleReconnect(); } }); this.transport.on('error', (error) => { this.emit('error', error); }); this.transport.on('message', (message) => { this.handleMessage(message); }); // Forward per-client disconnects from transports that support multi-client servers // We re-emit a 'clientDisconnected' event with the clientId if known so higher layers can act. // eslint-disable-next-line @typescript-eslint/no-explicit-any this.transport.on?.('clientDisconnected', (_socket, clientId) => { this.emit('clientDisconnected', clientId); }); this.transport.on('drain', () => { this.emit('drain'); }); } /** * Connect the channel */ async connect() { if (this.transport.isConnected()) { return; } try { await this.transport.connect(); } catch (error) { this.emit('error', error); if (this.options.autoReconnect && !this.isClosing) { this.scheduleReconnect(); } else { throw error; } } } /** * Disconnect the channel */ async disconnect() { this.isClosing = true; this.stopHeartbeat(); this.cancelReconnect(); this.clearPendingRequests(new Error('Channel closed')); await this.transport.disconnect(); } /** * Schedule a reconnection attempt */ scheduleReconnect() { if (this.isReconnecting || this.isClosing) { return; } if (this.options.maxReconnectAttempts !== Infinity && this.reconnectAttempts >= this.options.maxReconnectAttempts) { this.emit('error', new Error('Maximum reconnection attempts reached')); return; } this.isReconnecting = true; this.reconnectAttempts++; // Calculate delay with exponential backoff and jitter const baseDelay = Math.min(this.options.reconnectDelay * Math.pow(this.options.reconnectMultiplier, this.reconnectAttempts - 1), this.options.maxReconnectDelay); const jitter = Math.random() * 0.1 * baseDelay; // 10% jitter const delay = baseDelay + jitter; this.emit('reconnecting', { attempt: this.reconnectAttempts, delay }); this.reconnectTimer = setTimeout(async () => { try { await this.transport.connect(); } catch (error) { // Connection failed, will be rescheduled by disconnect handler } }, delay); } /** * Cancel scheduled reconnection */ cancelReconnect() { if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = undefined; } this.isReconnecting = false; } /** * Start heartbeat mechanism */ startHeartbeat() { if (!this.options.heartbeat) { return; } this.stopHeartbeat(); this.lastHeartbeat = Date.now(); this.connectionStartTime = Date.now(); // Send heartbeat messages this.heartbeatTimer = setInterval(() => { this.sendMessage('__heartbeat__', { timestamp: Date.now() }).catch(() => { // Ignore heartbeat send errors }); }, this.options.heartbeatInterval); // Delay starting the check until after the grace period const gracePeriod = this.options.heartbeatInitialGracePeriodMs || 0; if (gracePeriod > 0) { // Use a timer to delay the first check this.heartbeatGraceTimer = setTimeout(() => { this.startHeartbeatCheck(); }, gracePeriod); } else { // No grace period, start checking immediately this.startHeartbeatCheck(); } } /** * Start heartbeat timeout checking (separated for grace period handling) */ startHeartbeatCheck() { // Check for heartbeat timeout this.heartbeatCheckTimer = setInterval(() => { const timeSinceLastHeartbeat = Date.now() - this.lastHeartbeat; if (timeSinceLastHeartbeat > this.options.heartbeatTimeout) { const error = new Error('Heartbeat timeout'); if (this.options.heartbeatThrowOnTimeout !== false) { // Default behavior: emit error which may cause disconnect this.emit('error', error); this.transport.disconnect().catch(() => { }); } else { // Emit heartbeatTimeout event instead of error this.emit('heartbeatTimeout', error); // Clear timers to avoid repeated events this.stopHeartbeat(); } } }, Math.max(1000, Math.floor(this.options.heartbeatTimeout / 2))); } /** * Stop heartbeat mechanism */ stopHeartbeat() { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = undefined; } if (this.heartbeatCheckTimer) { clearInterval(this.heartbeatCheckTimer); this.heartbeatCheckTimer = undefined; } if (this.heartbeatGraceTimer) { clearTimeout(this.heartbeatGraceTimer); this.heartbeatGraceTimer = undefined; } } /** * Handle incoming messages */ handleMessage(message) { // Track metrics this.metrics.messagesReceived++; this.metrics.bytesReceived += JSON.stringify(message).length; // Handle heartbeat and send response if (message.type === '__heartbeat__') { this.lastHeartbeat = Date.now(); // Reply so the sender also observes liveness this.transport.send({ id: plugins.crypto.randomUUID(), type: '__heartbeat_response__', correlationId: message.id, timestamp: Date.now(), payload: { timestamp: Date.now() }, headers: message.headers?.clientId ? { clientId: message.headers.clientId } : undefined }).catch(() => { }); return; } // Handle heartbeat response if (message.type === '__heartbeat_response__') { this.lastHeartbeat = Date.now(); return; } // Handle request/response if (message.correlationId && this.pendingRequests.has(message.correlationId)) { const pending = this.pendingRequests.get(message.correlationId); this.pendingRequests.delete(message.correlationId); if (pending.timer) { clearTimeout(pending.timer); } if (message.headers?.error) { pending.reject(new Error(message.headers.error)); } else { pending.resolve(message.payload); } return; } // Handle regular messages if (this.messageHandlers.has(message.type)) { const handler = this.messageHandlers.get(message.type); // If message expects a response if (message.headers?.requiresResponse && message.id) { Promise.resolve() .then(() => handler(message.payload)) .then((result) => { const response = { id: plugins.crypto.randomUUID(), type: `${message.type}_response`, correlationId: message.id, timestamp: Date.now(), payload: result, headers: message.headers?.clientId ? { clientId: message.headers.clientId } : undefined }; return this.transport.send(response); }) .catch((error) => { const response = { id: plugins.crypto.randomUUID(), type: `${message.type}_response`, correlationId: message.id, timestamp: Date.now(), payload: null, headers: { error: error.message, ...(message.headers?.clientId ? { clientId: message.headers.clientId } : {}) } }; return this.transport.send(response); }); } else { // Fire and forget try { handler(message.payload); } catch (error) { this.emit('error', error); } } } else { // Emit unhandled message this.emit('message', message); } } /** * Send a message without expecting a response */ async sendMessage(type, payload, headers) { // Extract correlationId from headers and place it at top level const { correlationId, ...restHeaders } = headers ?? {}; const message = { id: plugins.crypto.randomUUID(), type, timestamp: Date.now(), payload, ...(correlationId ? { correlationId } : {}), headers: Object.keys(restHeaders).length ? restHeaders : undefined }; const success = await this.transport.send(message); if (!success) { this.metrics.errors++; throw new Error('Failed to send message'); } // Track metrics this.metrics.messagesSent++; this.metrics.bytesSent += JSON.stringify(message).length; } /** * Send a request and wait for response */ async request(type, payload, options) { const messageId = plugins.crypto.randomUUID(); const timeout = options?.timeout || 30000; const message = { id: messageId, type, timestamp: Date.now(), payload, headers: { ...options?.headers, requiresResponse: true } }; return new Promise((resolve, reject) => { // Setup timeout const timer = setTimeout(() => { this.pendingRequests.delete(messageId); reject(new Error(`Request timeout for ${type}`)); }, timeout); // Store pending request this.pendingRequests.set(messageId, { resolve, reject, timer }); // Send message with better error handling this.transport.send(message) .then((success) => { if (!success) { this.pendingRequests.delete(messageId); clearTimeout(timer); reject(new Error('Failed to send message')); } }) .catch((error) => { this.pendingRequests.delete(messageId); clearTimeout(timer); reject(error); }); }); } /** * Register a message handler */ on(event, handler) { if (event === 'message' || event === 'connect' || event === 'disconnect' || event === 'error' || event === 'reconnecting' || event === 'drain' || event === 'heartbeatTimeout' || event === 'clientDisconnected') { // Special handling for channel events super.on(event, handler); } else { // Register as message type handler this.messageHandlers.set(event, handler); } return this; } /** * Clear all pending requests */ clearPendingRequests(error) { for (const [id, pending] of this.pendingRequests) { if (pending.timer) { clearTimeout(pending.timer); } pending.reject(error); } this.pendingRequests.clear(); } /** * Check if channel is connected */ isConnected() { return this.transport.isConnected(); } /** * Get channel statistics */ getStats() { return { connected: this.transport.isConnected(), reconnectAttempts: this.reconnectAttempts, pendingRequests: this.pendingRequests.size, isReconnecting: this.isReconnecting, metrics: { ...this.metrics, uptime: this.metrics.connectedAt ? Date.now() - this.metrics.connectedAt : undefined } }; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5pcGNjaGFubmVsLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5pcGNjaGFubmVsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sdUJBQXVCLENBQUM7QUFDakQsT0FBTyxFQUFFLFlBQVksRUFBRSxlQUFlLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQXNDeEU7O0dBRUc7QUFDSCxNQUFNLE9BQU8sVUFBNEMsU0FBUSxPQUFPLENBQUMsWUFBWTtJQTRCbkYsWUFBWSxPQUEyQjtRQUNyQyxLQUFLLEVBQUUsQ0FBQztRQTFCRixvQkFBZSxHQUFHLElBQUksR0FBRyxFQUEyQixDQUFDO1FBQ3JELG9CQUFlLEdBQUcsSUFBSSxHQUFHLEVBQWdELENBQUM7UUFDMUUsc0JBQWlCLEdBQUcsQ0FBQyxDQUFDO1FBS3RCLGtCQUFhLEdBQVcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBQ25DLHdCQUFtQixHQUFXLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztRQUN6QyxtQkFBYyxHQUFHLEtBQUssQ0FBQztRQUN2QixjQUFTLEdBQUcsS0FBSyxDQUFDO1FBRTFCLFVBQVU7UUFDRixZQUFPLEdBQUc7WUFDaEIsWUFBWSxFQUFFLENBQUM7WUFDZixnQkFBZ0IsRUFBRSxDQUFDO1lBQ25CLFNBQVMsRUFBRSxDQUFDO1lBQ1osYUFBYSxFQUFFLENBQUM7WUFDaEIsVUFBVSxFQUFFLENBQUM7WUFDYixpQkFBaUIsRUFBRSxDQUFDO1lBQ3BCLE1BQU0sRUFBRSxDQUFDO1lBQ1QsZUFBZSxFQUFFLENBQUM7WUFDbEIsV0FBVyxFQUFFLENBQUM7U0FDZixDQUFDO1FBSUEsSUFBSSxDQUFDLE9BQU8sR0FBRztZQUNiLGFBQWEsRUFBRSxJQUFJO1lBQ25CLGNBQWMsRUFBRSxJQUFJO1lBQ3BCLGlCQUFpQixFQUFFLEtBQUs7WUFDeEIsbUJBQW1CLEVBQUUsR0FBRztZQUN4QixvQkFBb0Is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