/** * Popup UI Logic */ import './popup.css'; import { loadCredentials, saveCredentials, deleteCredentials, updateLastUse } from '../utils/storage'; import { uploadPDF, testConnection, BinectAPIError } from '../utils/binect-api'; import { fetchPDFBytes, DetectedPDF } from '../utils/pdf-detector'; import { addTrackingEntry } from '../tracking/tracker'; // DOM Elements const authView = document.getElementById('authView')!; const mainView = document.getElementById('mainView')!; const noPdfView = document.getElementById('noPdfView')!; const pdfView = document.getElementById('pdfView')!; const loginForm = document.getElementById('loginForm') as HTMLFormElement; const usernameInput = document.getElementById('username') as HTMLInputElement; const passwordInput = document.getElementById('password') as HTMLInputElement; const loginBtn = document.getElementById('loginBtn') as HTMLButtonElement; const authError = document.getElementById('authError')!; const pdfFilename = document.getElementById('pdfFilename')!; const pdfSize = document.getElementById('pdfSize')!; const pdfDomain = document.getElementById('pdfDomain')!; const pdfTimestamp = document.getElementById('pdfTimestamp')!; const sendBtn = document.getElementById('sendBtn') as HTMLButtonElement; const statusMessage = document.getElementById('statusMessage')!; const logoutBtn = document.getElementById('logoutBtn')!; const helpBtn = document.getElementById('helpBtn')!; const togglePasswordBtn = document.getElementById('togglePassword') as HTMLButtonElement; const eyeIcon = document.getElementById('eyeIcon')!; const eyeOffIcon = document.getElementById('eyeOffIcon')!; // State let currentPDF: DetectedPDF | null = null; let currentCredentials: { username: string; password: string } | null = null; /** * Initialize popup */ async function init() { // Check if user has credentials const credentials = await loadCredentials(); if (credentials) { // Try to test connection try { const isConnected = await testConnection(credentials.username, credentials.password); if (isConnected) { currentCredentials = credentials; await updateLastUse(); showMainView(); await loadLastPDF(); } else { // Authentication failed, credentials may be invalid showAuthView(); } } catch (error) { // Connection test failed console.error('[Popup] Connection test failed:', error); showAuthView(); } } else { showAuthView(); } // Setup event listeners setupEventListeners(); } /** * Setup event listeners */ function setupEventListeners() { loginForm.addEventListener('submit', handleLogin); sendBtn.addEventListener('click', handleSendPDF); logoutBtn.addEventListener('click', handleLogout); helpBtn.addEventListener('click', handleHelp); togglePasswordBtn.addEventListener('click', handleTogglePassword); } /** * Handle password visibility toggle */ function handleTogglePassword() { const isPassword = passwordInput.type === 'password'; if (isPassword) { // Show password passwordInput.type = 'text'; eyeIcon.style.display = 'none'; eyeOffIcon.style.display = 'block'; togglePasswordBtn.setAttribute('aria-label', 'Hide password'); togglePasswordBtn.setAttribute('title', 'Hide password'); } else { // Hide password passwordInput.type = 'password'; eyeIcon.style.display = 'block'; eyeOffIcon.style.display = 'none'; togglePasswordBtn.setAttribute('aria-label', 'Show password'); togglePasswordBtn.setAttribute('title', 'Show password'); } } /** * Handle login */ async function handleLogin(e: Event) { e.preventDefault(); const username = usernameInput.value.trim(); const password = passwordInput.value; if (!username || !password) { showError('Please enter username and password'); return; } loginBtn.disabled = true; loginBtn.textContent = 'Signing in...'; hideError(); try { const isConnected = await testConnection(username, password); if (!isConnected) { showError('Invalid credentials. Please check your username and password.'); return; } // Save credentials currentCredentials = { username, password }; await saveCredentials({ username, password }); showMainView(); await loadLastPDF(); } catch (error) { if (error instanceof BinectAPIError) { showError(error.message); } else { showError('Authentication failed. Please try again.'); } } finally { loginBtn.disabled = false; loginBtn.textContent = 'Sign In'; } } /** * Handle send PDF */ async function handleSendPDF() { if (!currentPDF || !currentCredentials) { return; } sendBtn.disabled = true; showStatus('Uploading...', 'uploading'); try { // Fetch PDF bytes const pdfBytes = await fetchPDFBytes(currentPDF.url); // Upload to Binect with credentials const document = await uploadPDF( pdfBytes, currentPDF.filename, currentCredentials.username, currentCredentials.password ); // Track successful transfer await addTrackingEntry({ timestamp: Date.now(), sourceDomain: currentPDF.sourceDomain, destinationUrl: 'https://api.binect.de/binectapi/v1/documents', pdfSize: pdfBytes.byteLength, // Use actual size from fetched data result: 'success' }); // Update last use timestamp await updateLastUse(); // Notify background script chrome.runtime.sendMessage({ action: 'pdfSent' }); showStatus(`Success! Document ID: ${document.id} (Status: ${document.status.text})`, 'success'); // Clear PDF after 3 seconds setTimeout(() => { currentPDF = null; showNoPDF(); hideStatus(); }, 3000); } catch (error) { let errorMessage = 'Upload failed'; if (error instanceof BinectAPIError) { errorMessage = error.message; // If auth error, might need to re-login if (error.statusCode === 401 || error.statusCode === 403) { errorMessage = 'Invalid credentials. Please sign in again.'; setTimeout(() => { handleLogout(); }, 2000); } } else if (error instanceof Error) { errorMessage = error.message; } // Track failed transfer await addTrackingEntry({ timestamp: Date.now(), sourceDomain: currentPDF.sourceDomain, destinationUrl: 'https://api.binect.de/binectapi/v1/documents', pdfSize: currentPDF.size || 0, result: 'failure', errorMessage }); showStatus(errorMessage, 'error'); } finally { sendBtn.disabled = false; } } /** * Handle logout */ async function handleLogout() { await deleteCredentials(); currentCredentials = null; currentPDF = null; // Clear form loginForm.reset(); showAuthView(); } /** * Handle help button */ function handleHelp() { // Open tracking page chrome.tabs.create({ url: chrome.runtime.getURL('tracking.html') }); } /** * Load last detected PDF */ async function loadLastPDF() { console.log('[Popup] Loading last PDF...'); // First, check if current tab is viewing a PDF const currentTabPDF = await checkCurrentTabForPDF(); if (currentTabPDF) { console.log('[Popup] Found PDF in current tab:', currentTabPDF.filename); currentPDF = currentTabPDF; showPDF(currentPDF); return; } console.log('[Popup] No PDF in current tab, checking background script...'); // If no PDF in current tab, ask background script for last detected download chrome.runtime.sendMessage({ action: 'getLastPDF' }, async (response) => { if (response && response.pdf && response.pdf !== null) { console.log('[Popup] Background returned PDF:', response.pdf.filename); currentPDF = response.pdf; showPDF(response.pdf); } else { console.log('[Popup] Background has no PDF, checking recent downloads as fallback...'); // Fallback: Check recent downloads directly const recentPDF = await checkRecentDownloads(); if (recentPDF !== null) { console.log('[Popup] Found recent PDF download:', recentPDF.filename); currentPDF = recentPDF; showPDF(recentPDF); } else { console.log('[Popup] No PDF found anywhere'); showNoPDF(); } } }); } /** * Check recent downloads for PDFs (fallback mechanism) */ async function checkRecentDownloads(): Promise { return new Promise((resolve) => { chrome.downloads.search( { limit: 20, // Check last 20 downloads orderBy: ['-startTime'] }, (items) => { console.log('[Popup] Checked recent downloads:', items.length, 'items'); // Find most recent completed PDF const pdfItem = items.find( (item) => item.state === 'complete' && (item.filename.toLowerCase().endsWith('.pdf') || item.mime === 'application/pdf') ); if (pdfItem) { console.log('[Popup] Found recent PDF:', pdfItem.filename); // Extract domain let domain = 'unknown'; try { const urlObj = new URL(pdfItem.url); domain = urlObj.hostname; } catch (e) { // Keep default } resolve({ id: `download-${pdfItem.id}`, filename: pdfItem.filename.split('/').pop() || pdfItem.filename, url: pdfItem.url, size: pdfItem.fileSize, timestamp: Date.now(), // Use current time as approximation sourceDomain: domain }); } else { console.log('[Popup] No recent PDF downloads found'); resolve(null); } } ); }); } /** * Check if the current active tab is viewing a PDF */ async function checkCurrentTabForPDF(): Promise { try { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); if (!tab || !tab.url) { return null; } // Check if the URL is a PDF const url = tab.url; const isPDF = url.toLowerCase().endsWith('.pdf') || url.includes('type=application/pdf') || url.includes('mime=application/pdf'); if (isPDF) { // Extract filename from URL let filename = 'document.pdf'; try { const urlObj = new URL(url); const pathname = urlObj.pathname; const pathParts = pathname.split('/'); const lastPart = pathParts[pathParts.length - 1]; if (lastPart && lastPart.toLowerCase().endsWith('.pdf')) { filename = decodeURIComponent(lastPart); } else if (tab.title && tab.title !== 'about:blank' && !tab.title.startsWith('chrome://')) { // Use tab title if available filename = tab.title.endsWith('.pdf') ? tab.title : `${tab.title}.pdf`; } } catch (e) { // Use tab title as fallback if (tab.title && tab.title !== 'about:blank') { filename = tab.title.endsWith('.pdf') ? tab.title : `${tab.title}.pdf`; } } // Extract domain let domain = 'unknown'; try { const urlObj = new URL(url); domain = urlObj.hostname; } catch (e) { // Keep default } return { id: `tab-${tab.id}`, filename, url, size: 0, // Unknown size for viewed PDFs timestamp: Date.now(), sourceDomain: domain }; } return null; } catch (error) { console.error('Error checking current tab for PDF:', error); return null; } } /** * Show auth view */ function showAuthView() { authView.style.display = 'block'; mainView.style.display = 'none'; } /** * Show main view */ function showMainView() { authView.style.display = 'none'; mainView.style.display = 'block'; } /** * Show no PDF view */ function showNoPDF() { noPdfView.style.display = 'block'; pdfView.style.display = 'none'; } /** * Show PDF view */ function showPDF(pdf: DetectedPDF) { noPdfView.style.display = 'none'; pdfView.style.display = 'block'; pdfFilename.textContent = pdf.filename; pdfSize.textContent = formatFileSize(pdf.size); pdfDomain.textContent = pdf.sourceDomain; pdfTimestamp.textContent = formatTimestamp(pdf.timestamp); sendBtn.disabled = false; hideStatus(); } /** * Show error message */ function showError(message: string) { authError.textContent = message; authError.style.display = 'block'; } /** * Hide error message */ function hideError() { authError.style.display = 'none'; } /** * Show status message */ function showStatus(message: string, type: 'uploading' | 'success' | 'error') { statusMessage.textContent = message; statusMessage.className = `status-message ${type}`; statusMessage.style.display = 'block'; } /** * Hide status message */ function hideStatus() { statusMessage.style.display = 'none'; } /** * Format file size */ function formatFileSize(bytes: number): string { if (bytes === 0) { return 'Size unknown'; } else if (bytes < 1024) { return `${bytes} B`; } else if (bytes < 1024 * 1024) { return `${(bytes / 1024).toFixed(1)} KB`; } else { return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } } /** * Format timestamp */ function formatTimestamp(timestamp: number): string { const now = Date.now(); const diff = now - timestamp; if (diff < 60 * 1000) { return 'Just now'; } else if (diff < 60 * 60 * 1000) { const minutes = Math.floor(diff / (60 * 1000)); return `${minutes} minute${minutes > 1 ? 's' : ''} ago`; } else if (diff < 24 * 60 * 60 * 1000) { const hours = Math.floor(diff / (60 * 60 * 1000)); return `${hours} hour${hours > 1 ? 's' : ''} ago`; } else { return new Date(timestamp).toLocaleDateString(); } } // Initialize on load init();