Clean up and fix

This commit is contained in:
2025-11-10 22:11:52 +01:00
parent bbb6a8e432
commit e9a1e2dbcf
8 changed files with 39 additions and 28 deletions

View File

@@ -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
exec cron -f

View File

@@ -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(() => {})
}
}

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -153,4 +153,4 @@ else
fi
echo "[$(date)] [run_daily.sh] Script finished"
# Lock is released automatically via trap
# Lock is released automatically via trap

View File

@@ -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 {

View File

@@ -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<string, any>
// 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<string, any> 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<string, any>
// 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<string, unknown>
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)

View File

@@ -114,7 +114,7 @@ export class Util {
* @example utils.chunkArray([1,2,3,4,5], 2) // [[1,2,3], [4,5]]
*/
chunkArray<T>(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.`)
}