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,6 +24,14 @@ 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')
|
||||
@@ -38,7 +46,7 @@ function showToast(message, type = 'success') {
|
||||
|
||||
toast.innerHTML = `
|
||||
<i class="fas ${iconMap[type]}"></i>
|
||||
<span>${message}</span>
|
||||
<span>${escapeHtml(message)}</span>
|
||||
`
|
||||
|
||||
container.appendChild(toast)
|
||||
@@ -84,23 +92,24 @@ function updateAccounts(data) {
|
||||
return
|
||||
}
|
||||
|
||||
// SECURITY FIX: Escape all user-provided data to prevent XSS
|
||||
container.innerHTML = data.map(acc => `
|
||||
<div class="account-item">
|
||||
<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-email">${acc.maskedEmail}</div>
|
||||
<div class="account-email">${escapeHtml(acc.maskedEmail)}</div>
|
||||
<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 class="account-stats">
|
||||
<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>
|
||||
<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>
|
||||
`).join('')
|
||||
@@ -122,12 +131,13 @@ function renderLogs() {
|
||||
return
|
||||
}
|
||||
|
||||
// SECURITY FIX: Escape all log data to prevent XSS
|
||||
container.innerHTML = logs.map(log => `
|
||||
<div class="log-entry log-level-${log.level}">
|
||||
<span class="log-timestamp">[${new Date(log.timestamp).toLocaleTimeString()}]</span>
|
||||
<span class="log-platform platform-${log.platform}">${log.platform}</span>
|
||||
<span class="log-title">[${log.title}]</span>
|
||||
<span>${log.message}</span>
|
||||
<div class="log-entry log-level-${escapeHtml(log.level)}">
|
||||
<span class="log-timestamp">[${escapeHtml(new Date(log.timestamp).toLocaleTimeString())}]</span>
|
||||
<span class="log-platform platform-${escapeHtml(log.platform)}">${escapeHtml(log.platform)}</span>
|
||||
<span class="log-title">[${escapeHtml(log.title)}]</span>
|
||||
<span>${escapeHtml(log.message)}</span>
|
||||
</div>
|
||||
`).join('')
|
||||
|
||||
|
||||
@@ -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<void> {
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user