UNPKG

server-function-bridge

Version:

Run server-side Node.js functions directly from client-side JavaScript using WebSockets

256 lines (238 loc) 8.11 kB
function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } /** * Client-side module for Server Function Bridge * Provides utilities for calling server-side functions from client JavaScript */ // Choose WebSocket implementation based on environment // Use global WebSocket when available (browser or jsdom mock) const WebSocketImpl = typeof WebSocket !== 'undefined' ? WebSocket : null; let serverSocket = null; let connectionPromise = null; let config = { wsPath: '/ws', timeout: 10000, debug: false, reconnect: true, url: null // Will be auto-detected if not provided }; /** * Configure the client-side settings * @param {Object} options - Configuration options * @param {string} [options.url] - Custom WebSocket URL * @param {string} [options.wsPath] - WebSocket path * @param {number} [options.timeout] - Request timeout in milliseconds * @param {boolean} [options.debug] - Enable verbose logging * @param {boolean} [options.reconnect] - Auto-reconnect on disconnect */ function configure() { let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; config = { ...config, ...options }; // Reset connection if config changes if (serverSocket) { serverSocket.close(); serverSocket = null; connectionPromise = null; } if (config.debug) { console.log('Server Function Bridge configured:', config); } } /** * Get the WebSocket connection, creating it if it doesn't exist * @returns {Promise<WebSocket>} - WebSocket connection */ function getConnection() { if (!connectionPromise) { connectionPromise = initializeConnection(); } return connectionPromise; } /** * Close the WebSocket connection if it exists */ function closeConnection() { if (serverSocket && (serverSocket.readyState === WebSocketImpl.OPEN || serverSocket.readyState === WebSocketImpl.CONNECTING)) { if (config.debug) { console.log('Closing WebSocket connection'); } serverSocket.close(); } serverSocket = null; connectionPromise = null; } /** * Create a function that calls a server-side method * @param {string} methodName - Name of the server-side method to call * @param {Object} [options] - Options for this specific server call * @returns {Function} - Function that calls the server method */ function serverCall(methodName) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; const callOptions = { ...config, ...options }; // Return a function that wraps the server call return function () { for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } // Return a promise that resolves with the server call result return getConnection().then(socket => { return makeServerCall(socket, methodName, args, callOptions); }).catch(error => { if (callOptions.debug) { console.error(`Error in serverCall(${methodName}):`, error); } throw error; }); }; } /** * Initialize the WebSocket connection * @private * @returns {Promise<WebSocket>} - WebSocket connection */ function initializeConnection() { return new Promise((resolve, reject) => { try { // Check if WebSocket is available if (!WebSocketImpl) { return reject(new Error('WebSocket is not available in this environment')); } // Build the WebSocket URL if not provided let wsUrl = config.url; if (!wsUrl) { const wsProtocol = typeof window !== 'undefined' && window.location && window.location.protocol === 'https:' ? 'wss://' : 'ws://'; const wsHost = typeof window !== 'undefined' && window.location ? window.location.host : 'localhost:8080'; wsUrl = wsProtocol + wsHost + config.wsPath; } if (config.debug) { console.log(`Establishing WebSocket connection to: ${wsUrl}`); } serverSocket = new WebSocketImpl(wsUrl); serverSocket.addEventListener('open', () => { if (config.debug) { console.log('WebSocket connection established'); } resolve(serverSocket); }); serverSocket.addEventListener('error', event => { const error = new Error('WebSocket connection error'); if (config.debug) { console.error('WebSocket connection error:', event); } reject(error); }); serverSocket.addEventListener('close', event => { if (config.debug) { console.log('WebSocket connection closed', event.code, event.reason); } // Reset variables serverSocket = null; connectionPromise = null; // Attempt to reconnect if configured if (config.reconnect) { if (config.debug) { console.log('Attempting to reconnect in 3 seconds...'); } setTimeout(() => { connectionPromise = initializeConnection(); }, 3000); } }); } catch (error) { if (config.debug) { console.error('Error initializing WebSocket connection:', error); } reject(error); } }); } /** * Make a server call over WebSocket * @private * @param {WebSocket} socket - WebSocket connection * @param {string} methodName - Name of server method to call * @param {Array} args - Arguments to pass to the server method * @param {Object} options - Options for this call * @returns {Promise<any>} - Promise resolving with the server response */ function makeServerCall(socket, methodName, args, options) { return new Promise((resolve, reject) => { try { // Check if socket is connected if (socket.readyState !== WebSocketImpl.OPEN) { if (options.debug) { console.log(`Socket not ready, current state: ${socket.readyState}`); } // If socket is connecting, wait for it if (socket.readyState === WebSocketImpl.CONNECTING) { socket.addEventListener('open', () => { // Retry the call once connected makeServerCall(socket, methodName, args, options).then(resolve).catch(reject); }, { once: true }); return; } return reject(new Error('WebSocket is not connected')); } const requestId = Date.now().toString() + Math.random().toString().slice(2); if (options.debug) { console.log(`Sending ${methodName} request with ID: ${requestId}`); } socket.send(JSON.stringify({ requestId: requestId, methodName: methodName, args: args })); const messageHandler = function (event) { try { const response = JSON.parse(event.data); if (response.requestId === requestId) { socket.removeEventListener('message', messageHandler); if (response.error) { if (options.debug) { console.error('Error in response:', response.error); } reject(new Error(response.error)); } else { if (options.debug) { console.log('Success response:', response.result); } resolve(response.result); } } } catch (error) { if (options.debug) { console.error('Error handling WebSocket message:', error); } } }; socket.addEventListener('message', messageHandler); // Add timeout to prevent hanging promises const timeoutId = setTimeout(() => { socket.removeEventListener('message', messageHandler); reject(new Error(`Request ${methodName} timed out after ${options.timeout}ms`)); }, options.timeout); } catch (error) { reject(error); } }); } // Export all functions var client = { serverCall, configure, getConnection, closeConnection }; var index = /*@__PURE__*/getDefaultExportFromCjs(client); export { index as default }; //# sourceMappingURL=index.js.map