@push.rocks/smartipc
Version:
A library for node inter process communication, providing an easy-to-use API for IPC.
275 lines • 20.5 kB
JavaScript
import * as plugins from './smartipc.plugins.js';
import { IpcChannel } from './classes.ipcchannel.js';
/**
* IPC Client for connecting to an IPC server
*/
export class IpcClient extends plugins.EventEmitter {
constructor(options) {
super();
this.messageHandlers = new Map();
this.isConnected = false;
this.didRegisterOnce = false;
this.options = options;
this.clientId = options.clientId || plugins.crypto.randomUUID();
// Create the channel
this.channel = new IpcChannel(this.options);
this.setupChannelHandlers();
}
/**
* Connect to the server
*/
async connect(connectOptions = {}) {
if (this.isConnected) {
return;
}
// Helper function to attempt registration
const attemptRegistration = async () => {
await this.attemptRegistrationInternal();
};
// Helper function to attempt connection with retry
const attemptConnection = async () => {
const retryConfig = this.options.connectRetry;
const maxAttempts = retryConfig?.maxAttempts || 1;
const initialDelay = retryConfig?.initialDelay || 100;
const maxDelay = retryConfig?.maxDelay || 1500;
const totalTimeout = retryConfig?.totalTimeout || 15000;
const startTime = Date.now();
let lastError;
let delay = initialDelay;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
// Check total timeout
if (totalTimeout && Date.now() - startTime > totalTimeout) {
throw new Error(`Connection timeout after ${totalTimeout}ms: ${lastError?.message || 'Unknown error'}`);
}
try {
// Connect the channel
await this.channel.connect();
// Attempt registration
await attemptRegistration();
return; // Success!
}
catch (error) {
lastError = error;
// Disconnect channel for retry
await this.channel.disconnect().catch(() => { });
// If this isn't the last attempt and retry is enabled, wait before retrying
if (attempt < maxAttempts && retryConfig?.enabled) {
// Check if we have time for another attempt
if (totalTimeout && Date.now() - startTime + delay > totalTimeout) {
break; // Will timeout, don't wait
}
await new Promise(resolve => setTimeout(resolve, delay));
// Exponential backoff with max limit
delay = Math.min(delay * 2, maxDelay);
}
}
}
// All attempts failed
throw lastError || new Error('Failed to connect to server');
};
// If waitForReady is specified, wait for server socket to exist first
if (connectOptions.waitForReady) {
const waitTimeout = connectOptions.waitTimeout || 10000;
const startTime = Date.now();
while (Date.now() - startTime < waitTimeout) {
try {
// Try to connect
await attemptConnection();
return; // Success!
}
catch (error) {
// If it's a connection refused error, server might not be ready yet
if (error.message?.includes('ECONNREFUSED') ||
error.message?.includes('ENOENT')) {
await new Promise(resolve => setTimeout(resolve, 100));
continue;
}
// Other errors should be thrown
throw error;
}
}
throw new Error(`Server not ready after ${waitTimeout}ms`);
}
else {
// Normal connection attempt
await attemptConnection();
}
}
/**
* Attempt to register this client over the current channel connection.
* Sets connection flags and emits 'connect' on success.
*/
async attemptRegistrationInternal() {
const registerTimeoutMs = this.options.registerTimeoutMs || 5000;
try {
const response = await this.channel.request('__register__', {
clientId: this.clientId,
metadata: this.options.metadata
}, {
timeout: registerTimeoutMs,
headers: { clientId: this.clientId }
});
if (!response.success) {
throw new Error(response.error || 'Registration failed');
}
this.isConnected = true;
this.didRegisterOnce = true;
this.emit('connect');
}
catch (error) {
throw new Error(`Failed to register with server: ${error.message}`);
}
}
/**
* Disconnect from the server
*/
async disconnect() {
if (!this.isConnected) {
return;
}
this.isConnected = false;
await this.channel.disconnect();
this.emit('disconnect');
}
/**
* Setup channel event handlers
*/
setupChannelHandlers() {
// Forward channel events
this.channel.on('connect', async () => {
// On reconnects, re-register automatically when we had connected before
if (this.didRegisterOnce && !this.isConnected) {
try {
await this.attemptRegistrationInternal();
}
catch (error) {
this.emit('error', error);
}
}
// For initial connect(), registration is handled explicitly there
});
this.channel.on('disconnect', (reason) => {
this.isConnected = false;
this.emit('disconnect', reason);
});
this.channel.on('error', (error) => {
// If heartbeat timeout and configured not to throw, convert to heartbeatTimeout event
if (error && error.message === 'Heartbeat timeout' && this.options.heartbeatThrowOnTimeout === false) {
this.emit('heartbeatTimeout', error);
return;
}
this.emit('error', error);
});
this.channel.on('heartbeatTimeout', (error) => {
// Forward heartbeatTimeout event (when heartbeatThrowOnTimeout is false)
this.emit('heartbeatTimeout', error);
});
this.channel.on('reconnecting', (info) => {
this.emit('reconnecting', info);
});
// Handle messages
this.channel.on('message', (message) => {
// Check if we have a handler for this message type
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) => {
return this.channel.sendMessage(`${message.type}_response`, result, { correlationId: message.id });
})
.catch((error) => {
return this.channel.sendMessage(`${message.type}_response`, null, { correlationId: message.id, error: error.message });
});
}
else {
// Fire and forget
handler(message.payload);
}
}
else {
// Emit unhandled message
this.emit('message', message);
}
});
}
/**
* Register a message handler
*/
onMessage(type, handler) {
this.messageHandlers.set(type, handler);
}
/**
* Send a message to the server
*/
async sendMessage(type, payload, headers) {
if (!this.isConnected) {
throw new Error('Client is not connected');
}
// Always include clientId in headers
await this.channel.sendMessage(type, payload, {
...headers,
clientId: this.clientId
});
}
/**
* Send a request to the server and wait for response
*/
async request(type, payload, options) {
if (!this.isConnected) {
throw new Error('Client is not connected');
}
// Always include clientId in headers
return this.channel.request(type, payload, {
...options,
headers: {
...options?.headers,
clientId: this.clientId
}
});
}
/**
* Subscribe to a topic (pub/sub pattern)
*/
async subscribe(topic, handler) {
// Register local handler
this.messageHandlers.set(`topic:${topic}`, handler);
// Notify server about subscription
await this.sendMessage('__subscribe__', { topic });
}
/**
* Unsubscribe from a topic
*/
async unsubscribe(topic) {
// Remove local handler
this.messageHandlers.delete(`topic:${topic}`);
// Notify server about unsubscription
await this.sendMessage('__unsubscribe__', { topic });
}
/**
* Publish to a topic
*/
async publish(topic, payload) {
await this.sendMessage('__publish__', { topic, payload });
}
/**
* Get client ID
*/
getClientId() {
return this.clientId;
}
/**
* Check if client is connected
*/
getIsConnected() {
return this.isConnected && this.channel.isConnected();
}
/**
* Get client statistics
*/
getStats() {
return this.channel.getStats();
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5pcGNjbGllbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLmlwY2NsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHVCQUF1QixDQUFDO0FBQ2pELE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQXFDckQ7O0dBRUc7QUFDSCxNQUFNLE9BQU8sU0FBVSxTQUFRLE9BQU8sQ0FBQyxZQUFZO0lBUWpELFlBQVksT0FBMEI7UUFDcEMsS0FBSyxFQUFFLENBQUM7UUFORixvQkFBZSxHQUFHLElBQUksR0FBRyxFQUFnRCxDQUFDO1FBQzFFLGdCQUFXLEdBQUcsS0FBSyxDQUFDO1FBRXBCLG9CQUFlLEdBQUcsS0FBSyxDQUFDO1FBSTlCLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDO1FBQ3ZCLElBQUksQ0FBQyxRQUFRLEdBQUcsT0FBTyxDQUFDLFFBQVEsSUFBSSxPQUFPLENBQUMsTUFBTSxDQUFDLFVBQVUsRUFBRSxDQUFDO1FBRWhFLHFCQUFxQjtRQUNyQixJQUFJLENBQUMsT0FBTyxHQUFHLElBQUksVUFBVSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUM1QyxJQUFJLENBQUMsb0JBQW9CLEVBQUUsQ0FBQztJQUM5QixDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsT0FBTyxDQUFDLGlCQUF3QyxFQUFFO1FBQzdELElBQUksSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO1lBQ3JCLE9BQU87UUFDVCxDQUFDO1FBRUQsMENBQTBDO1FBQzFDLE1BQU0sbUJBQW1CLEdBQUcsS0FBSyxJQUFtQixFQUFFO1lBQ3BELE1BQU0sSUFBSSxDQUFDLDJCQUEyQixFQUFFLENBQUM7UUFDM0MsQ0FBQyxDQUFDO1FBRUYsbURBQW1EO1FBQ25ELE1BQU0saUJBQWlCLEdBQUcsS0FBSyxJQUFtQixFQUFFO1lBQ2xELE1BQU0sV0FB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