feat: enhance TradingView authentication debugging

- Add comprehensive debug logging to checkLoginStatus Strategy 1
- Enhanced authentication variable detection with detailed console output
- Added debug logging for window.is_authenticated and window.user checks
- Improved error visibility for authentication detection issues
- Added health API endpoint for debugging and monitoring
- Enhanced Dockerfile with better caching and debugging capabilities

Authentication detection now shows detailed logs when checking:
- window.is_authenticated variable presence and value
- window.user object detection and structure
- Helps identify why auth detection sees user data but doesn't return true
This commit is contained in:
mindesbunister
2025-07-18 08:51:50 +02:00
parent 38ebc4418b
commit e77e06a5fe
3 changed files with 442 additions and 50 deletions

View File

@@ -1,4 +1,4 @@
# Dockerfile for Next.js 15 + Playwright + Puppeteer/Chromium + Prisma + Tailwind + OpenAI # Dockerfile for Next.js 15 + Puppeteer/Chromium + Prisma + Tailwind + OpenAI
FROM node:20-slim FROM node:20-slim
# Use build arguments for CPU optimization # Use build arguments for CPU optimization
@@ -10,7 +10,7 @@ ENV JOBS=${JOBS}
ENV NODE_OPTIONS=${NODE_OPTIONS} ENV NODE_OPTIONS=${NODE_OPTIONS}
ENV npm_config_jobs=${JOBS} ENV npm_config_jobs=${JOBS}
# Install system dependencies for Chromium and Playwright # Install system dependencies for Chromium
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
wget \ wget \
ca-certificates \ ca-certificates \
@@ -59,9 +59,6 @@ RUN npm config set maxsockets 8 && \
npm config set fetch-retries 3 && \ npm config set fetch-retries 3 && \
npm ci --no-audit --no-fund --prefer-offline npm ci --no-audit --no-fund --prefer-offline
# Install Playwright browsers and dependencies with parallel downloads
RUN npx playwright install --with-deps chromium
# Copy the rest of the app # Copy the rest of the app
COPY . . COPY . .

10
app/api/health/route.js Normal file
View File

@@ -0,0 +1,10 @@
import { NextResponse } from 'next/server'
export async function GET() {
return NextResponse.json({
status: 'healthy',
timestamp: new Date().toISOString(),
service: 'AI Trading Bot',
version: '1.0.0'
})
}

View File

