entrox-bypass 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/llm.ts","../src/cloudinary.ts","../src/captchaSolve.ts","../src/login.ts"],"sourcesContent":["import { solveCaptchaFromPage } from \"./captchaSolve.js\";\nimport { automateLogin, LoginOptions, LoginResult } from \"./login.js\";\n\nexport type { CaptchaResult } from \"./llm.js\";\nexport type { LoginOptions, LoginResult } from \"./login.js\";\n\nexport { solveCaptchaFromPage };\nexport { automateLogin };\n\nexport default {\n solveCaptchaFromPage,\n automateLogin,\n};\n","import \"dotenv/config\";\nimport { ChatOpenAI } from \"@langchain/openai\";\nimport { HumanMessage } from \"@langchain/core/messages\";\n\ninterface CaptchaResult {\n text: string;\n description: string;\n}\n\nclass LlmError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'LlmError';\n }\n}\n\nconst model = new ChatOpenAI({\n model: process.env.LLM_MODEL || \"google/gemma-4-26b-a4b-it\",\n temperature: 0,\n apiKey: process.env.OPENROUTER_API_KEY,\n configuration: {\n baseURL: \"https://openrouter.ai/api/v1\",\n },\n});\n\nconst getImageDetails = async function (image_url: string): Promise<CaptchaResult> {\n if (!process.env.OPENROUTER_API_KEY) {\n throw new LlmError('OPENROUTER_API_KEY environment variable is required');\n }\n\n const response = await model.invoke([\n new HumanMessage({\n content: [\n {\n type: \"text\",\n text: `You are a CAPTCHA solving assistant. Analyze the provided image and return the following in strict JSON format:\n- \"text\": The exact text or characters shown in the CAPTCHA\n- \"description\": A brief description of what appears in the image\n\nIf there is no text in the image, set \"text\" to \"no text found\".\n\nExample response:\n{\"text\": \"X7K9P\", \"description\": \"Black text on white background showing alphanumeric characters\"}\n\nReturn ONLY valid JSON with no markdown formatting.`,\n },\n {\n type: \"image_url\",\n image_url: {\n url: image_url,\n },\n },\n ],\n }),\n ]);\n\n const content = response.content as string | Array<any>;\n \n if (typeof content === 'string') {\n const jsonString = content\n .replace(/```json\\s*/i, '')\n .replace(/```\\s*$/, '')\n .trim();\n \n try {\n return JSON.parse(jsonString);\n } catch (parseError) {\n throw new LlmError(`Failed to parse LLM response as JSON: ${parseError}`);\n }\n }\n \n throw new LlmError('Unexpected LLM response format');\n};\n\nexport default getImageDetails;\nexport type { CaptchaResult };\nexport { LlmError };\n","import 'dotenv/config';\nimport { v2 as cloudinary } from 'cloudinary';\n\ninterface CloudinaryConfig {\n cloud_name: string;\n api_key: string;\n api_secret: string;\n}\n\nclass CloudinaryError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'CloudinaryError';\n }\n}\n\nfunction validateConfig(config: CloudinaryConfig): void {\n const missing: string[] = [];\n \n if (!config.cloud_name) missing.push('CLOUDINARY_CLOUD_NAME');\n if (!config.api_key) missing.push('CLOUDINARY_API_KEY');\n if (!config.api_secret) missing.push('CLOUDINARY_API_SECRET');\n \n if (missing.length > 0) {\n throw new CloudinaryError(`Missing Cloudinary credentials: ${missing.join(', ')}`);\n }\n}\n\nexport interface UploadResult {\n secure_url: string;\n public_id: string;\n format: string;\n width: number;\n height: number;\n bytes: number;\n resource_type: string;\n original_filename: string;\n}\n\nexport function getConfig(): CloudinaryConfig {\n return {\n cloud_name: process.env.CLOUDINARY_CLOUD_NAME || '',\n api_key: process.env.CLOUDINARY_API_KEY || '',\n api_secret: process.env.CLOUDINARY_API_SECRET || '',\n };\n}\n\nconst config = getConfig();\nvalidateConfig(config);\n\ncloudinary.config(config);\n\nexport default cloudinary;\nexport { CloudinaryError };\n","import { Page } from \"playwright\";\nimport getImageDetails, { CaptchaResult } from \"./llm.js\";\nimport cloudinary from \"./cloudinary.js\";\n\ninterface ScreenshotError extends Error {\n code: string;\n}\n\nexport async function solveCaptchaFromPage(\n page: Page,\n captchaSelector: string = \"img.captcha-image\",\n outputScreenshotPath?: string\n): Promise<CaptchaResult> {\n const captchaImage = page.locator(captchaSelector);\n\n try {\n await captchaImage.waitFor({ state: \"visible\" });\n } catch (error) {\n throw new Error(`CAPTCHA element not found or not visible. Selector: ${captchaSelector}`);\n }\n\n if (outputScreenshotPath) {\n await captchaImage.screenshot({ path: outputScreenshotPath });\n }\n\n let imageData: { mimeType: string; base64: string };\n\n try {\n imageData = await captchaImage.evaluate(async (img: HTMLImageElement) => {\n const response = await fetch(img.src);\n\n if (!response.ok) {\n throw new Error(`Image fetch failed: ${response.status}`);\n }\n\n const blob = await response.blob();\n\n return new Promise<{ mimeType: string; base64: string }>((resolve, reject) => {\n const reader = new FileReader();\n\n reader.onload = () => {\n const result = reader.result as string;\n const base64 = result.split(\",\")[1];\n resolve({\n mimeType: blob.type || \"image/png\",\n base64,\n });\n };\n\n reader.onerror = reject;\n reader.readAsDataURL(blob);\n });\n });\n } catch (error) {\n throw new Error(`Failed to extract CAPTCHA image: ${error}`);\n }\n\n let uploadResult;\n\n try {\n uploadResult = await cloudinary.uploader.upload(\n `data:${imageData.mimeType};base64,${imageData.base64}`,\n {\n folder: \"playwright-captcha-images\",\n resource_type: \"image\",\n type: \"auth\",\n }\n );\n } catch (error) {\n throw new Error(`Failed to upload CAPTCHA image to Cloudinary: ${error}`);\n }\n\n const imageUrl = uploadResult.secure_url;\n\n try {\n const result = await getImageDetails(imageUrl);\n return result;\n } catch (error) {\n throw new Error(`Failed to solve CAPTCHA using AI: ${error}`);\n }\n}\n","import { chromium, Browser, Page, BrowserContext } from \"playwright\";\nimport { solveCaptchaFromPage } from \"./captchaSolve.js\";\n\nexport interface LoginOptions {\n url: string;\n loginSelector?: string;\n passwordSelector?: string;\n captchaInputSelector?: string;\n submitButtonSelector?: string;\n headless?: boolean;\n outputScreenshotPath?: string;\n screenshotDelay?: number;\n}\n\nexport interface LoginResult {\n success: boolean;\n url: string;\n title: string;\n page: Page;\n error?: string;\n}\n\nclass BrowserError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'BrowserError';\n }\n}\n\nasync function validateEnv(): Promise<void> {\n const missing: string[] = [];\n \n if (!process.env.EMAIL || process.env.EMAIL === 'dev@example.com') {\n missing.push('EMAIL - Set to actual email address');\n }\n if (!process.env.PASSWORD || process.env.PASSWORD === '22222222') {\n missing.push('PASSWORD - Set to actual password');\n }\n \n if (missing.length > 0) {\n console.warn('Warning: The following environment variables are set to defaults or missing:');\n missing.forEach(m => console.warn(` - ${m}`));\n console.warn('Please update your .env file with real credentials.');\n }\n}\n\nexport async function automateLogin(options: LoginOptions): Promise<LoginResult> {\n let browser: Browser | null = null;\n let context: BrowserContext | null = null;\n let page: Page | null = null;\n\n try {\n await validateEnv();\n\n browser = await chromium.launch({\n headless: options.headless ?? false,\n });\n\n context = await browser.newContext();\n page = await context.newPage();\n\n await page.goto(options.url, { waitUntil: \"domcontentloaded\" });\n\n const loginSelector = options.loginSelector || \"#login\";\n const passwordSelector = options.passwordSelector || \"#password\";\n const captchaSelector = \"img.captcha-image\";\n const captchaInputSelector = options.captchaInputSelector || \"#captcha\";\n const submitButtonSelector = options.submitButtonSelector || \"button[type='submit']\";\n\n await page.locator(loginSelector).fill(process.env.EMAIL || \"\");\n await page.locator(passwordSelector).fill(process.env.PASSWORD || \"\");\n\n const captchaResult = await solveCaptchaFromPage(\n page,\n captchaSelector,\n options.outputScreenshotPath ? undefined : undefined\n );\n\n await page.locator(captchaInputSelector).fill(captchaResult.text);\n await page.locator(submitButtonSelector).click();\n\n try {\n await page.waitForLoadState(\"domcontentloaded\", { timeout: 15000 });\n \n if (options.screenshotDelay) {\n await page.waitForTimeout(options.screenshotDelay);\n }\n\n if (options.outputScreenshotPath) {\n await page.screenshot({ path: options.outputScreenshotPath, fullPage: true });\n }\n } catch (error) {\n console.warn('Warning: Could not wait for post-login state:', error);\n }\n\n return {\n success: true,\n url: page.url(),\n title: await page.title(),\n page,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n \n let pageTitle = '';\n if (page) {\n try {\n pageTitle = await page.title();\n } catch {\n pageTitle = 'failed to get title';\n }\n }\n \n return {\n success: false,\n url: page?.url() || '',\n title: pageTitle,\n page: page!,\n error: errorMessage,\n };\n } finally {\n if (browser && !options.headless) {\n await browser.close().catch(() => {});\n }\n }\n}\n"],"mappings":"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","names":["import_config","config","cloudinary"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/llm.ts","../src/cloudinary.ts","../src/utils/retry.ts","../src/captchaSolve.ts","../src/login.ts","../src/utils/humanDelay.ts"],"sourcesContent":["import { solveCaptchaFromPage, SolveCaptchaOptions } from \"./captchaSolve.js\";\nimport {\n automateLogin,\n LoginOptions,\n LoginResult,\n sendWebhook,\n saveCookies,\n loadCookies,\n setupProxy,\n} from \"./login.js\";\nimport {\n randomBetween,\n delay,\n executeWithRandomDelay,\n} from \"./utils/humanDelay.js\";\nimport { retry, isRetryable, RetryOptions, RetryableError } from \"./utils/retry.js\";\n\nexport type { LoginOptions, LoginResult } from \"./login.js\";\nexport type { CaptchaResult } from \"./llm.js\";\nexport type { SolveCaptchaOptions } from \"./captchaSolve.js\";\nexport type { RetryOptions, RetryableError } from \"./utils/retry.js\";\n\nexport { solveCaptchaFromPage };\nexport { automateLogin };\nexport { sendWebhook, saveCookies, loadCookies, setupProxy };\nexport { randomBetween, delay, executeWithRandomDelay };\nexport { retry, isRetryable };\n\nexport default {\n solveCaptchaFromPage,\n automateLogin,\n sendWebhook,\n saveCookies,\n loadCookies,\n setupProxy,\n randomBetween,\n delay,\n executeWithRandomDelay,\n retry,\n isRetryable,\n};\n","import \"dotenv/config\";\nimport { ChatOpenAI } from \"@langchain/openai\";\nimport { HumanMessage } from \"@langchain/core/messages\";\n\ninterface CaptchaResult {\n text: string;\n description: string;\n}\n\nclass LlmError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'LlmError';\n }\n}\n\nconst model = new ChatOpenAI({\n model: process.env.LLM_MODEL || \"google/gemma-4-26b-a4b-it\",\n temperature: 0,\n apiKey: process.env.OPENROUTER_API_KEY,\n configuration: {\n baseURL: \"https://openrouter.ai/api/v1\",\n },\n});\n\nconst getImageDetails = async function (image_url: string): Promise<CaptchaResult> {\n if (!process.env.OPENROUTER_API_KEY) {\n throw new LlmError('OPENROUTER_API_KEY environment variable is required');\n }\n\n const response = await model.invoke([\n new HumanMessage({\n content: [\n {\n type: \"text\",\n text: `You are a CAPTCHA solving assistant. Analyze the provided image and return the following in strict JSON format:\n- \"text\": The exact text or characters shown in the CAPTCHA\n- \"description\": A brief description of what appears in the image\n\nIf there is no text in the image, set \"text\" to \"no text found\".\n\nExample response:\n{\"text\": \"X7K9P\", \"description\": \"Black text on white background showing alphanumeric characters\"}\n\nReturn ONLY valid JSON with no markdown formatting.`,\n },\n {\n type: \"image_url\",\n image_url: {\n url: image_url,\n },\n },\n ],\n }),\n ]);\n\n const content = response.content as string | Array<any>;\n \n if (typeof content === 'string') {\n const jsonString = content\n .replace(/```json\\s*/i, '')\n .replace(/```\\s*$/, '')\n .trim();\n \n try {\n return JSON.parse(jsonString);\n } catch (parseError) {\n throw new LlmError(`Failed to parse LLM response as JSON: ${parseError}`);\n }\n }\n \n throw new LlmError('Unexpected LLM response format');\n};\n\nexport default getImageDetails;\nexport type { CaptchaResult };\nexport { LlmError };\n","import 'dotenv/config';\nimport { v2 as cloudinary } from 'cloudinary';\n\ninterface CloudinaryConfig {\n cloud_name: string;\n api_key: string;\n api_secret: string;\n}\n\nclass CloudinaryError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'CloudinaryError';\n }\n}\n\nfunction validateConfig(config: CloudinaryConfig): void {\n const missing: string[] = [];\n \n if (!config.cloud_name) missing.push('CLOUDINARY_CLOUD_NAME');\n if (!config.api_key) missing.push('CLOUDINARY_API_KEY');\n if (!config.api_secret) missing.push('CLOUDINARY_API_SECRET');\n \n if (missing.length > 0) {\n throw new CloudinaryError(`Missing Cloudinary credentials: ${missing.join(', ')}`);\n }\n}\n\nexport interface UploadResult {\n secure_url: string;\n public_id: string;\n format: string;\n width: number;\n height: number;\n bytes: number;\n resource_type: string;\n original_filename: string;\n}\n\nexport function getConfig(): CloudinaryConfig {\n return {\n cloud_name: process.env.CLOUDINARY_CLOUD_NAME || '',\n api_key: process.env.CLOUDINARY_API_KEY || '',\n api_secret: process.env.CLOUDINARY_API_SECRET || '',\n };\n}\n\nconst config = getConfig();\nvalidateConfig(config);\n\ncloudinary.config(config);\n\nexport default cloudinary;\nexport { CloudinaryError };\n","import { LlmError } from \"../llm.js\";\n\nexport interface RetryableError extends Error {\n name: string;\n retryable?: boolean;\n}\n\nconst RETRYABLE_ERROR_NAMES = new Set([\n \"TimeoutError\",\n \"NetworkError\",\n \"PlaywrightError\",\n \"LlmError\",\n \"CloudinaryError\",\n \"FetchError\",\n \"RateLimitError\",\n \"RetryableError\",\n]);\n\nexport function isRetryable(error: unknown): boolean {\n if (!error || typeof error !== \"object\") return false;\n\n const err = error as Partial<{ name: string; message: string }>;\n\n if (err.name && RETRYABLE_ERROR_NAMES.has(err.name)) {\n return true;\n }\n\n if (typeof err.message === \"string\") {\n const lowerMessage = err.message.toLowerCase();\n if (\n lowerMessage.includes(\"timeout\") ||\n lowerMessage.includes(\"network\") ||\n lowerMessage.includes(\"refused\") ||\n lowerMessage.includes(\"reset\") ||\n lowerMessage.includes(\"rate limit\") ||\n lowerMessage.includes(\"too many requests\") ||\n lowerMessage.includes(\"unavailable\")\n ) {\n return true;\n }\n }\n\n return false;\n}\n\nexport interface RetryOptions {\n maxAttempts?: number;\n delayMs?: number;\n multiplier?: number;\n onRetry?: (attempt: number, error: unknown, totalDelay: number) => void;\n}\n\nexport async function retry<T>(\n fn: () => Promise<T>,\n options?: RetryOptions\n): Promise<T> {\n const maxAttempts = options?.maxAttempts ?? 3;\n const initialDelay = options?.delayMs ?? 1000;\n const multiplier = options?.multiplier ?? 2;\n const onRetry = options?.onRetry;\n\n let lastError: unknown;\n let totalDelay = 0;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error;\n\n if (!isRetryable(error) || attempt >= maxAttempts) {\n break;\n }\n\n const waitTime = initialDelay * Math.pow(multiplier, attempt - 1);\n totalDelay += waitTime;\n\n if (onRetry) {\n onRetry(attempt, error, totalDelay);\n }\n\n await new Promise((resolve) => setTimeout(resolve, waitTime));\n }\n }\n\n throw lastError;\n}\n","import { Page } from \"playwright\";\nimport getImageDetails, { CaptchaResult, LlmError } from \"./llm.js\";\nimport cloudinary from \"./cloudinary.js\";\nimport { retry } from \"./utils/retry.js\";\n\nexport interface SolveCaptchaOptions {\n /** Max attempts for image fetch / LLM call chain. Default 3. */\n maxRetries?: number;\n /** Initial delay in ms before first retry. Default 1000. */\n retryDelayMs?: number;\n /** Multiplier applied to retryDelayMs per attempt. Default 2. */\n retryMultiplier?: number;\n /** Timeout in ms for the image fetch + upload + LLM sequence. Default 60000. */\n timeoutMs?: number;\n}\n\nexport async function solveCaptchaFromPage(\n page: Page,\n captchaSelector: string = \"img.captcha-image\",\n outputScreenshotPath?: string,\n options?: SolveCaptchaOptions\n): Promise<CaptchaResult> {\n const maxRetries = options?.maxRetries ?? 3;\n const retryDelay = options?.retryDelayMs ?? 1000;\n const retryMultiplier = options?.retryMultiplier ?? 2;\n\n const captchaImage = page.locator(captchaSelector);\n\n try {\n await captchaImage.waitFor({ state: \"visible\" });\n } catch (error) {\n throw new Error(`CAPTCHA element not found or not visible. Selector: ${captchaSelector}`);\n }\n\n if (outputScreenshotPath) {\n await captchaImage.screenshot({ path: outputScreenshotPath });\n }\n\n // ---- Extract image as base64 (retries on network errors) ----------\n let imageData: { mimeType: string; base64: string };\n\n try {\n imageData = await retry(\n async () => {\n return await captchaImage.evaluate(async (img: HTMLImageElement) => {\n const response = await fetch(img.src);\n if (!response.ok) {\n throw new Error(`Image fetch failed: ${response.status}`);\n }\n const blob = await response.blob();\n return new Promise<{ mimeType: string; base64: string }>((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => {\n const result = reader.result as string;\n resolve({\n mimeType: blob.type || \"image/png\",\n base64: result.split(\",\")[1],\n });\n };\n reader.onerror = reject;\n reader.readAsDataURL(blob);\n });\n });\n },\n {\n maxAttempts: maxRetries,\n delayMs: retryDelay,\n multiplier: retryMultiplier,\n }\n );\n } catch (error) {\n throw new Error(`Failed to extract CAPTCHA image: ${error}`);\n }\n\n // ---- Upload to Cloudinary (retries on network errors) -------------\n let uploadResult;\n\n try {\n uploadResult = await retry(\n async () => {\n return await cloudinary.uploader.upload(\n `data:${imageData.mimeType};base64,${imageData.base64}`,\n {\n folder: \"playwright-captcha-images\",\n resource_type: \"image\",\n type: \"auth\",\n }\n );\n },\n {\n maxAttempts: maxRetries,\n delayMs: retryDelay,\n multiplier: retryMultiplier,\n }\n );\n } catch (error) {\n throw new Error(`Failed to upload CAPTCHA image to Cloudinary: ${error}`);\n }\n\n const imageUrl = uploadResult.secure_url;\n\n // ---- LLM solve (retries on timeout / rate-limit) ------------------\n try {\n const result = await retry(\n async () => {\n return await getImageDetails(imageUrl);\n },\n {\n maxAttempts: maxRetries,\n delayMs: retryDelay,\n multiplier: retryMultiplier,\n }\n );\n return result;\n } catch (error) {\n throw new Error(`Failed to solve CAPTCHA using AI: ${error}`);\n }\n}\n","import { chromium, Browser, Page, BrowserContext } from \"playwright\";\nimport { solveCaptchaFromPage } from \"./captchaSolve.js\";\nimport { retry, isRetryable } from \"./utils/retry.js\";\nimport { delay, randomBetween, executeWithRandomDelay } from \"./utils/humanDelay.js\";\nimport * as fs from \"fs\";\nimport * as path from \"path\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface LoginOptions {\n // Existing\n url: string;\n loginSelector?: string;\n passwordSelector?: string;\n captchaInputSelector?: string;\n submitButtonSelector?: string;\n headless?: boolean;\n outputScreenshotPath?: string;\n screenshotDelay?: number;\n\n // New – credentials override\n email?: string;\n password?: string;\n\n // New – retry\n retryCount?: number;\n baseRetryDelay?: number;\n\n // New – human-like timing\n delayRange?: { min: number; max: number };\n\n // New – proxy\n proxy?: { server: string; username?: string; password?: string };\n\n // New – session persistence\n cookieFile?: string;\n\n // New – video recording\n videoPath?: string;\n\n // New – webhook notifications\n webhookUrl?: string;\n}\n\nexport interface LoginResult {\n success: boolean;\n url: string;\n title: string;\n page: Page;\n error?: string;\n}\n\ninterface SerializedCookie {\n name: string;\n value: string;\n domain: string;\n path: string;\n httpOnly?: boolean;\n secure?: boolean;\n sameSite?: \"Strict\" | \"Lax\" | \"None\";\n expires?: number;\n rawExpires?: string;\n}\n\nclass BrowserError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"BrowserError\";\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nasync function validateEnv(options: LoginOptions): Promise<void> {\n const email = options.email || process.env.EMAIL || \"\";\n const password = options.password || process.env.PASSWORD || \"\";\n\n if (email === \"\" || email === \"dev@example.com\") {\n console.warn(\n `Warning: EMAIL is set to default or missing. ${\n options.email ? \"Override via LoginOptions.email.\" : 'Set EMAIL env var.'\n }`\n );\n }\n if (password === \"\" || password === \"22222222\") {\n console.warn(\n `Warning: PASSWORD is set to default or missing. ${\n options.password ? \"Override via LoginOptions.password.\" : 'Set PASSWORD env var.'\n }`\n );\n }\n}\n\n/** POST JSON payload to a webhook URL with a single internal retry. */\nexport async function sendWebhook(url: string, payload: unknown): Promise<boolean> {\n try {\n await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(10_000),\n });\n return true;\n } catch {\n try {\n await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(10_000),\n });\n return true;\n } catch {\n return false;\n }\n }\n}\n\n/** Serialize Playwright browser-context cookies to a JSON file. */\nexport async function saveCookies(context: BrowserContext, filePath: string): Promise<void> {\n const cookies = await context.cookies();\n const dir = path.dirname(filePath);\n if (dir && !fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(filePath, JSON.stringify(cookies, null, 2), \"utf-8\");\n}\n\n/** Load cookies from a JSON file and add them to a browser context. */\nexport async function loadCookies(\n context: BrowserContext,\n filePath: string\n): Promise<void> {\n if (!fs.existsSync(filePath)) return;\n\n const data = fs.readFileSync(filePath, \"utf-8\");\n const cookies: SerializedCookie[] = JSON.parse(data);\n await context.addCookies(cookies as Parameters<BrowserContext[\"addCookies\"]>[0]);\n}\n\n/** Build Playwright-compatible proxy settings object. */\nexport function setupProxy(proxyConfig?: LoginOptions[\"proxy\"]) {\n if (!proxyConfig) return undefined;\n return {\n server: proxyConfig.server,\n username: proxyConfig.username,\n password: proxyConfig.password,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Core automation\n// ---------------------------------------------------------------------------\n\nexport async function automateLogin(options: LoginOptions): Promise<LoginResult> {\n let browser: Browser | null = null;\n let context: BrowserContext | null = null;\n let page: Page | null = null;\n\n try {\n await validateEnv(options);\n\n const retryCount = options.retryCount ?? 2;\n const baseRetryDelay = options.baseRetryDelay ?? 1000;\n const delayRange = options.delayRange ?? { min: 500, max: 1500 };\n\n // ---- Proxy --------------------------------------------------------\n const proxySettings = setupProxy(options.proxy);\n\n // ---- Launch browser -----------------------------------------------\n const launchOpts: Record<string, unknown> = {\n headless: options.headless ?? false,\n };\n if (proxySettings) {\n launchOpts.proxy = proxySettings;\n }\n\n browser = await chromium.launch(launchOpts);\n\n // ---- Context options ----------------------------------------------\n const contextOptions: Record<string, unknown> = {};\n if (proxySettings && !launchOpts.proxy) {\n contextOptions.proxy = proxySettings;\n }\n\n // Cookie persistence via storageState\n if (options.cookieFile) {\n contextOptions.storageState = options.cookieFile;\n }\n\n // Video recording via context recordVideo option\n if (options.videoPath) {\n const videoDir = path.isAbsolute(options.videoPath)\n ? path.dirname(options.videoPath)\n : path.join(process.cwd(), path.dirname(options.videoPath));\n contextOptions.recordVideo = {\n dir: videoDir,\n size: { width: 1280, height: 720 },\n };\n }\n\n context = await browser.newContext(contextOptions);\n page = await context.newPage();\n\n // ---- Selectors ----------------------------------------------------\n const loginSelector = options.loginSelector || \"#login\";\n const passwordSelector = options.passwordSelector || \"#password\";\n const captchaSelector = \"img.captcha-image\";\n const captchaInputSelector = options.captchaInputSelector || \"#captcha\";\n const submitButtonSelector = options.submitButtonSelector || \"button[type='submit']\";\n\n // ---- Single login attempt -----------------------------------------\n const attemptLogin = async (): Promise<{\n success: true;\n url: string;\n title: string;\n page: Page;\n }> => {\n await executeWithRandomDelay(\n async () =>\n page!.goto(options.url, { waitUntil: \"domcontentloaded\" }),\n ...Object.values(delayRange)\n );\n\n const emailVal = options.email || process.env.EMAIL || \"\";\n const passwordVal = options.password || process.env.PASSWORD || \"\";\n\n await executeWithRandomDelay(\n async () => page!.locator(loginSelector).fill(emailVal),\n ...Object.values(delayRange)\n );\n\n await executeWithRandomDelay(\n async () => page!.locator(passwordSelector).fill(passwordVal),\n ...Object.values(delayRange)\n );\n\n const captchaResult = await solveCaptchaFromPage(\n page!,\n captchaSelector,\n options.outputScreenshotPath ? undefined : undefined\n );\n\n await executeWithRandomDelay(\n async () => page!.locator(captchaInputSelector).fill(captchaResult.text),\n ...Object.values(delayRange)\n );\n\n await executeWithRandomDelay(\n async () => page!.locator(submitButtonSelector).click(),\n ...Object.values(delayRange)\n );\n\n try {\n await page!.waitForLoadState(\"domcontentloaded\", { timeout: 15000 });\n\n if (options.screenshotDelay) {\n await page!.waitForTimeout(options.screenshotDelay);\n }\n\n if (options.outputScreenshotPath) {\n await page!.screenshot({ path: options.outputScreenshotPath, fullPage: true });\n }\n } catch {\n console.warn(\"Warning: Could not wait for post-login state\");\n }\n\n return {\n success: true,\n url: page!.url(),\n title: await page!.title(),\n page: page!,\n };\n };\n\n // ---- Retry loop ---------------------------------------------------\n let result: Awaited<ReturnType<typeof attemptLogin>>;\n\n try {\n result = await retry(attemptLogin, {\n maxAttempts: retryCount + 1, // +1 for the initial attempt\n delayMs: baseRetryDelay,\n multiplier: 2,\n onRetry: (attempt, error, totalDelay) => {\n console.warn(\n `[retry] Attempt ${attempt} failed: ${error instanceof Error ? error.message : error}. Total backoff: ${totalDelay}ms`\n );\n },\n });\n } catch (error) {\n // ---- Failure path -----------------------------------------------\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n let pageTitle = \"\";\n if (page) {\n try {\n pageTitle = await page.title();\n } catch {\n pageTitle = \"failed to get title\";\n }\n }\n\n // Save cookies before leaving (partial session might still be useful)\n if (options.cookieFile && context) {\n try {\n await saveCookies(context, options.cookieFile);\n } catch {\n /* ignore */\n }\n }\n\n // Send failure webhook\n if (options.webhookUrl) {\n await sendWebhook(options.webhookUrl, {\n content: \"🔴 **Login Failed**\",\n embeds: [\n {\n title: \"Login Automation Failed\",\n fields: [\n { name: \"URL\", value: options.url, inline: true },\n {\n name: \"Error\",\n value:\n error instanceof Error ? error.message : String(error),\n inline: false,\n },\n ],\n color: 16734223,\n },\n ],\n });\n }\n\n return {\n success: false,\n url: page?.url() || \"\",\n title: pageTitle,\n page: page!,\n error: errorMessage,\n };\n }\n\n // ---- Success path -------------------------------------------------\n\n // Save cookies after successful login\n if (options.cookieFile && context) {\n try {\n await saveCookies(context, options.cookieFile);\n } catch (err) {\n console.warn(\"Warning: Could not save cookies:\", err);\n }\n }\n\n // Handle video recording — use the context-level recordVideo approach\n if (options.videoPath && page) {\n try {\n const videoObj = page.video();\n if (videoObj) {\n const outputPath = path.isAbsolute(options.videoPath!)\n ? options.videoPath!\n : path.join(process.cwd(), options.videoPath!);\n\n // Wait briefly then save\n await delay(500);\n await videoObj.saveAs(outputPath);\n console.log(`Video saved to ${outputPath}`);\n }\n } catch (err) {\n console.warn(\"Warning: Could not save video recording:\", err);\n }\n }\n\n // Send success webhook\n if (options.webhookUrl) {\n await sendWebhook(options.webhookUrl, {\n content: \"✅ **Login Successful**\",\n embeds: [\n {\n title: \"Login Automation Succeeded\",\n fields: [\n { name: \"URL\", value: options.url, inline: true },\n { name: \"Final URL\", value: result.url, inline: true },\n { name: \"Title\", value: result.title, inline: false },\n ],\n color: 5763719,\n },\n ],\n });\n }\n\n return {\n success: true,\n url: result.url,\n title: result.title,\n page: result.page,\n };\n } catch (error) {\n // Top-level catch for unexpected errors\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n let pageTitle = \"\";\n if (page) {\n try {\n pageTitle = await page.title();\n } catch {\n pageTitle = \"failed to get title\";\n }\n }\n\n // Save cookies on unexpected failure too\n if (options.cookieFile && context) {\n try {\n await saveCookies(context, options.cookieFile);\n } catch {\n /* ignore */\n }\n }\n\n // Send failure webhook\n if (options.webhookUrl) {\n await sendWebhook(options.webhookUrl, {\n content: \"🔴 **Login Error**\",\n embeds: [\n {\n title: \"Unexpected Login Error\",\n fields: [\n { name: \"URL\", value: options.url, inline: true },\n {\n name: \"Error\",\n value: error instanceof Error ? error.message : String(error),\n inline: false,\n },\n ],\n color: 16734223,\n },\n ],\n });\n }\n\n return {\n success: false,\n url: page?.url() || \"\",\n title: pageTitle,\n page: page!,\n error: errorMessage,\n };\n } finally {\n if (browser && !options.headless) {\n await browser.close().catch(() => {});\n }\n }\n}\n","/**\n * Returns a random integer between min and max (inclusive).\n */\nexport function randomBetween(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\n/**\n * Delays execution for ms milliseconds with ±20% jitter.\n */\nexport function delay(ms: number): Promise<void> {\n const jittered = ms + Math.floor((Math.random() * 0.4 - 0.2) * ms);\n const actualDelay = Math.max(0, jittered);\n return new Promise((resolve) => setTimeout(resolve, actualDelay));\n}\n\n/**\n * Executes an async action and applies a random delay after it completes.\n * The delay is randomly chosen between minDelay and maxDelay (if provided),\n * defaulting to 500-1500ms.\n */\nexport async function executeWithRandomDelay<T>(\n action: () => Promise<T>,\n minDelay: number = 500,\n maxDelay: number = 1500\n): Promise<T> {\n const result = await action();\n const delayMs = randomBetween(minDelay, maxDelay);\n await delay(delayMs);\n return result;\n}\n"],"mappings":"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","names":["import_config","config","cloudinary"]}
package/dist/index.js CHANGED
@@ -88,8 +88,62 @@ validateConfig(config);
88
88
  cloudinary.config(config);
