/** * Popup UI Logic */ import { loadCredentials, saveCredentials, deleteCredentials, updateLastUse } from '../utils/storage'; import { authenticate, uploadPDF, 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')!; // State let currentPDF: DetectedPDF | null = null; let authToken: string | null = null; /** * Initialize popup */ async function init() { // Check if user has credentials const credentials = await loadCredentials(); if (credentials) { // Try to authenticate try { const token = await authenticate(credentials.username, credentials.password); authToken = token.token; await updateLastUse(); showMainView(); await loadLastPDF(); } catch (error) { // Authentication failed, credentials may be invalid 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); } /** * 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 token = await authenticate(username, password); authToken = token.token; // Save credentials 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 || !authToken) { return; } sendBtn.disabled = true; showStatus('Uploading...', 'uploading'); try { // Fetch PDF bytes const pdfBytes = await fetchPDFBytes(currentPDF.url); // Upload to Binect const result = await uploadPDF(pdfBytes, currentPDF.filename, authToken); // Track successful transfer await addTrackingEntry({ timestamp: Date.now(), sourceDomain: currentPDF.sourceDomain, destinationUrl: 'https://api.binect.de/documents/upload', pdfSize: currentPDF.size, result: 'success' }); // Update last use timestamp await updateLastUse(); // Notify background script chrome.runtime.sendMessage({ action: 'pdfSent' }); showStatus(`Success! Document ID: ${result.documentId}`, '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) { errorMessage = 'Session expired. 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/documents/upload', pdfSize: currentPDF.size, result: 'failure', errorMessage }); showStatus(errorMessage, 'error'); } finally { sendBtn.disabled = false; } } /** * Handle logout */ async function handleLogout() { await deleteCredentials(); authToken = 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() { // Ask background script for last PDF chrome.runtime.sendMessage({ action: 'getLastPDF' }, (response) => { if (response && response.pdf) { currentPDF = response.pdf; if (currentPDF) { showPDF(currentPDF); } else { showNoPDF(); } } else { showNoPDF(); } }); } /** * 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 < 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();