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 fs = require('fs');
|
||||||
const app = express();
|
const app = express();
|
||||||
const port = process.env.PORT || 3512;
|
const port = process.env.PORT || 3512;
|
||||||
|
const {
|
||||||
|
processJsonOutput,
|
||||||
|
sanitizeHeaders,
|
||||||
|
timeoutPromise,
|
||||||
|
ensureLogin,
|
||||||
|
handleApiError,
|
||||||
|
activeSessions
|
||||||
|
} = require('./src/js/serverUtils.js');
|
||||||
require('./src/js/utils.js');
|
require('./src/js/utils.js');
|
||||||
|
|
||||||
app.set('trust proxy', true);
|
app.set('trust proxy', true);
|
||||||
@ -29,196 +37,6 @@ app.use(
|
|||||||
);
|
);
|
||||||
app.use(demoTracker.demoModeMiddleware);
|
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
|
// Set up demo mode cleanup interval
|
||||||
setInterval(
|
setInterval(
|
||||||
() => demoTracker.demoModeRequestTracker.cleanupExpiredEntries(),
|
() => 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