UNPKG

ziti-sdk-c-nodejs

Version:

Node.js wrapper for OpenZiti C SDK

539 lines (485 loc) 17.2 kB
let bindings; let ZitiWrapper; // Try to load the native module with error handling try { bindings = require('bindings')('ziti_sdk_c'); ZitiWrapper = bindings.ZitiWrapper; } catch (error) { console.error('❌ Failed to load Ziti SDK native module:', error.message); console.error('💡 Make sure the Ziti SDK libraries are properly installed'); // Create a dummy wrapper for graceful degradation ZitiWrapper = class DummyZitiWrapper { constructor() { throw new Error('Ziti SDK native module not available. Please check installation.'); } }; } class ZitiSDK { constructor() { try { this.ziti = new ZitiWrapper(); this.initialized = false; } catch (error) { throw new Error(`Failed to initialize Ziti SDK: ${error.message}`); } } /** * Initialize the Ziti SDK */ init() { if (this.initialized) { throw new Error('Ziti SDK already initialized'); } this.ziti.init(); this.initialized = true; } /** * Load a Ziti identity context from file * @param {string} identityFile - Path to the identity file */ loadContext(identityFile) { if (!this.initialized) { throw new Error('Ziti SDK not initialized. Call init() first.'); } this.ziti.loadContext(identityFile); } /** * Load a Ziti identity context from JSON data * @param {string} identityData - JSON string containing identity data */ loadContextFromData(identityData) { if (!this.initialized) { throw new Error('Ziti SDK not initialized. Call init() first.'); } this.ziti.loadContextFromData(identityData); } /** * Create a new socket * @returns {number} Socket handle */ socket() { if (!this.initialized) { throw new Error('Ziti SDK not initialized. Call init() first.'); } return this.ziti.socket(); } /** * Connect to a Ziti service * @param {number} socket - Socket handle * @param {string} service - Service name * @param {string} terminator - Terminator (optional) * @returns {number} Error code (0 = success) */ connect(socket, service, terminator = null) { if (!this.initialized) { throw new Error('Ziti SDK not initialized. Call init() first.'); } return this.ziti.connect(socket, service, terminator); } /** * Bind to a Ziti service * @param {number} socket - Socket handle * @param {string} service - Service name * @param {string} terminator - Terminator (optional) * @returns {number} Error code (0 = success) */ bind(socket, service, terminator = null) { if (!this.initialized) { throw new Error('Ziti SDK not initialized. Call init() first.'); } return this.ziti.bind(socket, service, terminator); } /** * Listen for incoming connections * @param {number} socket - Socket handle * @param {number} backlog - Maximum pending connections * @returns {number} Error code (0 = success) */ listen(socket, backlog) { if (!this.initialized) { throw new Error('Ziti SDK not initialized. Call init() first.'); } return this.ziti.listen(socket, backlog); } /** * Accept an incoming connection * @param {number} socket - Server socket handle * @returns {Object} Object containing client socket and caller info */ accept(socket) { if (!this.initialized) { throw new Error('Ziti SDK not initialized. Call init() first.'); } return this.ziti.accept(socket); } /** * Write data to a socket * @param {number} socket - Socket handle * @param {Buffer} data - Data to write * @returns {number} Number of bytes written */ write(socket, data) { if (!this.initialized) { throw new Error('Ziti SDK not initialized. Call init() first.'); } if (!Buffer.isBuffer(data)) { data = Buffer.from(data); } return this.ziti.write(socket, data); } /** * Read data from a socket * @param {number} socket - Socket handle * @param {number} size - Maximum bytes to read * @returns {Buffer} Data read from socket */ read(socket, size) { if (!this.initialized) { throw new Error('Ziti SDK not initialized. Call init() first.'); } return this.ziti.read(socket, size); } /** * Close a socket * @param {number} socket - Socket handle * @returns {number} Error code (0 = success) */ close(socket) { if (!this.initialized) { throw new Error('Ziti SDK not initialized. Call init() first.'); } return this.ziti.close(socket); } /** * Shutdown the Ziti SDK */ shutdown() { if (this.initialized) { this.ziti.shutdown(); this.initialized = false; } } /** * Set the log level for OpenZiti * @param {number} level - Log level (0=NONE, 1=ERROR, 2=WARN, 3=INFO, 4=DEBUG, 5=VERBOSE, 6=TRACE) * @param {string} marker - Optional marker for filtering logs */ setLogLevel(level, marker = null) { if (!this.initialized) { throw new Error('Ziti SDK not initialized. Call init() first.'); } this.ziti.setLogLevel(level, marker); } /** * Set the log level by label * @param {string} level - Log level label ('NONE', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'VERBOSE', 'TRACE') */ setLogLevelByLabel(level) { if (!this.initialized) { throw new Error('Ziti SDK not initialized. Call init() first.'); } this.ziti.setLogLevelByLabel(level); } /** * Get the current log level label * @returns {string} Current log level label */ getLogLevelLabel() { if (!this.initialized) { throw new Error('Ziti SDK not initialized. Call init() first.'); } return this.ziti.getLogLevelLabel(); } /** * Set a custom log callback function * @param {Function} callback - Function to handle log messages (level, message) => void */ setLogCallback(callback) { if (!this.initialized) { throw new Error('Ziti SDK not initialized. Call init() first.'); } if (typeof callback !== 'function') { throw new Error('Callback must be a function'); } this.ziti.setLogCallback(callback); } /** * Set a log file for writing logs * @param {string} logFile - Path to the log file */ setLogFile(logFile) { if (!this.initialized) { throw new Error('Ziti SDK not initialized. Call init() first.'); } if (typeof logFile !== 'string') { throw new Error('Log file path must be a string'); } this.ziti.setLogFile(logFile); } /** * Enable or disable native OpenZiti logging * @param {boolean} enable - Whether to enable native logging */ enableNativeLogs(enable) { if (!this.initialized) { throw new Error('Ziti SDK not initialized. Call init() first.'); } if (typeof enable !== 'boolean') { throw new Error('Enable flag must be a boolean'); } this.ziti.enableNativeLogs(enable); } // Asynchronous API methods (like official SDK) /** * Initialize Ziti SDK asynchronously * @param {string} identityPath - Path to the identity file * @param {Function} callback - Callback function (status) */ zitiInit(identityPath, callback) { this.ziti.ziti_init(identityPath, callback); } /** * Dial to a Ziti service asynchronously * @param {string} serviceName - Service name * @param {boolean} isWebSocket - Is WebSocket connection * @param {Function} onConnect - Connect callback (conn, status) * @param {Function} onData - Data callback (conn, data) */ zitiDial(serviceName, isWebSocket, onConnect, onData) { this.ziti.ziti_dial(serviceName, isWebSocket, onConnect, onData); } /** * Write data asynchronously * @param {number} conn - Connection handle * @param {Buffer} data - Data to write * @param {Function} callback - Write callback (result) */ zitiWrite(conn, data, callback) { this.ziti.ziti_write(conn, data, callback); } /** * Close connection asynchronously * @param {number} conn - Connection handle */ zitiClose(conn) { this.ziti.ziti_close(conn); } } // Convenience functions for common operations class ZitiClient { constructor(identityFileOrDataOrConfig) { this.sdk = new ZitiSDK(); this.sdk.init(); // Handle configuration object if (typeof identityFileOrDataOrConfig === 'object' && !Buffer.isBuffer(identityFileOrDataOrConfig)) { const config = identityFileOrDataOrConfig; // Setup logging if provided if (config.logCallback && typeof config.logCallback === 'function') { this.sdk.setLogCallback(config.logCallback); } if (config.logFile && typeof config.logFile === 'string') { this.sdk.setLogFile(config.logFile); } if (config.enableNativeLogs !== undefined) { this.sdk.enableNativeLogs(config.enableNativeLogs); } // Load identity from config if (config.identity) { if (typeof config.identity === 'string' && (config.identity.trim().startsWith('{') || config.identity.trim().startsWith('['))) { this.sdk.loadContextFromData(config.identity); } else { this.sdk.loadContext(config.identity); } } else { throw new Error('Identity is required in configuration object'); } } else { // Handle string (file path or JSON data) const identityFileOrData = identityFileOrDataOrConfig; // Check if it looks like JSON data (starts with { or [) if (typeof identityFileOrData === 'string' && (identityFileOrData.trim().startsWith('{') || identityFileOrData.trim().startsWith('['))) { this.sdk.loadContextFromData(identityFileOrData); } else { this.sdk.loadContext(identityFileOrData); } } } /** * Connect to a service and return a client socket * @param {string} service - Service name * @param {string} terminator - Terminator (optional) * @returns {number} Socket handle */ connect(service, terminator = null) { const socket = this.sdk.socket(); const error = this.sdk.connect(socket, service, terminator); if (error !== 0) { this.sdk.close(socket); throw new Error(`Failed to connect to service: ${error}`); } return socket; } /** * Send data to a service * @param {string} service - Service name * @param {Buffer|string} data - Data to send * @param {string} terminator - Terminator (optional) * @returns {Buffer} Response data */ send(service, data, terminator = null) { const socket = this.connect(service, terminator); try { this.sdk.write(socket, data); const response = this.sdk.read(socket, 4096); return response; } finally { this.sdk.close(socket); } } /** * Close the client and shutdown SDK */ close() { this.sdk.shutdown(); } } class ZitiServer { constructor(identityFileOrDataOrConfig, service, terminator = null) { this.sdk = new ZitiSDK(); this.sdk.init(); // Handle configuration object if (typeof identityFileOrDataOrConfig === 'object' && !Buffer.isBuffer(identityFileOrDataOrConfig)) { const config = identityFileOrDataOrConfig; // Setup logging if provided if (config.logCallback && typeof config.logCallback === 'function') { this.sdk.setLogCallback(config.logCallback); } if (config.logFile && typeof config.logFile === 'string') { this.sdk.setLogFile(config.logFile); } if (config.enableNativeLogs !== undefined) { this.sdk.enableNativeLogs(config.enableNativeLogs); } // Load identity from config if (config.identity) { if (typeof config.identity === 'string' && (config.identity.trim().startsWith('{') || config.identity.trim().startsWith('['))) { this.sdk.loadContextFromData(config.identity); } else { this.sdk.loadContext(config.identity); } } else { throw new Error('Identity is required in configuration object'); } // Get service and terminator from config or parameters this.service = config.service || service; this.terminator = config.terminator || terminator; } else { // Handle string (file path or JSON data) const identityFileOrData = identityFileOrDataOrConfig; // Check if it looks like JSON data (starts with { or [) if (typeof identityFileOrData === 'string' && (identityFileOrData.trim().startsWith('{') || identityFileOrData.trim().startsWith('['))) { this.sdk.loadContextFromData(identityFileOrData); } else { this.sdk.loadContext(identityFileOrData); } this.service = service; this.terminator = terminator; } this.serverSocket = null; this.running = false; } /** * Start the server * @param {number} backlog - Maximum pending connections */ start(backlog = 10) { this.serverSocket = this.sdk.socket(); const error = this.sdk.bind(this.serverSocket, this.service, this.terminator); if (error !== 0) { throw new Error(`Failed to bind to service: ${error}`); } this.sdk.listen(this.serverSocket, backlog); this.running = true; } /** * Accept a client connection * @returns {Object} Object containing client socket and caller info */ accept() { if (!this.running) { throw new Error('Server not running. Call start() first.'); } return this.sdk.accept(this.serverSocket); } /** * Stop the server */ stop() { if (this.serverSocket) { this.sdk.close(this.serverSocket); this.serverSocket = null; } this.running = false; } /** * Close the server and shutdown SDK */ close() { this.stop(); this.sdk.shutdown(); } } // Log level constants const LOG_LEVELS = { NONE: 0, ERROR: 1, WARN: 2, INFO: 3, DEBUG: 4, VERBOSE: 5, TRACE: 6 }; // Log level labels const LOG_LEVEL_LABELS = { 0: 'NONE', 1: 'ERROR', 2: 'WARN', 3: 'INFO', 4: 'DEBUG', 5: 'VERBOSE', 6: 'TRACE' }; module.exports = { ZitiSDK, ZitiClient, ZitiServer, LOG_LEVELS, LOG_LEVEL_LABELS, // Asynchronous API (like official SDK) ziti: { ziti_init: (identityPath, callback) => { const sdk = new ZitiSDK(); sdk.init(); sdk.zitiInit(identityPath, callback); }, ziti_dial: (serviceName, isWebSocket, onConnect, onData) => { const sdk = new ZitiSDK(); sdk.init(); sdk.zitiDial(serviceName, isWebSocket, onConnect, onData); }, ziti_write: (conn, data, callback) => { const sdk = new ZitiSDK(); sdk.init(); sdk.zitiWrite(conn, data, callback); }, ziti_close: (conn) => { const sdk = new ZitiSDK(); sdk.init(); sdk.zitiClose(conn); } } };