diff --git a/src/functions/Login.ts b/src/functions/Login.ts index 4504607..c126577 100644 --- a/src/functions/Login.ts +++ b/src/functions/Login.ts @@ -228,6 +228,10 @@ export class Login { await this.disableFido(page) + // CRITICAL: Setup dialog handlers BEFORE any login interactions + // This prevents native browser dialogs (Bluetooth, Windows Hello, Passkey) from blocking automation + this.passkeyHandler.setupDialogHandlers(page) + const [reloadResult, totpResult, portalCheck] = await Promise.allSettled([ this.bot.browser.utils.reloadBadPage(page), this.totpHandler.tryAutoTotp(page, 'initial landing'), diff --git a/src/functions/login/PasskeyHandler.ts b/src/functions/login/PasskeyHandler.ts index 3423185..ef92ec2 100644 --- a/src/functions/login/PasskeyHandler.ts +++ b/src/functions/login/PasskeyHandler.ts @@ -31,6 +31,57 @@ export class PasskeyHandler { }).catch(logError('LOGIN-FIDO', 'Route interception setup failed', this.bot.isMobile)) } + /** + * Setup dialog handlers to automatically dismiss native browser dialogs + * CRITICAL: This handles Bluetooth/Windows Hello/Passkey prompts that appear as native browser dialogs + * These are NOT DOM elements and cannot be clicked - they must be dismissed via page.on('dialog') + */ + public setupDialogHandlers(page: Page) { + // Remove any existing listeners to prevent duplicates + page.removeAllListeners('dialog') + + page.on('dialog', async (dialog) => { + const message = dialog.message() + const type = dialog.type() + + this.bot.log( + this.bot.isMobile, + 'LOGIN-DIALOG', + `Native browser dialog detected: [${type}] "${message.substring(0, 100)}"`, + 'warn' + ) + + // Auto-dismiss all dialogs (alert, confirm, prompt, beforeunload) + // For passkey/Bluetooth prompts, we want to DISMISS (equivalent to Cancel) + try { + if (type === 'beforeunload') { + // Accept beforeunload to allow navigation + await dialog.accept() + this.bot.log(this.bot.isMobile, 'LOGIN-DIALOG', 'Accepted beforeunload dialog', 'log', 'green') + } else { + // Dismiss all other dialogs (passkey, Bluetooth, alerts) + await dialog.dismiss() + this.bot.log( + this.bot.isMobile, + 'LOGIN-DIALOG', + `Dismissed ${type} dialog: "${message.substring(0, 50)}"`, + 'log', + 'green' + ) + } + } catch (error) { + this.bot.log( + this.bot.isMobile, + 'LOGIN-DIALOG', + `Failed to handle dialog: ${error instanceof Error ? error.message : String(error)}`, + 'error' + ) + } + }) + + this.bot.log(this.bot.isMobile, 'LOGIN-DIALOG', 'Dialog handlers installed (auto-dismiss enabled)', 'log', 'cyan') + } + public async handlePasskeyPrompts(page: Page, context: 'main' | 'oauth') { let did = false diff --git a/src/util/notifications/ErrorReportingWebhook.ts b/src/util/notifications/ErrorReportingWebhook.ts index 7056a49..c3cd9db 100644 --- a/src/util/notifications/ErrorReportingWebhook.ts +++ b/src/util/notifications/ErrorReportingWebhook.ts @@ -41,14 +41,15 @@ function sanitizeSensitiveText(text: string): string { /** * Build the Discord payload from error and context (sanitizes content) + * Returns null if error should be filtered (prevents sending) */ -function buildDiscordPayload(config: Config, error: Error | string, additionalContext?: Record) { +function buildDiscordPayload(config: Config, error: Error | string, additionalContext?: Record): { username: string; avatar_url?: string; embeds: DiscordEmbed[] } | null { const errorMessage = error instanceof Error ? error.message : String(error) const sanitizedForLogging = sanitizeSensitiveText(errorMessage) if (!shouldReportError(errorMessage)) { process.stderr.write(`[ErrorReporting] Filtered error (expected/benign): ${sanitizedForLogging.substring(0, 100)}\n`) - return { username: 'Microsoft-Rewards-Bot Error Reporter', content: 'Filtered error (not reported)' } + return null // FIXED: Return null instead of sending dummy message } const errorStack = error instanceof Error ? error.stack : undefined @@ -434,7 +435,15 @@ export async function sendErrorReport( process.stderr.write(`[ErrorReporting] Trying webhook: ${webhookUrl}\n`) try { - const response = await axios.post(webhookUrl, buildDiscordPayload(config, error, additionalContext), { + // FIXED: Check if payload is null (filtered error) + const payload = buildDiscordPayload(config, error, additionalContext) + if (!payload) { + process.stderr.write('[ErrorReporting] Skipping webhook send (error was filtered)\n') + sent = true // Mark as "sent" to prevent fallback error message + break + } + + const response = await axios.post(webhookUrl, payload, { headers: { 'Content-Type': 'application/json' }, timeout: 10000 })