Spaces:
Sleeping
Sleeping
File size: 7,897 Bytes
4bea261 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | 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' }
});
}
};
|