Your Name
feat: UI improvements and error suppression - Enhanced dashboard and market pages with improved header buttons, logo, and currency symbol display - Stopped animated ticker - Removed pie chart legends - Added error suppressor for external service errors (SSE, Permissions-Policy warnings) - Improved header button prominence and icon appearance - Enhanced logo with glow effects and better design - Fixed currency symbol visibility in market tables
8b7b267
| /** | |
| * HTML Sanitization Utility | |
| * Prevents XSS attacks by escaping HTML special characters | |
| */ | |
| /** | |
| * Escape HTML special characters to prevent XSS | |
| * @param {string|number} text - Text to escape | |
| * @param {boolean} forAttribute - If true, also escapes quotes for HTML attributes | |
| * @returns {string} Escaped HTML string | |
| */ | |
| export function escapeHtml(text, forAttribute = false) { | |
| if (text === null || text === undefined) { | |
| return ''; | |
| } | |
| const str = String(text); | |
| const map = { | |
| '&': '&', | |
| '<': '<', | |
| '>': '>', | |
| '"': '"', | |
| "'": ''' | |
| }; | |
| let escaped = str.replace(/[&<>"']/g, m => map[m]); | |
| // For attributes, ensure quotes are properly escaped | |
| if (forAttribute) { | |
| escaped = escaped.replace(/"/g, '"').replace(/'/g, '''); | |
| } | |
| return escaped; | |
| } | |
| /** | |
| * Safely set innerHTML with sanitization | |
| * @param {HTMLElement} element - DOM element to update | |
| * @param {string} html - HTML string (will be sanitized) | |
| */ | |
| export function safeSetInnerHTML(element, html) { | |
| if (!element || !(element instanceof HTMLElement)) { | |
| console.warn('[Sanitizer] Invalid element provided to safeSetInnerHTML'); | |
| return; | |
| } | |
| // For simple text content, use textContent instead | |
| if (!html.includes('<') && !html.includes('>')) { | |
| element.textContent = html; | |
| return; | |
| } | |
| // For HTML content, create a temporary container and sanitize | |
| const temp = document.createElement('div'); | |
| temp.innerHTML = html; | |
| // Sanitize all text nodes | |
| const walker = document.createTreeWalker( | |
| temp, | |
| NodeFilter.SHOW_TEXT, | |
| null, | |
| false | |
| ); | |
| let node; | |
| while (node = walker.nextNode()) { | |
| if (node.textContent) { | |
| node.textContent = node.textContent; // Already safe, but ensure it's set | |
| } | |
| } | |
| // Clear and append sanitized content | |
| element.innerHTML = ''; | |
| while (temp.firstChild) { | |
| element.appendChild(temp.firstChild); | |
| } | |
| } | |
| /** | |
| * Sanitize object values for HTML rendering | |
| * Recursively escapes string values in objects | |
| * @param {any} obj - Object to sanitize | |
| * @param {number} depth - Recursion depth limit | |
| * @returns {any} Sanitized object | |
| */ | |
| export function sanitizeObject(obj, depth = 5) { | |
| if (depth <= 0) { | |
| return '[Max Depth Reached]'; | |
| } | |
| if (obj === null || obj === undefined) { | |
| return ''; | |
| } | |
| if (typeof obj === 'string') { | |
| return escapeHtml(obj); | |
| } | |
| if (typeof obj === 'number' || typeof obj === 'boolean') { | |
| return obj; | |
| } | |
| if (Array.isArray(obj)) { | |
| return obj.map(item => sanitizeObject(item, depth - 1)); | |
| } | |
| if (typeof obj === 'object') { | |
| const sanitized = {}; | |
| for (const key in obj) { | |
| if (Object.prototype.hasOwnProperty.call(obj, key)) { | |
| sanitized[key] = sanitizeObject(obj[key], depth - 1); | |
| } | |
| } | |
| return sanitized; | |
| } | |
| return String(obj); | |
| } | |
| /** | |
| * Format number safely for display | |
| * @param {number} value - Number to format | |
| * @param {object} options - Formatting options | |
| * @returns {string} Formatted number | |
| */ | |
| export function safeFormatNumber(value, options = {}) { | |
| if (value === null || value === undefined || isNaN(value)) { | |
| return '—'; | |
| } | |
| const num = Number(value); | |
| if (isNaN(num)) { | |
| return '—'; | |
| } | |
| try { | |
| return num.toLocaleString('en-US', { | |
| minimumFractionDigits: options.minimumFractionDigits || 2, | |
| maximumFractionDigits: options.maximumFractionDigits || 2, | |
| ...options | |
| }); | |
| } catch (error) { | |
| console.warn('[Sanitizer] Number formatting error:', error); | |
| return String(num); | |
| } | |
| } | |
| /** | |
| * Safely format currency | |
| * @param {number} value - Currency value | |
| * @param {string} currency - Currency code (default: USD) | |
| * @returns {string} Formatted currency string | |
| */ | |
| export function safeFormatCurrency(value, currency = 'USD') { | |
| if (value === null || value === undefined || isNaN(value)) { | |
| return '—'; | |
| } | |
| const num = Number(value); | |
| if (isNaN(num)) { | |
| return '—'; | |
| } | |
| try { | |
| return new Intl.NumberFormat('en-US', { | |
| style: 'currency', | |
| currency: currency, | |
| minimumFractionDigits: 2, | |
| maximumFractionDigits: 2 | |
| }).format(num); | |
| } catch (error) { | |
| console.warn('[Sanitizer] Currency formatting error:', error); | |
| return `$${num.toFixed(2)}`; | |
| } | |
| } | |