ziti-sdk-c-nodejs
Version:
Node.js wrapper for OpenZiti C SDK
539 lines (485 loc) • 17.2 kB
JavaScript
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);
}
}
};