89
89
  var cloudinary_default = cloudinary;
90
90
 
91
+ // src/utils/retry.ts
92
+ var RETRYABLE_ERROR_NAMES = /* @__PURE__ */ new Set([
93
+ "TimeoutError",
94
+ "NetworkError",
95
+ "PlaywrightError",
96
+ "LlmError",
97
+ "CloudinaryError",
98
+ "FetchError",
99
+ "RateLimitError",
100
+ "RetryableError"
101
+ ]);
102
+ function isRetryable(error) {
103
+ if (!error || typeof error !== "object") return false;
104
+ const err = error;
105
+ if (err.name && RETRYABLE_ERROR_NAMES.has(err.name)) {
106
+ return true;
107
+ }
108
+ if (typeof err.message === "string") {
109
+ const lowerMessage = err.message.toLowerCase();
110
+ if (lowerMessage.includes("timeout") || lowerMessage.includes("network") || lowerMessage.includes("refused") || lowerMessage.includes("reset") || lowerMessage.includes("rate limit") || lowerMessage.includes("too many requests") || lowerMessage.includes("unavailable")) {
111
+ return true;
112
+ }
113
+ }
114
+ return false;
115
+ }
116
+ async function retry(fn, options) {
117
+ const maxAttempts = options?.maxAttempts ?? 3;
118
+ const initialDelay = options?.delayMs ?? 1e3;
119
+ const multiplier = options?.multiplier ?? 2;
120
+ const onRetry = options?.onRetry;
121
+ let lastError;
122
+ let totalDelay = 0;
123
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
124
+ try {
125
+ return await fn();
126
+ } catch (error) {
127
+ lastError = error;
128
+ if (!isRetryable(error) || attempt >= maxAttempts) {
129
+ break;
130
+ }
131
+ const waitTime = initialDelay * Math.pow(multiplier, attempt - 1);
132
+ totalDelay += waitTime;
133
+ if (onRetry) {
134
+ onRetry(attempt, error, totalDelay);
135
+ }
136
+ await new Promise((resolve) => setTimeout(resolve, waitTime));
137
+ }
138
+ }
139
+ throw lastError;
140
+ }
141
+
91
142
  // src/captchaSolve.ts