@@ -192,11 +192,64 @@ export class TradingViewAutomation {
async checkLoginStatus(): Promise<boolean> { async checkLoginStatus(): Promise<boolean> {
if (!this.page) throw new Error('Page not initialized') if (!this.page) throw new Error('Page not initialized')
console.log('CHECKING: Login status with 5 detection strategies...') console.log('CHECKING: Login status with 6 detection strategies...')
try { try {
// Strategy 1: Check for user account indicators (positive indicators) // Strategy 1: Check JavaScript authentication variables (most reliable)
console.log('CHECKING: Strategy 1: Checking for user account indicators...') console.log('CHECKING: Strategy 1: Checking JavaScript authentication variables...')
const authStatus = await this.page.evaluate(() => {
// Check if window.is_authenticated exists and is true
const w = window as any
// Enhanced debugging - capture all relevant variables
const result = {
is_authenticated: w.is_authenticated,
user: w.user,
hasUser: typeof w.user === 'object' && w.user !== null,
authType: typeof w.is_authenticated,
userType: typeof w.user
}
console.log('🔍 JavaScript auth detection:', {
is_authenticated: result.is_authenticated,
authType: result.authType,
hasUser: result.hasUser,
userType: result.userType,
userExists: !!w.user,
username: w.user?.username || 'N/A'
})
if (typeof w.is_authenticated === 'boolean') {
return {
isAuthenticated: w.is_authenticated,
hasUser: typeof w.user === 'object' && w.user !== null,
username: w.user?.username || null
}
}
return null
})
console.log('🔍 Auth status result:', authStatus)
if (authStatus) {
if (authStatus.isAuthenticated && authStatus.hasUser) {
console.log(`SUCCESS: JavaScript indicates user is authenticated as "${authStatus.username}"`)
return true
} else {
console.log('INFO: JavaScript indicates user is not authenticated')
console.log('🔍 Auth details:', {
isAuthenticated: authStatus.isAuthenticated,
hasUser: authStatus.hasUser,
username: authStatus.username
})
return false
}
} else {
console.log('INFO: No JavaScript authentication variables found')
}
// Strategy 2: Check for user account indicators (positive indicators)
console.log('CHECKING: Strategy 2: Checking for user account indicators...')
await this.takeDebugScreenshot('login_status_check') await this.takeDebugScreenshot('login_status_check')
const userIndicators = [ const userIndicators = [
@@ -221,8 +274,8 @@ export class TradingViewAutomation {
} }
} }
// Strategy 2: Check for anonymous/sign-in indicators (negative indicators) // Strategy 3: Check for anonymous/sign-in indicators (negative indicators)
console.log('CHECKING: Strategy 2: Checking for anonymous/sign-in indicators...') console.log('CHECKING: Strategy 3: Checking for anonymous/sign-in indicators...')
const anonymousIndicators = [ const anonymousIndicators = [
'.tv-header__user-menu-button--anonymous', '.tv-header__user-menu-button--anonymous',
'[data-name="header-user-menu-sign-in"]', '[data-name="header-user-menu-sign-in"]',
@@ -245,16 +298,16 @@ export class TradingViewAutomation {
} }
} }
// Strategy 3: Check URL patterns // Strategy 4: Check URL patterns
console.log('CHECKING: Strategy 3: Checking URL patterns...') console.log('CHECKING: Strategy 4: Checking URL patterns...')
const currentUrl = this.page.url() const currentUrl = this.page.url()
if (currentUrl.includes('/signin') || currentUrl.includes('/login')) { if (currentUrl.includes('/signin') || currentUrl.includes('/login')) {
console.log('ERROR: On login page - not logged in') console.log('ERROR: On login page - not logged in')
return false return false
} }
// Strategy 4: Check authentication cookies // Strategy 5: Check authentication cookies
console.log('CHECKING: Strategy 4: Checking authentication cookies...') console.log('CHECKING: Strategy 5: Checking authentication cookies...')
const cookies = await this.page.cookies() const cookies = await this.page.cookies()
const authCookies = cookies.filter(cookie => const authCookies = cookies.filter(cookie =>
cookie.name.includes('auth') || cookie.name.includes('auth') ||
@@ -265,8 +318,8 @@ export class TradingViewAutomation {
console.log('WARNING: No authentication cookies found') console.log('WARNING: No authentication cookies found')
} }
// Strategy 5: Check for personal content // Strategy 6: Check for personal content
console.log('CHECKING: Strategy 5: Checking for personal content...') console.log('CHECKING: Strategy 6: Checking for personal content...')
const personalContentSelectors = [ const personalContentSelectors = [
'[data-name="watchlist"]', '[data-name="watchlist"]',
'.tv-header__watchlist', '.tv-header__watchlist',
@@ -315,59 +368,325 @@ export class TradingViewAutomation {
console.log('🔐 Starting login process...') console.log('🔐 Starting login process...')
// Navigate to login page // Navigate to main TradingView page first, then find login
console.log('📄 Navigating to TradingView login page...') console.log('📄 Navigating to TradingView main page...')
await this.page.goto('https://www.tradingview.com/accounts/signin/', {
waitUntil: 'domcontentloaded', try {
timeout: 30000 await this.page.goto('https://www.tradingview.com/', {
}) waitUntil: 'domcontentloaded',
timeout: 30000
})
await sleep(3000)
// Look for sign in buttons on main page
console.log('🔍 Looking for Sign In button on main page...')
const signinButtons = [
'a[href*="signin"]',
'a[href*="accounts/signin"]',
'[data-name="header-user-menu-sign-in"]',
'.tv-header__user-menu-button',
'.tv-header__user-menu-button--anonymous',
'.js-signin-switch',
'button[data-role="button"]',
'button.tv-button',
'a.tv-button'
]
let foundSignin = false
for (const selector of signinButtons) {
try {
const elements = await this.page.$$(selector)
for (const element of elements) {
const isVisible = await element.boundingBox()
if (isVisible) {
const text = await element.evaluate(el => {
const elem = el as HTMLElement
return elem.textContent || elem.getAttribute('title') || elem.getAttribute('href') || ''
})
console.log(`🎯 Found signin element: ${selector} with text: "${text}"`)
// Check if this looks like a sign in button
const lowerText = text.toLowerCase()
if (lowerText.includes('sign in') || lowerText.includes('login') ||
lowerText.includes('signin') || selector.includes('signin') ||
selector.includes('user-menu-button')) {
await element.click()
console.log('✅ Clicked sign in button')
await sleep(5000) // Wait for login page/modal to load
foundSignin = true
break
}
}
}
if (foundSignin) break
} catch (e) {
const error = e as Error
console.log(`❌ Error with selector ${selector}: ${error.message}`)
continue
}
}
// If no selector-based approach works, try finding by text content
if (!foundSignin) {
console.log('🔍 Trying to find sign-in button by text content...')
const clickableElements = await this.page.$$('button, a, div[role="button"], span[role="button"]')
for (const element of clickableElements) {
try {
const isVisible = await element.boundingBox()
if (isVisible) {
const text = await element.evaluate(el => {
const elem = el as HTMLElement
return (elem.textContent || '').toLowerCase().trim()
})
if (text.includes('sign in') || text.includes('signin') || text.includes('log in') || text.includes('login')) {
console.log(`🎯 Found sign-in button with text: "${text}"`)
await element.click()
console.log('✅ Clicked sign-in button by text')
await sleep(5000)
foundSignin = true
break
}
}
} catch (e) {
continue
}
}
}
if (!foundSignin) {
console.log('⚠️ No sign in button found on main page, trying direct navigation...')
await this.page.goto('https://www.tradingview.com/accounts/signin/', {
waitUntil: 'domcontentloaded',
timeout: 30000
})
}
} catch (e) {
const error = e as Error
console.log(`❌ Navigation error: ${error.message}`)
// Fallback to direct login URL
await this.page.goto('https://www.tradingview.com/accounts/signin/', {
waitUntil: 'domcontentloaded',
timeout: 30000
})
}
await sleep(3000) await sleep(3000)
await this.takeDebugScreenshot('login_page_loaded') await this.takeDebugScreenshot('login_page_loaded')
// Check current URL and page state
const currentUrl = await this.page.url()
const pageTitle = await this.page.title()
console.log(`🌐 Current URL: ${currentUrl}`)
console.log(`📋 Page title: ${pageTitle}`)
// Wait for login form // Wait for login form
console.log('⏳ Waiting for login form...') console.log('⏳ Waiting for login form...')
await sleep(5000) await sleep(5000)
// Look for email login option // Look for email login option with comprehensive approach
console.log('CHECKING: Looking for Email login option...') console.log('CHECKING: Looking for Email login option...')
const emailTriggers = [ // Try to find email trigger button using XPath and text content
'button[data-overflow-tooltip-text="Email"]', let emailFormVisible = false
'button:contains("Email")',
'button:contains("email")', try {
'[data-name="email"]' // First try standard selectors
const emailTriggers = [
'button[data-overflow-tooltip-text="Email"]',
'[data-name="email"]',
'.tv-signin-dialog__toggle-email',
'.js-signin__email',
'[data-testid="email-signin"]',
'button[class*="email"]',
'.signin-form__toggle'
]
for (const trigger of emailTriggers) {
try {
const elements = await this.page.$$(trigger)
for (const element of elements) {
const isVisible = await element.boundingBox()
if (isVisible) {
const text = await element.evaluate(el => {
const elem = el as HTMLElement
return elem.textContent || elem.getAttribute('data-overflow-tooltip-text') || elem.getAttribute('title') || ''
})
console.log(`TARGET: Found email trigger: ${trigger} with text: "${text}"`)
await element.click()
console.log('SUCCESS: Clicked email trigger')
await sleep(3000)
emailFormVisible = true
break
}
}
if (emailFormVisible) break
} catch (e) {
continue
}
}
// If no standard selectors work, try finding buttons by text content
if (!emailFormVisible) {
console.log('🔍 Trying to find email button by text content...')
const buttons = await this.page.$$('button, span, div[role="button"], a')
for (const button of buttons) {
try {
const isVisible = await button.boundingBox()
if (isVisible) {
const text = await button.evaluate(el => {
const elem = el as HTMLElement
return (elem.textContent || '').toLowerCase().trim()
})
if (text.includes('email') || text.includes('continue with email')) {
console.log(`🎯 Found email button with text: "${text}"`)
await button.click()
console.log('✅ Clicked email button by text')
await sleep(3000)
emailFormVisible = true
break
}
}
} catch (e) {
continue
}
}
}
} catch (e) {
console.log('⚠️ Error finding email trigger:', e)
}
// If no email trigger found, check if we're in a modal or different login flow
if (!emailFormVisible) {
console.log('INFO: No email trigger found, checking for alternative login flows...')
// Wait a bit more for any modals to fully load
await sleep(5000)
await this.takeDebugScreenshot('checking_modal_login')
// Check for modal dialogs or different login containers
const modalSelectors = [
'[role="dialog"]',
'.tv-dialog',
'.tv-dialog__wrapper',
'.js-dialog',
'.signin-dialog',
'.auth-modal',
'.modal',
'[data-testid="auth-modal"]'
]
for (const modalSelector of modalSelectors) {
try {
const modal = await this.page.$(modalSelector)
if (modal) {
console.log(`📋 Found modal: ${modalSelector}`)
// Look for email options within the modal
const modalEmailButtons = await modal.$$('button, a, div[role="button"]')
for (const button of modalEmailButtons) {
try {
const isVisible = await button.boundingBox()
if (isVisible) {
const text = await button.evaluate(el => {
const elem = el as HTMLElement
return (elem.textContent || '').toLowerCase().trim()
})
console.log(`🎯 Modal button text: "${text}"`)
if (text.includes('email') || text.includes('continue with email') ||
text.includes('sign in with email') || text.includes('use email')) {
console.log(`✅ Found modal email button: "${text}"`)
await button.click()
console.log('✅ Clicked modal email button')
await sleep(3000)
emailFormVisible = true
break
}
}
} catch (e) {
continue
}
}
if (emailFormVisible) break
}
} catch (e) {
continue
}
}
}
if (!emailFormVisible) {
console.log('INFO: Still no email form, assuming email form is already visible')
}
await this.takeDebugScreenshot('after_email_trigger')
// Fill email - updated selectors for current TradingView
const emailInputSelectors = [
'input[type="email"]',
'input[name*="email"]',
'input[name="username"]',
'input[name="id_username"]',
'input[placeholder*="email" i]',
'input[placeholder*="Email" i]',
'input[autocomplete="username"]',
'input[data-name="email"]',
'input[class*="email"]',
'#id_username',
'#email',
'[data-testid="email-input"]',
'[data-testid="username-input"]'
] ]
let emailFormVisible = false // Debug: Check what's actually on the page
for (const trigger of emailTriggers) { await this.takeDebugScreenshot('before_email_search')
// Get all input elements to debug
const allInputs = await this.page.$$('input')
console.log(`DEBUG: Found ${allInputs.length} input elements on page`)
for (let i = 0; i < Math.min(allInputs.length, 10); i++) {
try { try {
const element = await this.page.$(trigger) const inputType = await allInputs[i].evaluate(el => el.type)
if (element) { const inputName = await allInputs[i].evaluate(el => el.name)
const isVisible = await element.boundingBox() const inputPlaceholder = await allInputs[i].evaluate(el => el.placeholder)
if (isVisible) { const inputId = await allInputs[i].evaluate(el => el.id)
console.log("TARGET: Found email trigger: " + trigger) const inputClass = await allInputs[i].evaluate(el => el.className)
await element.click() const inputValue = await allInputs[i].evaluate(el => el.value)
console.log('SUCCESS: Clicked email trigger') const isVisible = await allInputs[i].boundingBox()
await sleep(3000)
emailFormVisible = true console.log(`INPUT ${i}: type="${inputType}", name="${inputName}", placeholder="${inputPlaceholder}", id="${inputId}", class="${inputClass}", value="${inputValue}", visible=${!!isVisible}`)
break
// Check if this looks like an email/username field even if it doesn't match selectors
if (isVisible && (inputType === 'text' || inputType === 'email' || !inputType)) {
const lowerPlaceholder = (inputPlaceholder || '').toLowerCase()
const lowerName = (inputName || '').toLowerCase()
const lowerClass = (inputClass || '').toLowerCase()
if (lowerPlaceholder.includes('email') || lowerPlaceholder.includes('username') ||
lowerName.includes('email') || lowerName.includes('username') ||
lowerClass.includes('email') || lowerClass.includes('username')) {
console.log(`🎯 INPUT ${i} looks like email field based on attributes`)
} }
} }
} catch (e) { } catch (e) {
continue const error = e as Error
console.log(`INPUT ${i}: Error reading attributes - ${error.message}`)
} }
} }
// Fill email
const emailInputSelectors = [
'input[type="email"]',
'input[name*="email"]',
'input[name="username"]',
'input[placeholder*="email" i]'
]
let emailInput = null let emailInput = null
// First try specific selectors
for (const selector of emailInputSelectors) { for (const selector of emailInputSelectors) {
try { try {
emailInput = await this.page.$(selector) emailInput = await this.page.$(selector)
@@ -381,10 +700,76 @@ export class TradingViewAutomation {
} catch (e) { } catch (e) {
continue continue
} }
emailInput = null // Reset if not visible
}
// If no specific selector worked, try to find the first visible text input
if (!emailInput) {
console.log('🔍 No specific email input found, trying first visible text input...')
for (const input of allInputs) {
try {
const isVisible = await input.boundingBox()
const inputType = await input.evaluate(el => el.type)
if (isVisible && (inputType === 'text' || inputType === 'email' || !inputType)) {
console.log('🎯 Found first visible text input, assuming it\'s email field')
emailInput = input
break
}
} catch (e) {
continue
}
}
}
// Last resort: try to find inputs by looking for labels or surrounding text
if (!emailInput) {
console.log('🔍 Last resort: searching for inputs near email-related text...')
// Look for text that says "email" and find nearby inputs
const emailTexts = await this.page.evaluate(() => {
const allElements = Array.from(document.querySelectorAll('*'))
return allElements
.filter(el => {
const text = (el.textContent || '').toLowerCase()
return text.includes('email') || text.includes('username') || text.includes('sign in')
})
.map(el => {
const rect = el.getBoundingClientRect()
return {
text: (el.textContent || '').trim(),
x: rect.x,
y: rect.y,
tag: el.tagName
}
})
})
console.log('📋 Found email-related text elements:', emailTexts.slice(0, 5))
// Try to find any input that's not already found
for (const input of allInputs) {
try {
const isVisible = await input.boundingBox()
if (isVisible) {
console.log('🎯 Using any visible input as potential email field')
emailInput = input
break
}
} catch (e) {
continue
}
}
} }
if (!emailInput) { if (!emailInput) {
throw new Error('Could not find email input field') // Additional debug info
const currentUrl = await this.page.url()
const title = await this.page.title()
console.log(`❌ Email input not found on: ${currentUrl}`)
console.log(`❌ Page title: ${title}`)
await this.takeDebugScreenshot('email_input_not_found')
throw new Error(`Could not find email input field on ${currentUrl}. Found ${allInputs.length} inputs total.`)
} }
await emailInput.click() await emailInput.click()