mirror of
https://github.com/LightZirconite/Microsoft-Rewards-Bot.git
synced 2026-01-10 01:06:17 +00:00
Error correction
This commit is contained in:
@@ -24,25 +24,33 @@ if (savedTheme === 'light') {
|
|||||||
document.querySelector('.theme-toggle i').className = 'fas fa-sun'
|
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
|
// Toast notification
|
||||||
function showToast(message, type = 'success') {
|
function showToast(message, type = 'success') {
|
||||||
const container = document.getElementById('toastContainer')
|
const container = document.getElementById('toastContainer')
|
||||||
const toast = document.createElement('div')
|
const toast = document.createElement('div')
|
||||||
toast.className = `toast toast-${type}`
|
toast.className = `toast toast-${type}`
|
||||||
|
|
||||||
const iconMap = {
|
const iconMap = {
|
||||||
success: 'fa-check-circle',
|
success: 'fa-check-circle',
|
||||||
error: 'fa-exclamation-circle',
|
error: 'fa-exclamation-circle',
|
||||||
info: 'fa-info-circle'
|
info: 'fa-info-circle'
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.innerHTML = `
|
toast.innerHTML = `
|
||||||
<i class="fas ${iconMap[type]}"></i>
|
<i class="fas ${iconMap[type]}"></i>
|
||||||
<span>${message}</span>
|
<span>${escapeHtml(message)}</span>
|
||||||
`
|
`
|
||||||
|
|
||||||
container.appendChild(toast)
|
container.appendChild(toast)
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
toast.remove()
|
toast.remove()
|
||||||
}, 5000)
|
}, 5000)
|
||||||
@@ -54,7 +62,7 @@ function updateStatus(data) {
|
|||||||
const badge = document.getElementById('statusBadge')
|
const badge = document.getElementById('statusBadge')
|
||||||
const btnStart = document.getElementById('btnStart')
|
const btnStart = document.getElementById('btnStart')
|
||||||
const btnStop = document.getElementById('btnStop')
|
const btnStop = document.getElementById('btnStop')
|
||||||
|
|
||||||
if (data.running) {
|
if (data.running) {
|
||||||
badge.className = 'status-badge status-running'
|
badge.className = 'status-badge status-running'
|
||||||
badge.textContent = 'RUNNING'
|
badge.textContent = 'RUNNING'
|
||||||
@@ -78,29 +86,30 @@ function updateMetrics(data) {
|
|||||||
function updateAccounts(data) {
|
function updateAccounts(data) {
|
||||||
accounts = data
|
accounts = data
|
||||||
const container = document.getElementById('accountsList')
|
const container = document.getElementById('accountsList')
|
||||||
|
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
container.innerHTML = '<div class="empty-state"><i class="fas fa-inbox"></i><p>No accounts configured</p></div>'
|
container.innerHTML = '<div class="empty-state"><i class="fas fa-inbox"></i><p>No accounts configured</p></div>'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SECURITY FIX: Escape all user-provided data to prevent XSS
|
||||||
container.innerHTML = data.map(acc => `
|
container.innerHTML = data.map(acc => `
|
||||||
<div class="account-item">
|
<div class="account-item">
|
||||||
<div class="account-info">
|
<div class="account-info">
|
||||||
<div class="account-avatar">${acc.maskedEmail.charAt(0).toUpperCase()}</div>
|
<div class="account-avatar">${escapeHtml(acc.maskedEmail.charAt(0).toUpperCase())}</div>
|
||||||
<div class="account-details">
|
<div class="account-details">
|
||||||
<div class="account-email">${acc.maskedEmail}</div>
|
<div class="account-email">${escapeHtml(acc.maskedEmail)}</div>
|
||||||
<div class="account-status-text">
|
<div class="account-status-text">
|
||||||
${acc.lastSync ? `Last sync: ${new Date(acc.lastSync).toLocaleString()}` : 'Never synced'}
|
${acc.lastSync ? `Last sync: ${escapeHtml(new Date(acc.lastSync).toLocaleString())}` : 'Never synced'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="account-stats">
|
<div class="account-stats">
|
||||||
<div class="account-points">
|
<div class="account-points">
|
||||||
<div class="account-points-value">${acc.points !== undefined ? acc.points.toLocaleString() : 'N/A'}</div>
|
<div class="account-points-value">${acc.points !== undefined ? escapeHtml(acc.points.toLocaleString()) : 'N/A'}</div>
|
||||||
<div class="account-points-label">Points</div>
|
<div class="account-points-label">Points</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="account-badge badge-${acc.status}">${acc.status.toUpperCase()}</span>
|
<span class="account-badge badge-${escapeHtml(acc.status)}">${escapeHtml(acc.status.toUpperCase())}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`).join('')
|
`).join('')
|
||||||
@@ -116,21 +125,22 @@ function addLog(log) {
|
|||||||
|
|
||||||
function renderLogs() {
|
function renderLogs() {
|
||||||
const container = document.getElementById('logsContainer')
|
const container = document.getElementById('logsContainer')
|
||||||
|
|
||||||
if (logs.length === 0) {
|
if (logs.length === 0) {
|
||||||
container.innerHTML = '<div class="empty-state"><i class="fas fa-stream"></i><p>No logs yet...</p></div>'
|
container.innerHTML = '<div class="empty-state"><i class="fas fa-stream"></i><p>No logs yet...</p></div>'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SECURITY FIX: Escape all log data to prevent XSS
|
||||||
container.innerHTML = logs.map(log => `
|
container.innerHTML = logs.map(log => `
|
||||||
<div class="log-entry log-level-${log.level}">
|
<div class="log-entry log-level-${escapeHtml(log.level)}">
|
||||||
<span class="log-timestamp">[${new Date(log.timestamp).toLocaleTimeString()}]</span>
|
<span class="log-timestamp">[${escapeHtml(new Date(log.timestamp).toLocaleTimeString())}]</span>
|
||||||
<span class="log-platform platform-${log.platform}">${log.platform}</span>
|
<span class="log-platform platform-${escapeHtml(log.platform)}">${escapeHtml(log.platform)}</span>
|
||||||
<span class="log-title">[${log.title}]</span>
|
<span class="log-title">[${escapeHtml(log.title)}]</span>
|
||||||
<span>${log.message}</span>
|
<span>${escapeHtml(log.message)}</span>
|
||||||
</div>
|
</div>
|
||||||
`).join('')
|
`).join('')
|
||||||
|
|
||||||
// Auto-scroll to bottom
|
// Auto-scroll to bottom
|
||||||
container.scrollTop = container.scrollHeight
|
container.scrollTop = container.scrollHeight
|
||||||
}
|
}
|
||||||
@@ -144,7 +154,7 @@ async function fetchData() {
|
|||||||
fetch('/api/metrics'),
|
fetch('/api/metrics'),
|
||||||
fetch('/api/logs?limit=100')
|
fetch('/api/logs?limit=100')
|
||||||
])
|
])
|
||||||
|
|
||||||
updateStatus(await statusRes.json())
|
updateStatus(await statusRes.json())
|
||||||
updateAccounts(await accountsRes.json())
|
updateAccounts(await accountsRes.json())
|
||||||
updateMetrics(await metricsRes.json())
|
updateMetrics(await metricsRes.json())
|
||||||
@@ -223,15 +233,15 @@ function refreshData() {
|
|||||||
function connectWebSocket() {
|
function connectWebSocket() {
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
|
||||||
ws = new WebSocket(`${protocol}//${window.location.host}`)
|
ws = new WebSocket(`${protocol}//${window.location.host}`)
|
||||||
|
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
console.log('WebSocket connected')
|
console.log('WebSocket connected')
|
||||||
}
|
}
|
||||||
|
|
||||||
ws.onmessage = (event) => {
|
ws.onmessage = (event) => {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(event.data)
|
const data = JSON.parse(event.data)
|
||||||
|
|
||||||
if (data.type === 'init') {
|
if (data.type === 'init') {
|
||||||
logs = data.data.logs || []
|
logs = data.data.logs || []
|
||||||
renderLogs()
|
renderLogs()
|
||||||
@@ -256,12 +266,12 @@ function connectWebSocket() {
|
|||||||
console.error('WebSocket message error:', error)
|
console.error('WebSocket message error:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ws.onclose = () => {
|
ws.onclose = () => {
|
||||||
console.log('WebSocket disconnected, reconnecting...')
|
console.log('WebSocket disconnected, reconnecting...')
|
||||||
setTimeout(connectWebSocket, 3000)
|
setTimeout(connectWebSocket, 3000)
|
||||||
}
|
}
|
||||||
|
|
||||||
ws.onerror = (error) => {
|
ws.onerror = (error) => {
|
||||||
console.error('WebSocket error:', error)
|
console.error('WebSocket error:', error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import Humanizer from './util/browser/Humanizer'
|
|||||||
import { formatDetailedError, normalizeRecoveryEmail, shortErrorMessage, Util } from './util/core/Utils'
|
import { formatDetailedError, normalizeRecoveryEmail, shortErrorMessage, Util } from './util/core/Utils'
|
||||||
import Axios from './util/network/Axios'
|
import Axios from './util/network/Axios'
|
||||||
import { QueryDiversityEngine } from './util/network/QueryDiversityEngine'
|
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 JobState from './util/state/JobState'
|
||||||
import { loadAccounts, loadConfig } from './util/state/Load'
|
import { loadAccounts, loadConfig } from './util/state/Load'
|
||||||
import { MobileRetryTracker } from './util/state/MobileRetryTracker'
|
import { MobileRetryTracker } from './util/state/MobileRetryTracker'
|
||||||
@@ -908,18 +908,22 @@ async function main(): Promise<void> {
|
|||||||
const errorMsg = reason instanceof Error ? reason.message : String(reason)
|
const errorMsg = reason instanceof Error ? reason.message : String(reason)
|
||||||
const stack = reason instanceof Error ? reason.stack : undefined
|
const stack = reason instanceof Error ? reason.stack : undefined
|
||||||
log('main', 'FATAL', `UnhandledRejection: ${errorMsg}${stack ? `\nStack: ${stack.split('\n').slice(0, 3).join(' | ')}` : ''}`, 'error')
|
log('main', 'FATAL', `UnhandledRejection: ${errorMsg}${stack ? `\nStack: ${stack.split('\n').slice(0, 3).join(' | ')}` : ''}`, 'error')
|
||||||
|
stopWebhookCleanup() // CLEANUP FIX: Stop webhook cleanup interval
|
||||||
gracefulExit(1)
|
gracefulExit(1)
|
||||||
})
|
})
|
||||||
process.on('uncaughtException', (err: Error) => {
|
process.on('uncaughtException', (err: Error) => {
|
||||||
log('main', 'FATAL', `UncaughtException: ${err.message}${err.stack ? `\nStack: ${err.stack.split('\n').slice(0, 3).join(' | ')}` : ''}`, '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)
|
gracefulExit(1)
|
||||||
})
|
})
|
||||||
process.on('SIGTERM', () => {
|
process.on('SIGTERM', () => {
|
||||||
log('main', 'SHUTDOWN', 'Received SIGTERM, shutting down gracefully...', 'log')
|
log('main', 'SHUTDOWN', 'Received SIGTERM, shutting down gracefully...', 'log')
|
||||||
|
stopWebhookCleanup() // CLEANUP FIX: Stop webhook cleanup interval
|
||||||
gracefulExit(0)
|
gracefulExit(0)
|
||||||
})
|
})
|
||||||
process.on('SIGINT', () => {
|
process.on('SIGINT', () => {
|
||||||
log('main', 'SHUTDOWN', 'Received SIGINT (Ctrl+C), shutting down gracefully...', 'log')
|
log('main', 'SHUTDOWN', 'Received SIGINT (Ctrl+C), shutting down gracefully...', 'log')
|
||||||
|
stopWebhookCleanup() // CLEANUP FIX: Stop webhook cleanup interval
|
||||||
gracefulExit(0)
|
gracefulExit(0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,181 +151,5 @@ export async function waitForElementSmart(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// DEAD CODE REMOVED: waitForNavigationSmart, clickElementSmart, and typeIntoFieldSmart
|
||||||
* Wait for navigation to complete intelligently
|
// These functions were never used in the codebase and have been removed to reduce complexity
|
||||||
* 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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,23 +9,8 @@ export function getErrorMessage(error: unknown): string {
|
|||||||
return error instanceof Error ? error.message : String(error)
|
return error instanceof Error ? error.message : String(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// DEAD CODE REMOVED: formatErrorMessage() was never used (only JSDoc examples existed)
|
||||||
* Format a standardized error message for logging
|
// Use formatDetailedError() instead for error formatting with optional stack traces
|
||||||
* 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}`
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class for common operations
|
* Utility class for common operations
|
||||||
|
|||||||
Reference in New Issue
Block a user