chore: separate utility code
This commit is contained in:
parent
a5771c3499
commit
b6921dcbde
198
app.js
198
app.js
@ -9,6 +9,14 @@ const favicon = require('serve-favicon');
|
||||
const fs = require('fs');
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3512;
|
||||
const {
|
||||
processJsonOutput,
|
||||
sanitizeHeaders,
|
||||
timeoutPromise,
|
||||
ensureLogin,
|
||||
handleApiError,
|
||||
activeSessions
|
||||
} = require('./src/js/serverUtils.js');
|
||||
require('./src/js/utils.js');
|
||||
|
||||
app.set('trust proxy', true);
|
||||
@ -29,196 +37,6 @@ app.use(
|
||||
);
|
||||
app.use(demoTracker.demoModeMiddleware);
|
||||
|
||||
// Initialize key replacements
|
||||
let keyReplacements = {};
|
||||
|
||||
try {
|
||||
const replacementsPath = path.join(
|
||||
__dirname,
|
||||
'src',
|
||||
'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 });
|
||||
}
|
||||
|
||||
const replaceJsonKeys = (obj) => {
|
||||
if (!obj || typeof obj !== 'object') return obj;
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item) => replaceJsonKeys(item));
|
||||
}
|
||||
|
||||
const newObj = {};
|
||||
Object.keys(obj).forEach((key) => {
|
||||
// Replace key if it exists in replacements
|
||||
const newKey = keyReplacements[key] || key;
|
||||
|
||||
// DEBUG: Log replacements when they happen
|
||||
// if (newKey !== key) {
|
||||
// logger.debug(`Replacing key "${key}" with "${newKey}"`);
|
||||
// }
|
||||
|
||||
// Also check if the value should be replaced (if it's a string)
|
||||
let value = obj[key];
|
||||
if (typeof value === 'string' && keyReplacements[value]) {
|
||||
value = keyReplacements[value];
|
||||
// logger.debug(`Replacing value "${obj[key]}" with "${value}"`);
|
||||
}
|
||||
|
||||
// Process value recursively if it's an object or array
|
||||
newObj[newKey] = replaceJsonKeys(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 }
|
||||
) => {
|
||||
// Use a more efficient deep clone approach instead of JSON.parse(JSON.stringify())
|
||||
function deepClone(obj) {
|
||||
if (obj === null || typeof obj !== 'object') {
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item) => deepClone(item));
|
||||
}
|
||||
|
||||
const clone = {};
|
||||
Object.keys(obj).forEach((key) => {
|
||||
clone[key] = deepClone(obj[key]);
|
||||
});
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
// Create a deep copy of the data to avoid reference issues
|
||||
let processedData = deepClone(data);
|
||||
|
||||
// Apply sanitization if needed
|
||||
if (options.sanitize) {
|
||||
processedData = sanitizeJsonOutput(processedData);
|
||||
}
|
||||
|
||||
// Apply key replacement if needed
|
||||
if (options.replaceKeys) {
|
||||
processedData = replaceJsonKeys(processedData);
|
||||
}
|
||||
|
||||
return processedData;
|
||||
};
|
||||
|
||||
// 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 Error(`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');
|
||||
}
|
||||
};
|
||||
|
||||
// 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())}`);
|
||||
|
||||
// Try to extract more useful information from the error
|
||||
let errorMessage = error.message || 'Unknown API error';
|
||||
let errorName = error.name || 'ApiError';
|
||||
|
||||
// Handle the specific JSON parsing error
|
||||
if (errorName === 'SyntaxError' && errorMessage.includes('JSON')) {
|
||||
logger.debug('JSON parsing error detected');
|
||||
return res.status(200).json({
|
||||
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()),
|
||||
});
|
||||
}
|
||||
|
||||
// Send a more graceful response
|
||||
return res.status(200).json({
|
||||
status: 'error',
|
||||
message: errorMessage,
|
||||
error_type: errorName,
|
||||
timestamp: global.Utils.toIsoString(new Date()),
|
||||
});
|
||||
};
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Set up demo mode cleanup interval
|
||||
setInterval(
|
||||
() => demoTracker.demoModeRequestTracker.cleanupExpiredEntries(),
|
||||
|
204
src/js/serverUtils.js
Normal file
204
src/js/serverUtils.js
Normal file
@ -0,0 +1,204 @@
|
||||
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 });
|
||||
}
|
||||
|
||||
const replaceJsonKeys = (obj) => {
|
||||
if (!obj || typeof obj !== 'object') return obj;
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item) => replaceJsonKeys(item));
|
||||
}
|
||||
|
||||
const newObj = {};
|
||||
Object.keys(obj).forEach((key) => {
|
||||
// Replace key if it exists in replacements
|
||||
const newKey = keyReplacements[key] || key;
|
||||
|
||||
// DEBUG: Log replacements when they happen
|
||||
// if (newKey !== key) {
|
||||
// logger.debug(`Replacing key "${key}" with "${newKey}"`);
|
||||
// }
|
||||
|
||||
// Also check if the value should be replaced (if it's a string)
|
||||
let value = obj[key];
|
||||
if (typeof value === 'string' && keyReplacements[value]) {
|
||||
value = keyReplacements[value];
|
||||
// logger.debug(`Replacing value "${obj[key]}" with "${value}"`);
|
||||
}
|
||||
|
||||
// Process value recursively if it's an object or array
|
||||
newObj[newKey] = replaceJsonKeys(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 }
|
||||
) => {
|
||||
// Use a more efficient deep clone approach instead of JSON.parse(JSON.stringify())
|
||||
function deepClone(obj) {
|
||||
if (obj === null || typeof obj !== 'object') {
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item) => deepClone(item));
|
||||
}
|
||||
|
||||
const clone = {};
|
||||
Object.keys(obj).forEach((key) => {
|
||||
clone[key] = deepClone(obj[key]);
|
||||
});
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
// Create a deep copy of the data to avoid reference issues
|
||||
let processedData = deepClone(data);
|
||||
|
||||
// Apply sanitization if needed
|
||||
if (options.sanitize) {
|
||||
processedData = sanitizeJsonOutput(processedData);
|
||||
}
|
||||
|
||||
// Apply key replacement if needed
|
||||
if (options.replaceKeys) {
|
||||
processedData = replaceJsonKeys(processedData);
|
||||
}
|
||||
|
||||
return processedData;
|
||||
};
|
||||
|
||||
// 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 Error(`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');
|
||||
}
|
||||
};
|
||||
|
||||
// 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())}`);
|
||||
|
||||
// Try to extract more useful information from the error
|
||||
let errorMessage = error.message || 'Unknown API error';
|
||||
let errorName = error.name || 'ApiError';
|
||||
|
||||
// Handle the specific JSON parsing error
|
||||
if (errorName === 'SyntaxError' && errorMessage.includes('JSON')) {
|
||||
logger.debug('JSON parsing error detected');
|
||||
return res.status(200).json({
|
||||
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()),
|
||||
});
|
||||
}
|
||||
|
||||
// Send a more graceful response
|
||||
return res.status(200).json({
|
||||
status: 'error',
|
||||
message: errorMessage,
|
||||
error_type: errorName,
|
||||
timestamp: global.Utils.toIsoString(new Date()),
|
||||
});
|
||||
};
|
||||
|
||||
// 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
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user