server-function-bridge
Version:
Run server-side Node.js functions directly from client-side JavaScript using WebSockets
256 lines (238 loc) • 8.11 kB
JavaScript
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