92
- async function solveCaptchaFromPage(page, captchaSelector = "img.captcha-image", outputScreenshotPath) {
143
+ async function solveCaptchaFromPage(page, captchaSelector = "img.captcha-image", outputScreenshotPath, options) {
144
+ const maxRetries = options?.maxRetries ?? 3;
145
+ const retryDelay = options?.retryDelayMs ?? 1e3;
146
+ const retryMultiplier = options?.retryMultiplier ?? 2;
93
147
  const captchaImage = page.locator(captchaSelector);
94
148
  try {
95
149
  await captchaImage.waitFor({ state: "visible" });
@@ -101,37 +155,54 @@ async function solveCaptchaFromPage(page, captchaSelector = "img.captcha-image",
101
155
  }
102
156
  let imageData;
103
157
  try {
104
- imageData = await captchaImage.evaluate(async (img) => {
105
- const response = await fetch(img.src);
106
- if (!response.ok) {
107
- throw new Error(`Image fetch failed: ${response.status}`);
108
- }
109
- const blob = await response.blob();
110
- return new Promise((resolve, reject) => {
111
- const reader = new FileReader();
112
- reader.onload = () => {
113
- const result = reader.result;
114
- const base64 = result.split(",")[1];
115
- resolve({
116
- mimeType: blob.type || "image/png",
117
- base64
158
+ imageData = await retry(
159
+ async () => {
160
+ return await captchaImage.evaluate(async (img) => {
161
+ const response = await fetch(img.src);
162
+ if (!response.ok) {
163
+ throw new Error(`Image fetch failed: ${response.status}`);
164
+ }
165
+ const blob = await response.blob();
166
+ return new Promise((resolve, reject) => {
167
+ const reader = new FileReader();
168
+ reader.onload = () => {
169
+ const result = reader.result;
170
+ resolve({
171
+ mimeType: blob.type || "image/png",
172
+ base64: result.split(",")[1]
173
+ });
174
+ };
175
+ reader.onerror = reject;
176
+ reader.readAsDataURL(blob);
118
177
  });
119
- };
120
- reader.onerror = reject;
121
- reader.readAsDataURL(blob);
122
- });
123
- });
178
+ });
179
+ },
180
+ {
181
+ maxAttempts: maxRetries,
182
+ delayMs: retryDelay,
183
+ multiplier: retryMultiplier
184
+ }
185
+ );
124
186
  } catch (error) {
125
187
  throw new Error(`Failed to extract CAPTCHA image: ${error}`);
126
188
  }
127
189
  let uploadResult;
128
190
  try {
129
- uploadResult = await cloudinary_default.uploader.upload(
130
- `data:${imageData.mimeType};base64,${imageData.base64}`,
191
+ uploadResult = await retry(
192
+ async () => {
193
+ return await cloudinary_default.uploader.upload(
194
+ `data:${imageData.mimeType};base64,${imageData.base64}`,
195
+ {
196
+ folder: "playwright-captcha-images",
197
+ resource_type: "image",
198
+ type: "auth"
199
+ }
200
+ );
201
+ },
131
202
  {
132
- folder: "playwright-captcha-images",
133
- resource_type: "image",
134
- type: "auth"
203
+ maxAttempts: maxRetries,
204
+ delayMs: retryDelay,
205
+ multiplier: retryMultiplier
135
206
  }
136
207
  );
137
208
  } catch (error) {
@@ -139,7 +210,16 @@ async function solveCaptchaFromPage(page, captchaSelector = "img.captcha-image",
139
210
  }
140
211
  const imageUrl = uploadResult.secure_url;
141
212
  try {
142
- const result = await llm_default(imageUrl);
213
+ const result = await retry(
214
+ async () => {
215
+ return await llm_default(imageUrl);
216
+ },
217
+ {
218
+ maxAttempts: maxRetries,
219
+ delayMs: retryDelay,
220
+ multiplier: retryMultiplier
221
+ }
222
+ );
143
223
  return result;
144
224
  } catch (error) {
145
225
  throw new Error(`Failed to solve CAPTCHA using AI: ${error}`);
@@ -148,62 +228,266 @@ async function solveCaptchaFromPage(page, captchaSelector = "img.captcha-image",
148
228
 
149
229
  // src/login.ts
150
230
  import { chromium } from "playwright";
151
- async function validateEnv() {
152
- const missing = [];
153
- if (!process.env.EMAIL || process.env.EMAIL === "dev@example.com") {
154
- missing.push("EMAIL - Set to actual email address");
231
+
232
+ // src/utils/humanDelay.ts
233
+ function randomBetween(min, max) {
234
+ return Math.floor(Math.random() * (max - min + 1)) + min;
235
+ }
236
+ function delay(ms) {
237
+ const jittered = ms + Math.floor((Math.random() * 0.4 - 0.2) * ms);
238
+ const actualDelay = Math.max(0, jittered);
239
+ return new Promise((resolve) => setTimeout(resolve, actualDelay));
240
+ }
241
+ async function executeWithRandomDelay(action, minDelay = 500, maxDelay = 1500) {
242
+ const result = await action();
243
+ const delayMs = randomBetween(minDelay, maxDelay);
244
+ await delay(delayMs);
245
+ return result;
246
+ }
247
+
248
+ // src/login.ts
249
+ import * as fs from "fs";
250
+ import * as path from "path";
251
+ async function validateEnv(options) {
252
+ const email = options.email || process.env.EMAIL || "";
253
+ const password = options.password || process.env.PASSWORD || "";
254
+ if (email === "" || email === "dev@example.com") {
255
+ console.warn(
256
+ `Warning: EMAIL is set to default or missing. ${options.email ? "Override via LoginOptions.email." : "Set EMAIL env var."}`
257
+ );
155
258
  }
156
- if (!process.env.PASSWORD || process.env.PASSWORD === "22222222") {
157
- missing.push("PASSWORD - Set to actual password");
259
+ if (password === "" || password === "22222222") {
260
+ console.warn(
261
+ `Warning: PASSWORD is set to default or missing. ${options.password ? "Override via LoginOptions.password." : "Set PASSWORD env var."}`
262
+ );
158
263
  }
159
- if (missing.length > 0) {
160
- console.warn("Warning: The following environment variables are set to defaults or missing:");
161
- missing.forEach((m) => console.warn(` - ${m}`));
162
- console.warn("Please update your .env file with real credentials.");
264
+ }
265
+ async function sendWebhook(url, payload) {
266
+ try {
267
+ await fetch(url, {
268
+ method: "POST",
269
+ headers: { "Content-Type": "application/json" },
270
+ body: JSON.stringify(payload),
271
+ signal: AbortSignal.timeout(1e4)
272
+ });
273
+ return true;
274
+ } catch {
275
+ try {
276
+ await fetch(url, {
277
+ method: "POST",
278
+ headers: { "Content-Type": "application/json" },
279
+ body: JSON.stringify(payload),
280
+ signal: AbortSignal.timeout(1e4)
281
+ });
282
+ return true;
283
+ } catch {
284
+ return false;
285
+ }
163
286
  }
164
287
  }
288
+ async function saveCookies(context, filePath) {
289
+ const cookies = await context.cookies();
290
+ const dir = path.dirname(filePath);
291
+ if (dir && !fs.existsSync(dir)) {
292
+ fs.mkdirSync(dir, { recursive: true });
293
+ }
294
+ fs.writeFileSync(filePath, JSON.stringify(cookies, null, 2), "utf-8");
295
+ }
296
+ async function loadCookies(context, filePath) {
297
+ if (!fs.existsSync(filePath)) return;
298
+ const data = fs.readFileSync(filePath, "utf-8");
299
+ const cookies = JSON.parse(data);
300
+ await context.addCookies(cookies);
301
+ }
302
+ function setupProxy(proxyConfig) {
303
+ if (!proxyConfig) return void 0;
304
+ return {
305
+ server: proxyConfig.server,
306
+ username: proxyConfig.username,
307
+ password: proxyConfig.password
308
+ };
309
+ }
165
310
  async function automateLogin(options) {
166
311
  let browser = null;
167
312
  let context = null;
168
313
  let page = null;
169
314
  try {
170
- await validateEnv();
171
- browser = await chromium.launch({
315
+ await validateEnv(options);
316
+ const retryCount = options.retryCount ?? 2;
317
+ const baseRetryDelay = options.baseRetryDelay ?? 1e3;
318
+ const delayRange = options.delayRange ?? { min: 500, max: 1500 };
319
+ const proxySettings = setupProxy(options.proxy);
320
+ const launchOpts = {
172
321
  headless: options.headless ?? false
173
- });
174
- context = await browser.newContext();
322
+ };
323
+ if (proxySettings) {
324
+ launchOpts.proxy = proxySettings;
325
+ }
326
+ browser = await chromium.launch(launchOpts);
327
+ const contextOptions = {};
328
+ if (proxySettings && !launchOpts.proxy) {
329
+ contextOptions.proxy = proxySettings;
330
+ }
331
+ if (options.cookieFile) {
332
+ contextOptions.storageState = options.cookieFile;
333
+ }
334
+ if (options.videoPath) {
335
+ const videoDir = path.isAbsolute(options.videoPath) ? path.dirname(options.videoPath) : path.join(process.cwd(), path.dirname(options.videoPath));
336
+ contextOptions.recordVideo = {
337
+ dir: videoDir,
338
+ size: { width: 1280, height: 720 }
339
+ };
340
+ }
341
+ context = await browser.newContext(contextOptions);
175
342
  page = await context.newPage();
176
- await page.goto(options.url, { waitUntil: "domcontentloaded" });
177
343
  const loginSelector = options.loginSelector || "#login";
178
344
  const passwordSelector = options.passwordSelector || "#password";
179
345
  const captchaSelector = "img.captcha-image";
180
346
  const captchaInputSelector = options.captchaInputSelector || "#captcha";
181
347
  const submitButtonSelector = options.submitButtonSelector || "button[type='submit']";
182
- await page.locator(loginSelector).fill(process.env.EMAIL || "");
183
- await page.locator(passwordSelector).fill(process.env.PASSWORD || "");
184
- const captchaResult = await solveCaptchaFromPage(
185
- page,
186
- captchaSelector,
187
- options.outputScreenshotPath ? void 0 : void 0
188
- );
189
- await page.locator(captchaInputSelector).fill(captchaResult.text);
190
- await page.locator(submitButtonSelector).click();
348
+ const attemptLogin = async () => {
349
+ await executeWithRandomDelay(
350
+ async () => page.goto(options.url, { waitUntil: "domcontentloaded" }),
351
+ ...Object.values(delayRange)
352
+ );
353
+ const emailVal = options.email || process.env.EMAIL || "";
354
+ const passwordVal = options.password || process.env.PASSWORD || "";
355
+ await executeWithRandomDelay(
356
+ async () => page.locator(loginSelector).fill(emailVal),
357
+ ...Object.values(delayRange)
358
+ );
359
+ await executeWithRandomDelay(
360
+ async () => page.locator(passwordSelector).fill(passwordVal),
361
+ ...Object.values(delayRange)
362
+ );
363
+ const captchaResult = await solveCaptchaFromPage(
364
+ page,
365
+ captchaSelector,
366
+ options.outputScreenshotPath ? void 0 : void 0
367
+ );
368
+ await executeWithRandomDelay(
369
+ async () => page.locator(captchaInputSelector).fill(captchaResult.text),
370
+ ...Object.values(delayRange)
371
+ );
372
+ await executeWithRandomDelay(
373
+ async () => page.locator(submitButtonSelector).click(),
374
+ ...Object.values(delayRange)
375
+ );
376
+ try {
377
+ await page.waitForLoadState("domcontentloaded", { timeout: 15e3 });
378
+ if (options.screenshotDelay) {
379
+ await page.waitForTimeout(options.screenshotDelay);
380
+ }
381
+ if (options.outputScreenshotPath) {
382
+ await page.screenshot({ path: options.outputScreenshotPath, fullPage: true });
383
+ }
384
+ } catch {
385
+ console.warn("Warning: Could not wait for post-login state");
386
+ }
387
+ return {
388
+ success: true,
389
+ url: page.url(),
390
+ title: await page.title(),
391
+ page
392
+ };
393
+ };
394
+ let result;
191
395
  try {
192
- await page.waitForLoadState("domcontentloaded", { timeout: 15e3 });
193
- if (options.screenshotDelay) {
194
- await page.waitForTimeout(options.screenshotDelay);
396
+ result = await retry(attemptLogin, {
397
+ maxAttempts: retryCount + 1,
398
+ // +1 for the initial attempt
399
+ delayMs: baseRetryDelay,
400
+ multiplier: 2,
401
+ onRetry: (attempt, error, totalDelay) => {
402
+ console.warn(
403
+ `[retry] Attempt ${attempt} failed: ${error instanceof Error ? error.message : error}. Total backoff: ${totalDelay}ms`
404
+ );
405
+ }
406
+ });
407
+ } catch (error) {
408
+ const errorMessage = error instanceof Error ? error.message : String(error);
409
+ let pageTitle = "";
410
+ if (page) {
411
+ try {
412
+ pageTitle = await page.title();
413
+ } catch {
414
+ pageTitle = "failed to get title";
415
+ }
195
416
  }
196
- if (options.outputScreenshotPath) {
197
- await page.screenshot({ path: options.outputScreenshotPath, fullPage: true });
417
+ if (options.cookieFile && context) {
418
+ try {
419
+ await saveCookies(context, options.cookieFile);
420
+ } catch {
421
+ }
422
+ }
423
+ if (options.webhookUrl) {
424
+ await sendWebhook(options.webhookUrl, {
425
+ content: "\u{1F534} **Login Failed**",
426
+ embeds: [
427
+ {
428
+ title: "Login Automation Failed",
429
+ fields: [
430
+ { name: "URL", value: options.url, inline: true },
431
+ {
432
+ name: "Error",
433
+ value: error instanceof Error ? error.message : String(error),
434
+ inline: false
435
+ }
436
+ ],
437
+ color: 16734223
438
+ }
439
+ ]
440
+ });
441
+ }
442
+ return {
443
+ success: false,
444
+ url: page?.url() || "",
445
+ title: pageTitle,
446
+ page,
447
+ error: errorMessage
448
+ };
449
+ }
450
+ if (options.cookieFile && context) {
451
+ try {
452
+ await saveCookies(context, options.cookieFile);
453
+ } catch (err) {
454
+ console.warn("Warning: Could not save cookies:", err);
198
455
  }
199
- } catch (error) {
200
- console.warn("Warning: Could not wait for post-login state:", error);
456
+ }
457
+ if (options.videoPath && page) {
458
+ try {
459
+ const videoObj = page.video();
460
+ if (videoObj) {
461
+ const outputPath = path.isAbsolute(options.videoPath) ? options.videoPath : path.join(process.cwd(), options.videoPath);
462
+ await delay(500);
463
+ await videoObj.saveAs(outputPath);
464
+ console.log(`Video saved to ${outputPath}`);
465
+ }
466
+ } catch (err) {
467
+ console.warn("Warning: Could not save video recording:", err);
468
+ }
469
+ }
470
+ if (options.webhookUrl) {
471
+ await sendWebhook(options.webhookUrl, {
472
+ content: "\u2705 **Login Successful**",
473
+ embeds: [
474
+ {
475
+ title: "Login Automation Succeeded",
476
+ fields: [
477
+ { name: "URL", value: options.url, inline: true },
478
+ { name: "Final URL", value: result.url, inline: true },
479
+ { name: "Title", value: result.title, inline: false }
480
+ ],
481
+ color: 5763719
482
+ }
483
+ ]
484
+ });
201
485
  }
202
486
  return {
203
487
  success: true,
204
- url: page.url(),
205
- title: await page.title(),
206
- page
488
+ url: result.url,
489
+ title: result.title,
490
+ page: result.page
207
491
  };
208
492
  } catch (error) {
209
493
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -215,6 +499,31 @@ async function automateLogin(options) {
215
499
  pageTitle = "failed to get title";
216
500
  }
217
501
  }
502
+ if (options.cookieFile && context) {
503
+ try {
504
+ await saveCookies(context, options.cookieFile);
505
+ } catch {
506
+ }
507
+ }
508
+ if (options.webhookUrl) {
509
+ await sendWebhook(options.webhookUrl, {
510
+ content: "\u{1F534} **Login Error**",
511
+ embeds: [
512
+ {
513
+ title: "Unexpected Login Error",
514
+ fields: [
515
+ { name: "URL", value: options.url, inline: true },
516
+ {
517
+ name: "Error",
518
+ value: error instanceof Error ? error.message : String(error),
519
+ inline: false
520
+ }
521
+ ],
522
+ color: 16734223
523
+ }
524
+ ]
525
+ });
526
+ }
218
527
  return {
219
528
  success: false,
220
529
  url: page?.url() || "",
@@ -233,11 +542,29 @@ async function automateLogin(options) {
233
542
  // src/index.ts
234
543
  var index_default = {
235
544
  solveCaptchaFromPage,
236
- automateLogin
545
+ automateLogin,
546
+ sendWebhook,
547
+ saveCookies,
548
+ loadCookies,
549
+ setupProxy,
550
+ randomBetween,
551
+ delay,
552
+ executeWithRandomDelay,
553
+ retry,
554
+ isRetryable
237
555
  };
238
556
  export {
239
557
  automateLogin,
240
558
  index_default as default,
559
+ delay,
560
+ executeWithRandomDelay,
561
+ isRetryable,
562
+ loadCookies,
563
+ randomBetween,
564
+ retry,
565
+ saveCookies,
566
+ sendWebhook,
567
+ setupProxy,
241
568
  solveCaptchaFromPage
242
569
  };
243
570
  //# sourceMappingURL=index.js.map