const { logger } = require('./logger.js'); const API = require('./index.js'); const path = require('path'); const fs = require('fs'); require('./utils.js'); // Initialize key replacements let keyReplacements = {}; try { const replacementsPath = path.join( __dirname, '..', 'data', 'replacements.json' ); if (fs.existsSync(replacementsPath)) { const replacementsContent = fs.readFileSync(replacementsPath, 'utf8'); keyReplacements = JSON.parse(replacementsContent); // logger.debug("Replacements loaded successfully"); } else { logger.warn('replacements.json not found, key replacement disabled'); } } catch (error) { logger.error('Error loading replacements file:', { error: error.message }); } // Optimized replaceJsonKeys function const replaceJsonKeys = (obj, replacements) => { // Handle non-objects early if (!obj || typeof obj !== 'object') return obj; // Fast path for arrays if (Array.isArray(obj)) { return obj.length === 0 ? obj : obj.map((item) => replaceJsonKeys(item, replacements)); } // Fast path for empty objects const keys = Object.keys(obj); if (keys.length === 0) return obj; // Process normal objects const newObj = {}; for (let i = 0; i < keys.length; i++) { const key = keys[i]; const newKey = replacements[key] || key; let value = obj[key]; // Replace string values if they match a key in replacements if (typeof value === 'string' && replacements[value]) { value = replacements[value]; } else if (value && typeof value === 'object') { // Only recurse for objects/arrays value = replaceJsonKeys(value, replacements); } newObj[newKey] = value; } return newObj; }; // Utility regex function const sanitizeJsonOutput = (data) => { if (!data) return data; // Convert to string to perform regex operations const jsonString = JSON.stringify(data); // Define regex pattern that matches HTML entities const regexPattern = /<span class=".*?">|<\/span>|">/g; // Replace unwanted patterns const sanitizedString = jsonString.replace(regexPattern, ''); // Parse back to object try { return JSON.parse(sanitizedString); } catch (e) { logger.error('Error parsing sanitized JSON:', e); return data; // Return original data if parsing fails } }; // Replace the processJsonOutput function with this more efficient version const processJsonOutput = ( data, options = { sanitize: true, replaceKeys: true, keyReplacements: {}, } ) => { // Early return for null or non-objects if (data === null || typeof data !== 'object') { return data; } // Copy data first to avoid reference issues let processedData = deepClone(data); // Apply sanitization if needed if (options.sanitize) { processedData = sanitizeJsonOutput(processedData); } // Apply key replacement if needed - pass replacements directly if ( options.replaceKeys && Object.keys(options.keyReplacements || {}).length > 0 ) { processedData = replaceJsonKeys(processedData, options.keyReplacements); } return processedData; }; /** * Optimized deep clone function * @param {any} obj - The object to clone * @returns {any} - The cloned object */ const deepClone = (obj) => { if (obj === null || typeof obj !== 'object') { return obj; } // Fast path for arrays if (Array.isArray(obj)) { const length = obj.length; const clone = new Array(length); for (let i = 0; i < length; i++) { clone[i] = deepClone(obj[i]); } return clone; } // Fast path for objects const clone = {}; const keys = Object.keys(obj); for (let i = 0; i < keys.length; i++) { const key = keys[i]; clone[key] = deepClone(obj[key]); } return clone; }; // Store active sessions to avoid repeated logins const activeSessions = new Map(); // Utility function to create a timeout promise const timeoutPromise = (ms) => { return new Promise((_, reject) => { setTimeout( () => reject(new ApiTimeoutError(`Request timed out after ${ms}ms`)), ms ); }); }; // Helper function to ensure login const ensureLogin = async (ssoToken) => { if (!activeSessions.has(ssoToken)) { logger.info( `Attempting to login with SSO token: ${ssoToken.substring(0, 5)}...` ); // logger.info(`Attempting to login with SSO token: ${ssoToken}`); const loginResult = await Promise.race([ API.login(ssoToken), timeoutPromise(10000), // 10 second timeout ]); logger.debug(`Login successful: ${JSON.stringify(loginResult)}`); logger.debug(`Session created at: ${global.Utils.toIsoString(new Date())}`); activeSessions.set(ssoToken, new Date()); } else { logger.debug('Using existing session'); } }; // Create custom error classes class ApiTimeoutError extends Error { constructor(message = 'API request timed out') { super(message); this.name = 'ApiTimeoutError'; } } class ApiAuthError extends Error { constructor(message = 'Authentication failed') { super(message); this.name = 'ApiAuthError'; } } class ApiDataError extends Error { constructor(message = 'Data processing error') { super(message); this.name = 'ApiDataError'; } } // Helper function to handle API errors const handleApiError = (error, res) => { logger.error('API Error:', error); logger.error(`Error Stack: ${error.stack}`); logger.error(`Error Time: ${global.Utils.toIsoString(new Date())}`); // Map error types to appropriate responses const errorResponses = { ApiTimeoutError: { status: 200, body: { status: 'error', message: 'The request timed out. Please try again.', error_type: 'timeout', timestamp: global.Utils.toIsoString(new Date()), }, }, ApiAuthError: { status: 200, body: { status: 'error', message: 'Authentication failed. Please check your SSO token.', error_type: 'auth_failure', timestamp: global.Utils.toIsoString(new Date()), }, }, SyntaxError: { status: 200, body: { status: 'error', message: 'Failed to parse API response. This usually means the SSO token is invalid or expired.', error_type: 'InvalidResponseError', timestamp: global.Utils.toIsoString(new Date()), }, }, default: { status: 200, body: { status: 'error', message: error.message || 'An unknown error occurred', error_type: error.name || 'UnknownError', timestamp: global.Utils.toIsoString(new Date()), }, }, }; // Get the appropriate response or use default const response = errorResponses[error.name] || errorResponses.default; return res.status(response.status).json(response.body); }; // Helper function to remove sensitive data from headers function sanitizeHeaders(headers) { const safeHeaders = { ...headers }; // Remove potential sensitive information const sensitiveHeaders = ['authorization', 'cookie', 'set-cookie']; sensitiveHeaders.forEach((header) => { if (safeHeaders[header]) { safeHeaders[header] = '[REDACTED]'; } }); return safeHeaders; } module.exports = { activeSessions, timeoutPromise, ensureLogin, handleApiError, sanitizeHeaders, processJsonOutput, };