nano-invoke
Version:
Simple IPC client for Nano Framework applications
247 lines • 7.5 kB
JavaScript
/**
* Nano Framework IPC Client
*
* Simple, clean invoke function for calling nano backend functions
*
* @package nano-invoke
* @author Imperium Industries
* @version 1.0.0
*/
// Default configuration
let config = {
baseUrl: '',
debug: false,
timeout: 10000
};
/**
* Configure the invoke client
*
* @param newConfig - Configuration options
*/
export function configure(newConfig) {
config = { ...config, ...newConfig };
}
/**
* Simple invoke function for calling nano functions
*
* @param cmd - Function name to invoke
* @param args - Arguments to pass to the function
* @returns Promise resolving to the function result
*/
export async function invoke(cmd, args = {}) {
const request = {
cmd,
args: args || {}
};
try {
if (config.debug) {
console.log(`[Nano IPC] Invoking: ${cmd}`, args);
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), config.timeout);
const response = await fetch(`${config.baseUrl}/ipc`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(request),
signal: controller.signal
});
clearTimeout(timeoutId);
if (config.debug) {
console.log(`[Nano IPC] Response status: ${response.status} ${response.statusText}`);
}
if (!response.ok) {
const errorText = await response.text();
if (config.debug) {
console.error(`[Nano IPC] Error response:`, errorText);
}
if (response.status === 404) {
throw new Error('IPC endpoint not found. Make sure the Nano server is running.');
}
throw new Error(`HTTP ${response.status}: ${response.statusText} - ${errorText}`);
}
const result = await response.json();
if (config.debug) {
console.log(`[Nano IPC] Result:`, result);
}
if (result.error) {
throw new Error(result.error);
}
return result.result;
}
catch (error) {
if (config.debug) {
console.error(`[Nano IPC] Invoke error:`, error);
}
if (error instanceof Error && error.name === 'AbortError') {
throw new Error(`Request timeout after ${config.timeout}ms`);
}
if (error instanceof TypeError && error.message.includes('fetch')) {
throw new Error('Cannot connect to server. Make sure the Nano application is running.');
}
throw new Error(error instanceof Error ? error.message : String(error));
}
}
export class EventManager {
constructor() {
this.ws = null;
this.listeners = new Map();
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectDelay = 1000;
}
/**
* Connect to the WebSocket server
*/
async connect(wsUrl) {
return new Promise((resolve, reject) => {
try {
const url = wsUrl || `ws://${window.location.host}/ws`;
this.ws = new WebSocket(url);
this.ws.onopen = () => {
this.reconnectAttempts = 0;
if (config.debug) {
console.log('[Nano Events] Connected to WebSocket');
}
resolve();
};
this.ws.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
this.emit(message.event || 'message', message.payload || message);
}
catch (error) {
if (config.debug) {
console.error('[Nano Events] Failed to parse event:', error);
}
}
};
this.ws.onclose = () => {
if (config.debug) {
console.log('[Nano Events] WebSocket connection closed');
}
this.attemptReconnect();
};
this.ws.onerror = (error) => {
if (config.debug) {
console.error('[Nano Events] WebSocket error:', error);
}
reject(new Error('WebSocket connection failed'));
};
}
catch (error) {
reject(error);
}
});
}
/**
* Disconnect from WebSocket
*/
disconnect() {
if (this.ws) {
this.ws.close();
this.ws = null;
}
}
/**
* Subscribe to an event
*/
on(event, listener) {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event).add(listener);
}
/**
* Unsubscribe from an event
*/
off(event, listener) {
const eventListeners = this.listeners.get(event);
if (eventListeners) {
eventListeners.delete(listener);
}
}
/**
* Send a message through the WebSocket
*/
send(message) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(message));
}
else {
throw new Error('WebSocket is not connected');
}
}
emit(event, data) {
const eventListeners = this.listeners.get(event);
if (eventListeners) {
eventListeners.forEach(listener => {
try {
listener(data);
}
catch (error) {
if (config.debug) {
console.error(`[Nano Events] Error in event listener:`, error);
}
}
});
}
}
async attemptReconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
const delay = this.reconnectDelay * this.reconnectAttempts;
if (config.debug) {
console.log(`[Nano Events] Attempting reconnection ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${delay}ms`);
}
setTimeout(() => {
this.connect().catch(() => {
// Reconnection failed, will try again
});
}, delay);
}
}
}
// Default event manager instance
export const events = new EventManager();
// Utility functions
export var Utils;
(function (Utils) {
/**
* Ping the nano server
*/
async function ping() {
return await invoke('ping');
}
Utils.ping = ping;
/**
* Get system information
*/
async function getSystemInfo() {
return await invoke('get_system_info');
}
Utils.getSystemInfo = getSystemInfo;
/**
* Check if the nano server is accessible
*/
async function isServerRunning() {
try {
await ping();
return true;
}
catch {
return false;
}
}
Utils.isServerRunning = isServerRunning;
})(Utils || (Utils = {}));
// Export everything as default as well
export default {
invoke,
configure,
events,
EventManager,
Utils
};
//# sourceMappingURL=index.js.map