dptxa-proxy / src /pages /api /player /report-error.js
TXAVLOG
Deploy DPTXA to Hugging Face Spaces
4bea261
Raw
History Blame Contribute Delete
7.9 kB
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' }
});
}
};