import { txasupabase } from '../../../lib/txasupabase.js'; // Decryption Helper for Browser Token function decryptToken(token, key) { if (!token || !token.startsWith('tk_')) return null; try { const rawToken = token.substring(3); let str = rawToken.replace(/-/g, '+').replace(/_/g, '/'); while (str.length % 4) str += '='; // Decode base64 to binary string const text = Buffer.from(str, 'base64').toString('binary'); let utf8Text = ''; for (let i = 0; i < text.length; i++) { const charCode = text.charCodeAt(i); const keyChar = key.charCodeAt(i % key.length); const decryptedChar = charCode ^ keyChar; utf8Text += String.fromCharCode(decryptedChar); } return decodeURIComponent(escape(utf8Text)); } catch (e) { console.error('[Telemetry] Decryption failed:', e); return null; } } // User-Agent Parser for Device & Browser function parseUserAgent(ua) { let device = 'Thiết bị khác'; let browser = 'Trình duyệt khác'; if (!ua) return { device, browser }; // OS / Device detection if (ua.includes('Windows NT 10.0')) { device = 'Windows 10/11'; } else if (ua.includes('Windows NT 6.1')) { device = 'Windows 7'; } else if (ua.includes('Windows NT 6.2') || ua.includes('Windows NT 6.3')) { device = 'Windows 8/8.1'; } else if (ua.includes('Android')) { const androidMatch = ua.match(/Android\s+([^;)]+)/); const modelMatch = ua.match(/Android\s+[^;]+;\s+([^;)]+)/); const deviceName = modelMatch ? modelMatch[1].trim() : (androidMatch ? androidMatch[0] : 'Android Device'); device = `Android (${deviceName})`; } else if (ua.includes('iPhone')) { const iphoneMatch = ua.match(/iPhone;\s+CPU\s+iPhone\s+OS\s+([^;)]+)/); const osVer = iphoneMatch ? iphoneMatch[1].replace(/_/g, '.') : ''; device = osVer ? `iPhone (iOS ${osVer})` : 'iPhone'; } else if (ua.includes('iPad')) { device = 'iPad'; } else if (ua.includes('Macintosh') || ua.includes('Mac OS X')) { device = 'macOS'; } else if (ua.includes('Linux')) { device = 'Linux'; } // Browser detection if (ua.includes('Edg/')) { browser = 'Microsoft Edge'; } else if (ua.includes('Chrome/') && !ua.includes('Chromium')) { browser = 'Google Chrome'; } else if (ua.includes('Safari/') && !ua.includes('Chrome')) { browser = 'Safari'; } else if (ua.includes('Firefox/')) { browser = 'Mozilla Firefox'; } else if (ua.includes('OPR/') || ua.includes('Opera/')) { browser = 'Opera'; } else if (ua.includes('Zalo/')) { browser = 'Zalo Browser'; } return { device, browser }; } export const POST = async ({ request }) => { try { const body = await request.json(); const { movie_slug, movie_name, episode_name, error_type, error_message, error_details, browser_token } = body; if (!error_type) { return new Response(JSON.stringify({ error: 'Error type is required!' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); } const userAgent = request.headers.get('user-agent') || ''; const fallbackIp = request.headers.get('cf-connecting-ip') || request.headers.get('x-real-ip') || request.headers.get('x-forwarded-for')?.split(',')[0].trim() || '127.0.0.1'; // Decrypt and parse browser, IP, location & device details let decryptedInfo = null; let parsedDevice = 'Không rõ'; let parsedBrowser = 'Không rõ'; let parsedIp = fallbackIp; let parsedLocation = null; if (browser_token) { if (browser_token.startsWith('tk_')) { decryptedInfo = decryptToken(browser_token, 'phimtxadinhvaikk'); if (decryptedInfo) { const parts = decryptedInfo.split(' - '); const ua = parts[0] || ''; // Get IP from token const ipPart = parts.find(p => p.startsWith('IP: ')); if (ipPart) { parsedIp = ipPart.replace('IP: ', ''); } // Get Location from token const locParts = parts.filter(p => !p.startsWith('IP: ') && p !== ua); if (locParts.length > 0) { parsedLocation = locParts.join(', '); } // Parse UA from token const parsed = parseUserAgent(ua); parsedDevice = parsed.device; parsedBrowser = parsed.browser; } else { // Decryption failed but we still have request UA const parsed = parseUserAgent(userAgent); parsedDevice = parsed.device; parsedBrowser = parsed.browser; } } else if (browser_token.startsWith('txanot-')) { // Fallback token format: txanot-[ip]-[uaHash] const parts = browser_token.split('-'); if (parts[1]) { parsedIp = parts[1]; } const parsed = parseUserAgent(userAgent); parsedDevice = parsed.device; parsedBrowser = parsed.browser; } } else { // No token provided, fallback to request headers const parsed = parseUserAgent(userAgent); parsedDevice = parsed.device; parsedBrowser = parsed.browser; } const enrichedDetails = { ...(error_details || {}), parsed_device: parsedDevice, parsed_browser: parsedBrowser, parsed_ip: parsedIp, parsed_location: parsedLocation, decrypted_raw: decryptedInfo }; // Insert into txa_player_errors const { data, error } = await txasupabase.client .from('txa_player_errors') .insert({ movie_slug: movie_slug || null, movie_name: movie_name || null, episode_name: episode_name || null, error_type, error_message: error_message || null, error_details: enrichedDetails, user_agent: userAgent, ip: parsedIp, browser_token: browser_token || null }) .select() .single(); if (error) { console.error('Error inserting player error into Supabase:', error); return new Response(JSON.stringify({ error: error.message }), { status: 500, headers: { 'Content-Type': 'application/json' } }); } // Write a unified system log featuring decrypted info const locationStr = parsedLocation ? ` (${parsedLocation})` : ''; const logMessage = `Lỗi Player (${error_type}) trên phim "${movie_name || 'Không rõ'}" (${episode_name || 'Không rõ'}): ${error_message || ''}. Thiết bị: ${parsedDevice} | Trình duyệt: ${parsedBrowser} | IP: ${parsedIp}${locationStr}`; try { if (typeof txasupabase.createLog === 'function') { await txasupabase.createLog( 'player_error', 'error', logMessage, { movie_slug, movie_name, episode_name, error_type, error_message, ip: parsedIp, browser_token, parsed_device: parsedDevice, parsed_browser: parsedBrowser } ); } else { await txasupabase.client.from('logs').insert({ action: 'player_error', level: 'error', message: logMessage, details: JSON.stringify({ movie_slug, movie_name, episode_name, error_type, error_message, ip: parsedIp, browser_token, parsed_device: parsedDevice, parsed_browser: parsedBrowser }) }); } } catch (logErr) { console.error('Failed to write to logs table:', logErr); } return new Response(JSON.stringify({ success: true, message: 'Player error reported successfully.', data }), { status: 200, headers: { 'Content-Type': 'application/json' } }); } catch (err) { console.error('Error in report-error API:', err); return new Response(JSON.stringify({ error: 'Internal system error!' }), { status: 500, headers: { 'Content-Type': 'application/json' } }); } };