import type { Page } from 'patchright' import type { MicrosoftRewardsBot } from '../../../index' import { getErrorMessage, getSubtitleMessage, promptInput } from './LoginUtils' export class CodeLogin { private readonly textInputSelector = '[data-testid="codeInputWrapper"]' private readonly secondairyInputSelector = 'input[id="otc-confirmation-input"], input[name="otc"]' private readonly maxManualSeconds = 60 private readonly maxManualAttempts = 5 constructor(private bot: MicrosoftRewardsBot) {} private async fillCode(page: Page, code: string): Promise { try { const visibleInput = await page .waitForSelector(this.textInputSelector, { state: 'visible', timeout: 500 }) .catch(() => null) if (visibleInput) { await page.keyboard.type(code, { delay: 50 }) this.bot.logger.info(this.bot.isMobile, 'LOGIN-CODE', 'Filled code input') return true } const secondairyInput = await page.$(this.secondairyInputSelector) if (secondairyInput) { await page.keyboard.type(code, { delay: 50 }) this.bot.logger.info(this.bot.isMobile, 'LOGIN-CODE', 'Filled code input') return true } this.bot.logger.warn(this.bot.isMobile, 'LOGIN-CODE', 'No code input field found') return false } catch (error) { this.bot.logger.warn( this.bot.isMobile, 'LOGIN-CODE', `Failed to fill code input: ${error instanceof Error ? error.message : String(error)}` ) return false } } async handle(page: Page): Promise { try { this.bot.logger.info(this.bot.isMobile, 'LOGIN-CODE', 'Code login authentication requested') const emailMessage = await getSubtitleMessage(page) if (emailMessage) { this.bot.logger.info(this.bot.isMobile, 'LOGIN-CODE', `Page message: "${emailMessage}"`) } else { this.bot.logger.warn(this.bot.isMobile, 'LOGIN-CODE', 'Unable to retrieve email code destination') } for (let attempt = 1; attempt <= this.maxManualAttempts; attempt++) { const code = await promptInput({ question: `Enter the 6-digit code (waiting ${this.maxManualSeconds}s): `, timeoutSeconds: this.maxManualSeconds, validate: code => /^\d{6}$/.test(code) }) if (!code || !/^\d{6}$/.test(code)) { this.bot.logger.warn( this.bot.isMobile, 'LOGIN-CODE', `Invalid or missing code (attempt ${attempt}/${this.maxManualAttempts}) | input length=${code?.length}` ) if (attempt === this.maxManualAttempts) { throw new Error('Manual code input failed or timed out') } continue } const filled = await this.fillCode(page, code) if (!filled) { this.bot.logger.error( this.bot.isMobile, 'LOGIN-CODE', `Unable to fill code input (attempt ${attempt}/${this.maxManualAttempts})` ) if (attempt === this.maxManualAttempts) { throw new Error('Code input field not found') } continue } await this.bot.utils.wait(500) await page.waitForLoadState('networkidle', { timeout: 5000 }).catch(() => {}) // Check if wrong code was entered const errorMessage = await getErrorMessage(page) if (errorMessage) { this.bot.logger.warn( this.bot.isMobile, 'LOGIN-CODE', `Incorrect code: ${errorMessage} (attempt ${attempt}/${this.maxManualAttempts})` ) if (attempt === this.maxManualAttempts) { throw new Error(`Maximum attempts reached: ${errorMessage}`) } // Clear the input field before retrying const inputToClear = await page.$(this.textInputSelector).catch(() => null) if (inputToClear) { await inputToClear.click() await page.keyboard.press('Control+A') await page.keyboard.press('Backspace') } continue } this.bot.logger.info(this.bot.isMobile, 'LOGIN-CODE', 'Code authentication completed successfully') return } throw new Error(`Code input failed after ${this.maxManualAttempts} attempts`) } catch (error) { this.bot.logger.error( this.bot.isMobile, 'LOGIN-CODE', `Error occurred: ${error instanceof Error ? error.message : String(error)}` ) throw error } } }