/** * @cc-platform/server/proxy — Cross-service integration proxy * * Extracted from the reciprocal proxy endpoints in both servers: * remoteturtle: /api/integration/inventory-locate → INVENTORY_SERVER_URL * inventory-manager: /api/integration/turtle-status → TURTLE_SERVER_URL * * Both use the exact same pattern: check URL configured → fetch → forward JSON. * * Usage: * const { proxyRequest, createProxyEndpoint } = require('@cc-platform/server/proxy'); * * // One-off proxy call * const result = await proxyRequest('http://other-server:3001', '/api/items', { q: 'diamond' }); * * // Register a proxy endpoint on Express * createProxyEndpoint(app, '/api/integration/items', 'INVENTORY_SERVER_URL', '/api/integration/items'); */ 'use strict'; /** * Proxy a request to a remote service, returning JSON. * * @param {string} baseUrl - Base URL of the remote service (e.g. 'http://localhost:3001') * @param {string} remotePath - Path on the remote service (e.g. '/api/items') * @param {Object} [query={}] - Query parameters to forward * @param {Object} [options={}] - Additional options * @param {number} [options.timeoutMs=5000] - Request timeout in milliseconds * @returns {Promise} Parsed JSON response, or error/unconfigured object */ async function proxyRequest(baseUrl, remotePath, query = {}, options = {}) { if (!baseUrl) { return { configured: false, message: 'Integration not configured — remote service URL not set', }; } const timeoutMs = options.timeoutMs || 5000; try { const params = new URLSearchParams(query).toString(); const url = params ? `${baseUrl}${remotePath}?${params}` : `${baseUrl}${remotePath}`; const resp = await fetch(url, { signal: AbortSignal.timeout(timeoutMs), }); if (!resp.ok) { return { error: `Remote service returned ${resp.status}: ${resp.statusText}`, status: resp.status, }; } return await resp.json(); } catch (e) { return { error: `Integration unavailable: ${e.message}`, }; } } /** * Register a proxy endpoint on an Express app. * * Creates a GET endpoint at `localPath` that proxies requests to * `remotePath` on the service URL specified by the given environment variable. * * @param {Express.Application} app - Express app instance * @param {string} localPath - Local endpoint path (e.g. '/api/integration/items') * @param {string} remoteBaseUrlEnvVar - Env var name for the remote URL (e.g. 'INVENTORY_SERVER_URL') * @param {string} remotePath - Path on the remote service (e.g. '/api/integration/items') * @param {Object} [options={}] - proxyRequest options * @returns {string} The resolved base URL (for logging) */ function createProxyEndpoint(app, localPath, remoteBaseUrlEnvVar, remotePath, options = {}) { const baseUrl = process.env[remoteBaseUrlEnvVar] || ''; app.get(localPath, async (req, res) => { try { const result = await proxyRequest(baseUrl, remotePath, req.query, options); res.json(result); } catch (e) { res.status(500).json({ error: e.message }); } }); return baseUrl; } module.exports = { proxyRequest, createProxyEndpoint };