diff --git a/public/app.js b/public/app.js
index 9ef5972..42e6d03 100644
--- a/public/app.js
+++ b/public/app.js
@@ -24,25 +24,33 @@ if (savedTheme === 'light') {
document.querySelector('.theme-toggle i').className = 'fas fa-sun'
}
+// HTML escaping utility to prevent XSS attacks
+function escapeHtml(text) {
+ if (text === null || text === undefined) return ''
+ const div = document.createElement('div')
+ div.textContent = String(text)
+ return div.innerHTML
+}
+
// Toast notification
function showToast(message, type = 'success') {
const container = document.getElementById('toastContainer')
const toast = document.createElement('div')
toast.className = `toast toast-${type}`
-
+
const iconMap = {
success: 'fa-check-circle',
error: 'fa-exclamation-circle',
info: 'fa-info-circle'
}
-
+
toast.innerHTML = `
- ${message}
+ ${escapeHtml(message)}
`
-
+
container.appendChild(toast)
-
+
setTimeout(() => {
toast.remove()
}, 5000)
@@ -54,7 +62,7 @@ function updateStatus(data) {
const badge = document.getElementById('statusBadge')
const btnStart = document.getElementById('btnStart')
const btnStop = document.getElementById('btnStop')
-
+
if (data.running) {
badge.className = 'status-badge status-running'
badge.textContent = 'RUNNING'
@@ -78,29 +86,30 @@ function updateMetrics(data) {
function updateAccounts(data) {
accounts = data
const container = document.getElementById('accountsList')
-
+
if (data.length === 0) {
container.innerHTML = '
-
[${new Date(log.timestamp).toLocaleTimeString()}]
-
${log.platform}
-
[${log.title}]
-
${log.message}
+
+ [${escapeHtml(new Date(log.timestamp).toLocaleTimeString())}]
+ ${escapeHtml(log.platform)}
+ [${escapeHtml(log.title)}]
+ ${escapeHtml(log.message)}
`).join('')
-
+
// Auto-scroll to bottom
container.scrollTop = container.scrollHeight
}
@@ -144,7 +154,7 @@ async function fetchData() {
fetch('/api/metrics'),
fetch('/api/logs?limit=100')
])
-
+
updateStatus(await statusRes.json())
updateAccounts(await accountsRes.json())
updateMetrics(await metricsRes.json())
@@ -223,15 +233,15 @@ function refreshData() {
function connectWebSocket() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
ws = new WebSocket(`${protocol}//${window.location.host}`)
-
+
ws.onopen = () => {
console.log('WebSocket connected')
}
-
+
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data)
-
+
if (data.type === 'init') {
logs = data.data.logs || []
renderLogs()
@@ -256,12 +266,12 @@ function connectWebSocket() {
console.error('WebSocket message error:', error)
}
}
-
+
ws.onclose = () => {
console.log('WebSocket disconnected, reconnecting...')
setTimeout(connectWebSocket, 3000)
}
-
+
ws.onerror = (error) => {
console.error('WebSocket error:', error)
}
diff --git a/src/index.ts b/src/index.ts
index 5986c50..4a24fdf 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -11,7 +11,7 @@ import Humanizer from './util/browser/Humanizer'
import { formatDetailedError, normalizeRecoveryEmail, shortErrorMessage, Util } from './util/core/Utils'
import Axios from './util/network/Axios'
import { QueryDiversityEngine } from './util/network/QueryDiversityEngine'
-import { log } from './util/notifications/Logger'
+import { log, stopWebhookCleanup } from './util/notifications/Logger'
import JobState from './util/state/JobState'
import { loadAccounts, loadConfig } from './util/state/Load'
import { MobileRetryTracker } from './util/state/MobileRetryTracker'
@@ -908,18 +908,22 @@ async function main(): Promise
{
const errorMsg = reason instanceof Error ? reason.message : String(reason)
const stack = reason instanceof Error ? reason.stack : undefined
log('main', 'FATAL', `UnhandledRejection: ${errorMsg}${stack ? `\nStack: ${stack.split('\n').slice(0, 3).join(' | ')}` : ''}`, 'error')
+ stopWebhookCleanup() // CLEANUP FIX: Stop webhook cleanup interval
gracefulExit(1)
})
process.on('uncaughtException', (err: Error) => {
log('main', 'FATAL', `UncaughtException: ${err.message}${err.stack ? `\nStack: ${err.stack.split('\n').slice(0, 3).join(' | ')}` : ''}`, 'error')
+ stopWebhookCleanup() // CLEANUP FIX: Stop webhook cleanup interval
gracefulExit(1)
})
process.on('SIGTERM', () => {
log('main', 'SHUTDOWN', 'Received SIGTERM, shutting down gracefully...', 'log')
+ stopWebhookCleanup() // CLEANUP FIX: Stop webhook cleanup interval
gracefulExit(0)
})
process.on('SIGINT', () => {
log('main', 'SHUTDOWN', 'Received SIGINT (Ctrl+C), shutting down gracefully...', 'log')
+ stopWebhookCleanup() // CLEANUP FIX: Stop webhook cleanup interval
gracefulExit(0)
})
}
diff --git a/src/util/browser/SmartWait.ts b/src/util/browser/SmartWait.ts
index 6e4e493..7f7ede2 100644
--- a/src/util/browser/SmartWait.ts
+++ b/src/util/browser/SmartWait.ts
@@ -151,181 +151,5 @@ export async function waitForElementSmart(
}
}
-/**
- * Wait for navigation to complete intelligently
- * Uses URL change + DOM ready instead of fixed timeouts
- */
-export async function waitForNavigationSmart(
- page: Page,
- options: {
- expectedUrl?: string | RegExp
- maxWaitMs?: number
- logFn?: (msg: string) => void
- } = {}
-): Promise<{ completed: boolean; timeMs: number; url: string }> {
- const startTime = Date.now()
- const maxWaitMs = options.maxWaitMs ?? 15000
- const logFn = options.logFn ?? (() => { })
-
- try {
- // Wait for URL to change (if we expect it to)
- if (options.expectedUrl) {
- const urlPattern = typeof options.expectedUrl === 'string'
- ? new RegExp(options.expectedUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
- : options.expectedUrl
-
- let urlChanged = false
- const checkInterval = 100
- const maxChecks = maxWaitMs / checkInterval
-
- for (let i = 0; i < maxChecks; i++) {
- const currentUrl = page.url()
- if (urlPattern.test(currentUrl)) {
- urlChanged = true
- logFn(`✓ URL changed to expected pattern (${Date.now() - startTime}ms)`)
- break
- }
- await page.waitForTimeout(checkInterval)
- }
-
- if (!urlChanged) {
- const elapsed = Date.now() - startTime
- logFn(`⚠ URL did not match expected pattern after ${elapsed}ms`)
- return { completed: false, timeMs: elapsed, url: page.url() }
- }
- }
-
- // Wait for page to be ready after navigation
- const readyResult = await waitForPageReady(page, {
- logFn
- })
-
- const elapsed = Date.now() - startTime
- return { completed: readyResult.ready, timeMs: elapsed, url: page.url() }
-
- } catch (error) {
- const elapsed = Date.now() - startTime
- const errorMsg = error instanceof Error ? error.message : String(error)
- logFn(`✗ Navigation wait failed after ${elapsed}ms: ${errorMsg}`)
- return { completed: false, timeMs: elapsed, url: page.url() }
- }
-}
-
-/**
- * Click element with smart waiting (wait for element + click + verify action)
- */
-export async function clickElementSmart(
- page: Page,
- selector: string,
- options: {
- waitBeforeClick?: number
- waitAfterClick?: number
- verifyDisappeared?: boolean
- maxWaitMs?: number
- logFn?: (msg: string) => void
- } = {}
-): Promise<{ success: boolean; timeMs: number }> {
- const startTime = Date.now()
- const waitBeforeClick = options.waitBeforeClick ?? 100
- const waitAfterClick = options.waitAfterClick ?? 500
- const logFn = options.logFn ?? (() => { })
-
- try {
- // Wait for element to be clickable
- const elementResult = await waitForElementSmart(page, selector, {
- state: 'visible',
- initialTimeoutMs: options.maxWaitMs ? Math.floor(options.maxWaitMs * 0.4) : 2000,
- extendedTimeoutMs: options.maxWaitMs ? Math.floor(options.maxWaitMs * 0.6) : 5000,
- logFn
- })
-
- if (!elementResult.found || !elementResult.element) {
- return { success: false, timeMs: Date.now() - startTime }
- }
-
- // Small delay for stability
- if (waitBeforeClick > 0) {
- await page.waitForTimeout(waitBeforeClick)
- }
-
- // Click the element
- await elementResult.element.click()
- logFn('✓ Clicked element')
-
- // Wait for action to process
- if (waitAfterClick > 0) {
- await page.waitForTimeout(waitAfterClick)
- }
-
- // Verify element disappeared (optional)
- if (options.verifyDisappeared) {
- const disappeared = await page.locator(selector).isVisible()
- .then(() => false)
- .catch(() => true)
-
- if (disappeared) {
- logFn('✓ Element disappeared after click (expected)')
- }
- }
-
- const elapsed = Date.now() - startTime
- return { success: true, timeMs: elapsed }
-
- } catch (error) {
- const elapsed = Date.now() - startTime
- const errorMsg = error instanceof Error ? error.message : String(error)
- logFn(`✗ Click failed after ${elapsed}ms: ${errorMsg}`)
- return { success: false, timeMs: elapsed }
- }
-}
-
-/**
- * Type text into input field with smart waiting
- */
-export async function typeIntoFieldSmart(
- page: Page,
- selector: string,
- text: string,
- options: {
- clearFirst?: boolean
- delay?: number
- maxWaitMs?: number
- logFn?: (msg: string) => void
- } = {}
-): Promise<{ success: boolean; timeMs: number }> {
- const startTime = Date.now()
- const delay = options.delay ?? 20
- const logFn = options.logFn ?? (() => { })
-
- try {
- // Wait for input field
- const elementResult = await waitForElementSmart(page, selector, {
- state: 'visible',
- initialTimeoutMs: options.maxWaitMs ? Math.floor(options.maxWaitMs * 0.4) : 2000,
- extendedTimeoutMs: options.maxWaitMs ? Math.floor(options.maxWaitMs * 0.6) : 5000,
- logFn
- })
-
- if (!elementResult.found || !elementResult.element) {
- return { success: false, timeMs: Date.now() - startTime }
- }
-
- // Clear field if requested
- if (options.clearFirst) {
- await elementResult.element.clear()
- }
-
- // Type text with delay
- await elementResult.element.type(text, { delay })
- logFn('✓ Typed into field')
-
- const elapsed = Date.now() - startTime
- return { success: true, timeMs: elapsed }
-
- } catch (error) {
- const elapsed = Date.now() - startTime
- const errorMsg = error instanceof Error ? error.message : String(error)
- logFn(`✗ Type failed after ${elapsed}ms: ${errorMsg}`)
- return { success: false, timeMs: elapsed }
- }
-}
+// DEAD CODE REMOVED: waitForNavigationSmart, clickElementSmart, and typeIntoFieldSmart
+// These functions were never used in the codebase and have been removed to reduce complexity
diff --git a/src/util/core/Utils.ts b/src/util/core/Utils.ts
index 049a4f7..0d94485 100644
--- a/src/util/core/Utils.ts
+++ b/src/util/core/Utils.ts
@@ -9,23 +9,8 @@ export function getErrorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error)
}
-/**
- * Format a standardized error message for logging
- * Ensures consistent error message formatting across all modules
- *
- * @param context - Context string (e.g., 'SEARCH-BING', 'LOGIN')
- * @param error - Error object or unknown value
- * @param prefix - Optional custom prefix (defaults to 'Error')
- * @returns Formatted error message
- *
- * @example
- * formatErrorMessage('SEARCH', err) // 'Error in SEARCH: Network timeout'
- * formatErrorMessage('LOGIN', err, 'Failed') // 'Failed in LOGIN: Invalid credentials'
- */
-export function formatErrorMessage(context: string, error: unknown, prefix: string = 'Error'): string {
- const errorMsg = getErrorMessage(error)
- return `${prefix} in ${context}: ${errorMsg}`
-}
+// DEAD CODE REMOVED: formatErrorMessage() was never used (only JSDoc examples existed)
+// Use formatDetailedError() instead for error formatting with optional stack traces
/**
* Utility class for common operations