mirror of
https://github.com/LightZirconite/Microsoft-Rewards-Bot.git
synced 2026-01-11 09:46:16 +00:00
feat: Improve error logging and validation across various flows and utilities
This commit is contained in:
@@ -9,7 +9,6 @@ import { Counters, DashboardData, MorePromotion, PromotionalItem } from '../inte
|
|||||||
import { EarnablePoints } from '../interface/Points'
|
import { EarnablePoints } from '../interface/Points'
|
||||||
import { QuizData } from '../interface/QuizData'
|
import { QuizData } from '../interface/QuizData'
|
||||||
import { saveSessionData } from '../util/Load'
|
import { saveSessionData } from '../util/Load'
|
||||||
import { logError } from '../util/Logger'
|
|
||||||
|
|
||||||
|
|
||||||
export default class BrowserFunc {
|
export default class BrowserFunc {
|
||||||
@@ -114,7 +113,7 @@ export default class BrowserFunc {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||||
this.bot.log(this.bot.isMobile, 'GO-HOME', 'An error occurred: ' + errorMessage, 'error')
|
this.bot.log(this.bot.isMobile, 'GO-HOME', `[goHome] Navigation failed: ${errorMessage}`, 'error')
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,7 +178,7 @@ export default class BrowserFunc {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||||
this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', `Error fetching dashboard data: ${errorMessage}`, 'error')
|
this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', `[getDashboardData] Failed to fetch dashboard data: ${errorMessage}`, 'error')
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,7 +245,7 @@ export default class BrowserFunc {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse dashboard object from script content
|
* Parse dashboard object from script content
|
||||||
* FIXED: Added format validation before JSON.parse
|
* IMPROVED: Enhanced validation with structure checks
|
||||||
*/
|
*/
|
||||||
private async parseDashboardFromScript(page: Page, scriptContent: string): Promise<DashboardData | null> {
|
private async parseDashboardFromScript(page: Page, scriptContent: string): Promise<DashboardData | null> {
|
||||||
return await page.evaluate((scriptContent: string) => {
|
return await page.evaluate((scriptContent: string) => {
|
||||||
@@ -262,16 +261,27 @@ export default class BrowserFunc {
|
|||||||
try {
|
try {
|
||||||
const jsonStr = match[1]
|
const jsonStr = match[1]
|
||||||
// Validate basic JSON structure before parsing
|
// Validate basic JSON structure before parsing
|
||||||
if (!jsonStr.trim().startsWith('{') || !jsonStr.trim().endsWith('}')) {
|
const trimmed = jsonStr.trim()
|
||||||
|
if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsed = JSON.parse(jsonStr)
|
const parsed = JSON.parse(jsonStr)
|
||||||
// Validate it's actually an object
|
|
||||||
|
// Enhanced validation: check structure and type
|
||||||
if (typeof parsed !== 'object' || parsed === null) {
|
if (typeof parsed !== 'object' || parsed === null) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate essential dashboard properties exist
|
||||||
|
if (!parsed.userStatus || typeof parsed.userStatus !== 'object') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Successfully validated dashboard structure
|
||||||
return parsed
|
return parsed
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
// JSON.parse failed or validation error - try next pattern
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -339,7 +349,7 @@ export default class BrowserFunc {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||||
this.bot.log(this.bot.isMobile, 'GET-BROWSER-EARNABLE-POINTS', 'An error occurred: ' + errorMessage, 'error')
|
this.bot.log(this.bot.isMobile, 'GET-BROWSER-EARNABLE-POINTS', `[getBrowserEarnablePoints] Failed to calculate earnable points: ${errorMessage}`, 'error')
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -404,7 +414,7 @@ export default class BrowserFunc {
|
|||||||
return points
|
return points
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||||
this.bot.log(this.bot.isMobile, 'GET-APP-EARNABLE-POINTS', 'An error occurred: ' + errorMessage, 'error')
|
this.bot.log(this.bot.isMobile, 'GET-APP-EARNABLE-POINTS', `[getAppEarnablePoints] Failed to fetch app earnable points: ${errorMessage}`, 'error')
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -420,7 +430,7 @@ export default class BrowserFunc {
|
|||||||
return data.userStatus.availablePoints
|
return data.userStatus.availablePoints
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||||
this.bot.log(this.bot.isMobile, 'GET-CURRENT-POINTS', 'An error occurred: ' + errorMessage, 'error')
|
this.bot.log(this.bot.isMobile, 'GET-CURRENT-POINTS', `[getCurrentPoints] Failed to fetch current points: ${errorMessage}`, 'error')
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -493,7 +503,7 @@ export default class BrowserFunc {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||||
this.bot.log(this.bot.isMobile, 'GET-QUIZ-DATA', 'An error occurred: ' + errorMessage, 'error')
|
this.bot.log(this.bot.isMobile, 'GET-QUIZ-DATA', `[getQuizData] Failed to extract quiz data: ${errorMessage}`, 'error')
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -561,7 +571,7 @@ export default class BrowserFunc {
|
|||||||
this.bot.log(this.bot.isMobile, 'CLOSE-BROWSER', 'Browser closed cleanly!')
|
this.bot.log(this.bot.isMobile, 'CLOSE-BROWSER', 'Browser closed cleanly!')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||||
this.bot.log(this.bot.isMobile, 'CLOSE-BROWSER', 'An error occurred: ' + errorMessage, 'error')
|
this.bot.log(this.bot.isMobile, 'CLOSE-BROWSER', `[closeBrowser] Failed to close browser cleanly: ${errorMessage}`, 'error')
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse environment variable as number with validation
|
* Parse environment variable as number with validation
|
||||||
* FIXED: Added strict validation for min/max boundaries
|
* FIXED: Added strict validation for min/max boundaries with logging
|
||||||
* @param key Environment variable name
|
* @param key Environment variable name
|
||||||
* @param defaultValue Default value if parsing fails or out of range
|
* @param defaultValue Default value if parsing fails or out of range
|
||||||
* @param min Minimum allowed value
|
* @param min Minimum allowed value
|
||||||
@@ -18,13 +18,24 @@ function parseEnvNumber(key: string, defaultValue: number, min: number, max: num
|
|||||||
|
|
||||||
const parsed = Number(raw)
|
const parsed = Number(raw)
|
||||||
// Strict validation: must be finite, not NaN, and within bounds
|
// Strict validation: must be finite, not NaN, and within bounds
|
||||||
if (!Number.isFinite(parsed) || parsed < min || parsed > max) {
|
if (!Number.isFinite(parsed)) {
|
||||||
|
console.warn(`[Constants] Invalid ${key}="${raw}" (not a finite number), using default: ${defaultValue}`)
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed < min || parsed > max) {
|
||||||
|
console.warn(`[Constants] ${key}=${parsed} out of range [${min}, ${max}], using default: ${defaultValue}`)
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsed
|
return parsed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Login timeout boundaries (in milliseconds)
|
||||||
|
const LOGIN_TIMEOUT_MIN_MS = 30000 // 30 seconds - minimum login wait
|
||||||
|
const LOGIN_TIMEOUT_MAX_MS = 600000 // 10 minutes - maximum login wait
|
||||||
|
const LOGIN_TIMEOUT_DEFAULT_MS = 180000 // 3 minutes - default login timeout
|
||||||
|
|
||||||
export const TIMEOUTS = {
|
export const TIMEOUTS = {
|
||||||
SHORT: 500,
|
SHORT: 500,
|
||||||
MEDIUM: 1500,
|
MEDIUM: 1500,
|
||||||
@@ -33,7 +44,7 @@ export const TIMEOUTS = {
|
|||||||
VERY_LONG: 5000,
|
VERY_LONG: 5000,
|
||||||
EXTRA_LONG: 10000,
|
EXTRA_LONG: 10000,
|
||||||
DASHBOARD_WAIT: 10000,
|
DASHBOARD_WAIT: 10000,
|
||||||
LOGIN_MAX: parseEnvNumber('LOGIN_MAX_WAIT_MS', 180000, 30000, 600000),
|
LOGIN_MAX: parseEnvNumber('LOGIN_MAX_WAIT_MS', LOGIN_TIMEOUT_DEFAULT_MS, LOGIN_TIMEOUT_MIN_MS, LOGIN_TIMEOUT_MAX_MS),
|
||||||
NETWORK_IDLE: 5000,
|
NETWORK_IDLE: 5000,
|
||||||
ONE_MINUTE: 60000,
|
ONE_MINUTE: 60000,
|
||||||
ONE_HOUR: 3600000,
|
ONE_HOUR: 3600000,
|
||||||
|
|||||||
@@ -29,8 +29,23 @@ export class DesktopFlow {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the full desktop automation flow for an account
|
* Execute the full desktop automation flow for an account
|
||||||
* @param account Account to process
|
*
|
||||||
* @returns Points collected during the flow
|
* Performs the following tasks in sequence:
|
||||||
|
* 1. Browser initialization with fingerprinting
|
||||||
|
* 2. Microsoft account login with 2FA support
|
||||||
|
* 3. Daily set completion
|
||||||
|
* 4. More promotions (quizzes, polls, etc.)
|
||||||
|
* 5. Punch cards
|
||||||
|
* 6. Desktop searches
|
||||||
|
*
|
||||||
|
* @param account Account to process (email, password, totp, proxy)
|
||||||
|
* @returns Promise resolving to points collected during the flow
|
||||||
|
* @throws {Error} If critical operation fails (login, browser init)
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const flow = new DesktopFlow(bot)
|
||||||
|
* const result = await flow.run(account)
|
||||||
|
* console.log(`Collected ${result.collectedPoints} points`)
|
||||||
*/
|
*/
|
||||||
async run(account: Account): Promise<DesktopFlowResult> {
|
async run(account: Account): Promise<DesktopFlowResult> {
|
||||||
this.bot.log(false, 'DESKTOP-FLOW', 'Starting desktop automation flow')
|
this.bot.log(false, 'DESKTOP-FLOW', 'Starting desktop automation flow')
|
||||||
@@ -63,7 +78,10 @@ export class DesktopFlow {
|
|||||||
undefined,
|
undefined,
|
||||||
0xFFAA00
|
0xFFAA00
|
||||||
)
|
)
|
||||||
} catch {/* ignore */}
|
} catch (error) {
|
||||||
|
const errorMsg = error instanceof Error ? error.message : String(error)
|
||||||
|
this.bot.log(false, 'DESKTOP-FLOW', `Failed to send security webhook: ${errorMsg}`, 'warn')
|
||||||
|
}
|
||||||
|
|
||||||
// Save session for convenience, but do not close the browser
|
// Save session for convenience, but do not close the browser
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -31,9 +31,24 @@ export class MobileFlow {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the full mobile automation flow for an account
|
* Execute the full mobile automation flow for an account
|
||||||
* @param account Account to process
|
*
|
||||||
* @param retryTracker Retry tracker for mobile search failures
|
* Performs the following tasks in sequence:
|
||||||
* @returns Points collected during the flow
|
* 1. Mobile browser initialization with mobile user agent
|
||||||
|
* 2. Microsoft account login
|
||||||
|
* 3. OAuth token acquisition for mobile API access
|
||||||
|
* 4. Daily check-in via mobile API
|
||||||
|
* 5. Read to earn articles
|
||||||
|
* 6. Mobile searches with retry logic
|
||||||
|
*
|
||||||
|
* @param account Account to process (email, password, totp, proxy)
|
||||||
|
* @param retryTracker Retry tracker for mobile search failures (auto-created if not provided)
|
||||||
|
* @returns Promise resolving to points collected during mobile flow
|
||||||
|
* @throws {Error} If critical operation fails (login, OAuth)
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const flow = new MobileFlow(bot)
|
||||||
|
* const result = await flow.run(account)
|
||||||
|
* console.log(`Mobile: ${result.collectedPoints} points`)
|
||||||
*/
|
*/
|
||||||
async run(
|
async run(
|
||||||
account: Account,
|
account: Account,
|
||||||
@@ -69,7 +84,10 @@ export class MobileFlow {
|
|||||||
undefined,
|
undefined,
|
||||||
0xFFAA00
|
0xFFAA00
|
||||||
)
|
)
|
||||||
} catch {/* ignore */}
|
} catch (error) {
|
||||||
|
const errorMsg = error instanceof Error ? error.message : String(error)
|
||||||
|
this.bot.log(true, 'MOBILE-FLOW', `Failed to send security webhook: ${errorMsg}`, 'warn')
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await saveSessionData(this.bot.config.sessionPath, this.bot.homePage.context(), account.email, true)
|
await saveSessionData(this.bot.config.sessionPath, this.bot.homePage.context(), account.email, true)
|
||||||
|
|||||||
@@ -39,6 +39,24 @@ export class Activities {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Centralized dispatcher for activities from dashboard/punchcards
|
// Centralized dispatcher for activities from dashboard/punchcards
|
||||||
|
/**
|
||||||
|
* Execute a promotional activity (quiz, poll, search-on-bing, etc.)
|
||||||
|
*
|
||||||
|
* Automatically detects activity type and delegates to specialized handler:
|
||||||
|
* - quiz → Quiz handler
|
||||||
|
* - abc → ABC (drag-and-drop) handler
|
||||||
|
* - thisorthat → This or That handler
|
||||||
|
* - poll → Poll handler
|
||||||
|
* - urlreward → URL reward handler
|
||||||
|
*
|
||||||
|
* @param page Playwright page for activity execution
|
||||||
|
* @param activity Activity metadata from dashboard data
|
||||||
|
* @returns Promise resolving when activity is complete
|
||||||
|
* @throws {Error} If activity type is unsupported or execution fails
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await activities.run(page, dailySetActivity)
|
||||||
|
*/
|
||||||
async run(page: Page, activity: MorePromotion | PromotionalItem): Promise<void> {
|
async run(page: Page, activity: MorePromotion | PromotionalItem): Promise<void> {
|
||||||
// First, try custom handlers (if any)
|
// First, try custom handlers (if any)
|
||||||
for (const h of this.handlers) {
|
for (const h of this.handlers) {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Page } from 'rebrowser-playwright'
|
|
||||||
import { platform } from 'os'
|
import { platform } from 'os'
|
||||||
|
import { Page } from 'rebrowser-playwright'
|
||||||
|
|
||||||
import { Workers } from '../Workers'
|
import { Workers } from '../Workers'
|
||||||
|
|
||||||
|
import { AxiosRequestConfig } from 'axios'
|
||||||
import { Counters, DashboardData } from '../../interface/DashboardData'
|
import { Counters, DashboardData } from '../../interface/DashboardData'
|
||||||
import { GoogleSearch } from '../../interface/Search'
|
import { GoogleSearch } from '../../interface/Search'
|
||||||
import { AxiosRequestConfig } from 'axios'
|
|
||||||
|
|
||||||
type GoogleTrendsResponse = [
|
type GoogleTrendsResponse = [
|
||||||
string,
|
string,
|
||||||
@@ -16,6 +16,10 @@ type GoogleTrendsResponse = [
|
|||||||
][]
|
][]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Search stagnation thresholds (magic numbers extracted as constants)
|
||||||
|
const MOBILE_STAGNATION_LIMIT = 5 // Mobile searches: abort after 5 queries without points
|
||||||
|
const DESKTOP_STAGNATION_LIMIT = 10 // Desktop searches: abort after 10 queries without points
|
||||||
|
|
||||||
export class Search extends Workers {
|
export class Search extends Workers {
|
||||||
private bingHome = 'https://bing.com'
|
private bingHome = 'https://bing.com'
|
||||||
private searchPageURL = ''
|
private searchPageURL = ''
|
||||||
@@ -119,15 +123,15 @@ export class Search extends Workers {
|
|||||||
|
|
||||||
if (missingPoints === 0) break
|
if (missingPoints === 0) break
|
||||||
|
|
||||||
// Only for mobile searches
|
// Only for mobile searches - abort early if User-Agent is likely incorrect
|
||||||
if (stagnation > 5 && this.bot.isMobile) {
|
if (stagnation > MOBILE_STAGNATION_LIMIT && this.bot.isMobile) {
|
||||||
this.bot.log(this.bot.isMobile, 'SEARCH-BING', 'Search didn\'t gain point for 5 iterations, likely bad User-Agent', 'warn')
|
this.bot.log(this.bot.isMobile, 'SEARCH-BING', `Search didn't gain points for ${MOBILE_STAGNATION_LIMIT} iterations, likely bad User-Agent`, 'warn')
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we didn't gain points for 10 iterations, assume it's stuck
|
// If we didn't gain points for many iterations, assume it's stuck
|
||||||
if (stagnation > 10) {
|
if (stagnation > DESKTOP_STAGNATION_LIMIT) {
|
||||||
this.bot.log(this.bot.isMobile, 'SEARCH-BING', 'Search didn\'t gain point for 10 iterations aborting searches', 'warn')
|
this.bot.log(this.bot.isMobile, 'SEARCH-BING', `Search didn't gain points for ${DESKTOP_STAGNATION_LIMIT} iterations, aborting searches`, 'warn')
|
||||||
stagnation = 0 // allow fallback loop below
|
stagnation = 0 // allow fallback loop below
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,6 +139,17 @@ function determineColorFromContent(content: string): number {
|
|||||||
return DISCORD.COLOR_GRAY
|
return DISCORD.COLOR_GRAY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type guard to check if config has valid logging configuration
|
||||||
|
*/
|
||||||
|
function hasValidLogging(config: unknown): config is { logging: { excludeFunc?: string[]; webhookExcludeFunc?: string[] } } {
|
||||||
|
return typeof config === 'object' &&
|
||||||
|
config !== null &&
|
||||||
|
'logging' in config &&
|
||||||
|
typeof config.logging === 'object' &&
|
||||||
|
config.logging !== null
|
||||||
|
}
|
||||||
|
|
||||||
function enqueueWebhookLog(url: string, line: string) {
|
function enqueueWebhookLog(url: string, line: string) {
|
||||||
const buf = getBuffer(url)
|
const buf = getBuffer(url)
|
||||||
buf.lines.push(line)
|
buf.lines.push(line)
|
||||||
@@ -164,9 +175,8 @@ function enqueueWebhookLog(url: string, line: string) {
|
|||||||
export function log(isMobile: boolean | 'main', title: string, message: string, type: 'log' | 'warn' | 'error' = 'log', color?: keyof typeof chalk): Error | void {
|
export function log(isMobile: boolean | 'main', title: string, message: string, type: 'log' | 'warn' | 'error' = 'log', color?: keyof typeof chalk): Error | void {
|
||||||
const configData = loadConfig()
|
const configData = loadConfig()
|
||||||
|
|
||||||
// Access logging config with fallback for backward compatibility
|
// Access logging config with type guard for safer access
|
||||||
const configAny = configData as unknown as Record<string, unknown>
|
const logging = hasValidLogging(configData) ? configData.logging : undefined
|
||||||
const logging = configAny.logging as { excludeFunc?: string[]; logExcludeFunc?: string[] } | undefined
|
|
||||||
const logExcludeFunc = logging?.excludeFunc ?? (configData as { logExcludeFunc?: string[] }).logExcludeFunc ?? []
|
const logExcludeFunc = logging?.excludeFunc ?? (configData as { logExcludeFunc?: string[] }).logExcludeFunc ?? []
|
||||||
|
|
||||||
if (logExcludeFunc.some((x: string) => x.toLowerCase() === title.toLowerCase())) {
|
if (logExcludeFunc.some((x: string) => x.toLowerCase() === title.toLowerCase())) {
|
||||||
@@ -178,7 +188,7 @@ export function log(isMobile: boolean | 'main', title: string, message: string,
|
|||||||
|
|
||||||
// Clean string for notifications (no chalk, structured)
|
// Clean string for notifications (no chalk, structured)
|
||||||
type LoggingCfg = { excludeFunc?: string[]; webhookExcludeFunc?: string[]; redactEmails?: boolean }
|
type LoggingCfg = { excludeFunc?: string[]; webhookExcludeFunc?: string[]; redactEmails?: boolean }
|
||||||
const loggingCfg: LoggingCfg = (configAny.logging || {}) as LoggingCfg
|
const loggingCfg: LoggingCfg = logging || {}
|
||||||
const shouldRedact = !!loggingCfg.redactEmails
|
const shouldRedact = !!loggingCfg.redactEmails
|
||||||
const redact = (s: string) => shouldRedact ? s.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/ig, (m) => {
|
const redact = (s: string) => shouldRedact ? s.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/ig, (m) => {
|
||||||
const [u, d] = m.split('@'); return `${(u||'').slice(0,2)}***@${d||''}`
|
const [u, d] = m.split('@'); return `${(u||'').slice(0,2)}***@${d||''}`
|
||||||
@@ -269,7 +279,7 @@ export function log(isMobile: boolean | 'main', title: string, message: string,
|
|||||||
|
|
||||||
// Webhook streaming (live logs)
|
// Webhook streaming (live logs)
|
||||||
try {
|
try {
|
||||||
const loggingCfg: Record<string, unknown> = (configAny.logging || {}) as Record<string, unknown>
|
const loggingCfg: Record<string, unknown> = (logging || {}) as Record<string, unknown>
|
||||||
const webhookCfg = configData.webhook
|
const webhookCfg = configData.webhook
|
||||||
const liveUrlRaw = typeof loggingCfg.liveWebhookUrl === 'string' ? loggingCfg.liveWebhookUrl.trim() : ''
|
const liveUrlRaw = typeof loggingCfg.liveWebhookUrl === 'string' ? loggingCfg.liveWebhookUrl.trim() : ''
|
||||||
const liveUrl = liveUrlRaw || (webhookCfg?.enabled && webhookCfg.url ? webhookCfg.url : '')
|
const liveUrl = liveUrlRaw || (webhookCfg?.enabled && webhookCfg.url ? webhookCfg.url : '')
|
||||||
|
|||||||
@@ -123,6 +123,11 @@ export class Util {
|
|||||||
return []
|
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) {
|
if (!Number.isFinite(numChunks) || numChunks <= 0) {
|
||||||
throw new Error(`Invalid numChunks: ${numChunks}. Must be a positive finite number.`)
|
throw new Error(`Invalid numChunks: ${numChunks}. Must be a positive finite number.`)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user