diff --git a/entrypoint.sh b/entrypoint.sh index 22d53b8..244a279 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -47,4 +47,4 @@ crontab /etc/cron.d/microsoft-rewards-cron echo "[entrypoint] Cron configured with schedule: $CRON_SCHEDULE and timezone: $TZ; starting cron at $(date)" # 5. Start cron in foreground (PID 1) -exec cron -f \ No newline at end of file +exec cron -f diff --git a/src/account-creation/AccountCreator.ts b/src/account-creation/AccountCreator.ts index fe40b11..b154887 100644 --- a/src/account-creation/AccountCreator.ts +++ b/src/account-creation/AccountCreator.ts @@ -350,6 +350,7 @@ export class AccountCreator { await this.page.waitForLoadState('networkidle', { timeout: Math.min(maxWaitMs, 10000) }) // STEP 2: Wait for DOM to be fully loaded + // Silent catch justified: DOMContentLoaded may already be complete await this.page.waitForLoadState('domcontentloaded', { timeout: 3000 }).catch(() => {}) // STEP 3: REDUCED delay - pages load fast @@ -369,7 +370,7 @@ export class AccountCreator { const visible = await element.isVisible().catch(() => false) if (visible) { - // Wait silently, no spam logs + // Silent catch justified: Loading indicators may disappear before timeout, which is fine await element.waitFor({ state: 'hidden', timeout: Math.min(5000, maxWaitMs - (Date.now() - startTime)) }).catch(() => {}) } } diff --git a/src/functions/Login.ts b/src/functions/Login.ts index 5559172..3b1e6fe 100644 --- a/src/functions/Login.ts +++ b/src/functions/Login.ts @@ -543,6 +543,7 @@ export class Login { await this.bot.utils.wait(500) // Increased from 250ms // IMPROVEMENT: Wait for page to be fully ready before looking for email field + // Silent catch justified: DOMContentLoaded may already be complete, which is fine await page.waitForLoadState('domcontentloaded', { timeout: 10000 }).catch(() => {}) await this.bot.utils.wait(300) // Extra settling time @@ -572,6 +573,7 @@ export class Login { const content = await page.content().catch(() => '') if (content.length < 1000) { this.bot.log(this.bot.isMobile, 'LOGIN', 'Page content too small, reloading...', 'warn') + // Silent catch justified: Reload may timeout if page is slow, but we continue anyway await page.reload({ waitUntil: 'domcontentloaded', timeout: 15000 }).catch(() => {}) await this.bot.utils.wait(1500) } diff --git a/src/functions/activities/Search.ts b/src/functions/activities/Search.ts index b3acadb..68af942 100644 --- a/src/functions/activities/Search.ts +++ b/src/functions/activities/Search.ts @@ -387,7 +387,8 @@ export class Search extends Workers { private async clickRandomLink(page: Page) { try { - await page.click('#b_results .b_algo h2', { timeout: 2000 }).catch(() => { }) // Since we don't really care if it did it or not + // Silent catch justified: Click is best-effort humanization, failure is acceptable + await page.click('#b_results .b_algo h2', { timeout: 2000 }).catch(() => {}) // Only used if the browser is not the edge browser (continue on Edge popup) await this.closeContinuePopup(page) diff --git a/src/run_daily.sh b/src/run_daily.sh index 9ee9271..6024c6f 100644 --- a/src/run_daily.sh +++ b/src/run_daily.sh @@ -153,4 +153,4 @@ else fi echo "[$(date)] [run_daily.sh] Script finished" -# Lock is released automatically via trap \ No newline at end of file +# Lock is released automatically via trap diff --git a/src/util/JobState.ts b/src/util/JobState.ts index c839b9b..5f93948 100644 --- a/src/util/JobState.ts +++ b/src/util/JobState.ts @@ -41,7 +41,11 @@ export class JobState { const raw = fs.readFileSync(file, 'utf-8') const parsed = JSON.parse(raw) return parsed && typeof parsed === 'object' && parsed.days ? parsed as FileState : { days: {} } - } catch { return { days: {} } } + } catch (error) { + // Silent catch justified: Corrupted job state files should be reset to empty state + // rather than crashing the bot. This is a recoverable error. + return { days: {} } + } } private save(email: string, state: FileState): void { diff --git a/src/util/Load.ts b/src/util/Load.ts index aa80084..1a932d3 100644 --- a/src/util/Load.ts +++ b/src/util/Load.ts @@ -70,12 +70,15 @@ function stripJsonComments(input: string): string { // Normalize both legacy (flat) and new (nested) config schemas into the flat Config interface function normalizeConfig(raw: unknown): Config { - // TYPE SAFETY NOTE: Using `any` here is intentional and justified - // Reason: Config format evolved from flat → nested structure. This function must support BOTH - // for backward compatibility. Runtime validation via explicit checks ensures safety. - // Return type (Config interface) provides type safety at function boundary. + // TYPE SAFETY: Validate raw input before processing + // Config files are untrusted JSON that could have any structure + // We use explicit runtime checks for each property below + if (!raw || typeof raw !== 'object') { + throw new Error('Config must be a valid object') + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any - const n = (raw || {}) as any + const n = raw as Record // Browser settings const browserConfig = n.browser ?? {} @@ -356,19 +359,20 @@ export function loadAccounts(): Account[] { // Accept either a root array or an object with an `accounts` array, ignore `_note` const parsed = Array.isArray(parsedUnknown) ? parsedUnknown : (parsedUnknown && typeof parsedUnknown === 'object' && Array.isArray((parsedUnknown as { accounts?: unknown }).accounts) ? (parsedUnknown as { accounts: unknown[] }).accounts : null) if (!Array.isArray(parsed)) throw new Error('accounts must be an array') - // FIXED: Validate entries BEFORE type assertion for better type safety + // TYPE SAFETY: Validate entries BEFORE processing for (const entry of parsed) { - // Pre-validation: Check basic structure before casting + // Pre-validation: Check basic structure if (!entry || typeof entry !== 'object') { throw new Error('each account entry must be an object') } - // JUSTIFIED USE OF `any`: Accounts come from untrusted user JSON with unpredictable structure - // We perform explicit runtime validation of each property below (typeof checks, regex validation, etc.) - // This is safer than trusting a type assertion to a specific interface + // Use Record to access dynamic properties from untrusted JSON + // Runtime validation below ensures type safety // eslint-disable-next-line @typescript-eslint/no-explicit-any - const a = entry as any - if (!a || typeof a.email !== 'string' || typeof a.password !== 'string') { + const a = entry as Record + + // Validate required fields with proper type checking + if (typeof a.email !== 'string' || typeof a.password !== 'string') { throw new Error('each account must have email and password strings') } a.email = String(a.email).trim() @@ -392,11 +396,15 @@ export function loadAccounts(): Account[] { if (!a.proxy || typeof a.proxy !== 'object') { a.proxy = { proxyAxios: true, url: '', port: 0, username: '', password: '' } } else { - a.proxy.proxyAxios = a.proxy.proxyAxios !== false - a.proxy.url = typeof a.proxy.url === 'string' ? a.proxy.url : '' - a.proxy.port = typeof a.proxy.port === 'number' ? a.proxy.port : 0 - a.proxy.username = typeof a.proxy.username === 'string' ? a.proxy.username : '' - a.proxy.password = typeof a.proxy.password === 'string' ? a.proxy.password : '' + // Safe proxy property access with runtime validation + const proxy = a.proxy as Record + a.proxy = { + proxyAxios: proxy.proxyAxios !== false, + url: typeof proxy.url === 'string' ? proxy.url : '', + port: typeof proxy.port === 'number' ? proxy.port : 0, + username: typeof proxy.username === 'string' ? proxy.username : '', + password: typeof proxy.password === 'string' ? proxy.password : '' + } } } // Filter out disabled accounts (enabled: false) diff --git a/src/util/Utils.ts b/src/util/Utils.ts index 3e7c6e1..1e8fb28 100644 --- a/src/util/Utils.ts +++ b/src/util/Utils.ts @@ -114,7 +114,7 @@ export class Util { * @example utils.chunkArray([1,2,3,4,5], 2) // [[1,2,3], [4,5]] */ chunkArray(arr: T[], numChunks: number): T[][] { - // FIXED: Stricter validation with better error messages + // Validate input parameters if (!Array.isArray(arr)) { throw new Error('Invalid input: arr must be an array.') } @@ -123,11 +123,6 @@ export class Util { return [] } - // Check for undefined/null elements which could cause issues downstream - if (arr.some(item => item === undefined || item === null)) { - throw new Error('Array contains undefined or null elements which are not allowed.') - } - if (!Number.isFinite(numChunks) || numChunks <= 0) { throw new Error(`Invalid numChunks: ${numChunks}. Must be a positive finite number.`) }