/** * Service Worker (Background Script) * Handles PDF detection, queue management, and credential expiry checks */ import { startPDFDetection, DetectedPDF } from '../utils/pdf-detector'; import { loadCredentials } from '../utils/storage'; import { addPDF, getActionableCount, getAllPDFs, getLiveProxies, getArchivedProxies, getPendingPDFs, updatePDFStatus, archiveProxy, restoreProxy, dismissPDF, removePDF, cleanupOldEntries, syncFromServer, clearServerFields, attachServerDocument, PDFStatus, PDFStatusMeta } from '../utils/pdf-queue'; import { shipDocument, getDocumentStatus, deleteDocument, listServerDocuments } from '../utils/binect-api'; /** * Initialize extension on install */ chrome.runtime.onInstalled.addListener((details) => { console.log('[Service Worker] onInstalled event:', details.reason); if (details.reason === 'install') { console.log('[Service Worker] BinectChrome installed'); setupAlarms(); } else if (details.reason === 'update') { console.log('[Service Worker] BinectChrome updated'); setupAlarms(); } }); /** * Handle extension startup */ chrome.runtime.onStartup.addListener(() => { console.log('[Service Worker] onStartup event - BinectChrome started'); setupAlarms(); updateBadge(); }); /** * Set up alarms for periodic tasks */ function setupAlarms() { // Credential expiry check chrome.alarms.create('checkCredentialExpiry', { delayInMinutes: 1, periodInMinutes: 24 * 60 // Every 24 hours }); // PDF queue cleanup chrome.alarms.create('cleanupPDFQueue', { delayInMinutes: 60, // First cleanup in 1 hour periodInMinutes: 6 * 60 // Every 6 hours }); } /** * Handle alarm events */ chrome.alarms.onAlarm.addListener((alarm) => { if (alarm.name === 'checkCredentialExpiry') { checkAndDeleteExpiredCredentials(); } if (alarm.name === 'cleanupPDFQueue') { cleanupOldEntries(); } }); /** * Check if credentials are expired and delete them */ async function checkAndDeleteExpiredCredentials() { const credentials = await loadCredentials(); if (credentials === null) { console.log('[Service Worker] Credentials expired and deleted'); } } /** * Update badge with actionable PDF count */ async function updateBadge() { const count = await getActionableCount(); const text = count > 0 ? count.toString() : '•'; chrome.action.setBadgeText({ text }); chrome.action.setBadgeBackgroundColor({ color: '#4A90E2' }); // Binect Blue console.log('[Service Worker] Badge updated:', text); } // Initialize badge on load updateBadge(); /** * Start PDF detection */ console.log('[Service Worker] Initializing PDF detection...'); startPDFDetection(async (pdf: DetectedPDF) => { console.log('[Service Worker] PDF DETECTED:', pdf.filename); // Add to persistent queue const entry = await addPDF(pdf); if (entry) { console.log('[Service Worker] PDF added to queue:', entry.filename); } else { console.log('[Service Worker] PDF skipped (already uploaded):', pdf.filename); } // Update badge await updateBadge(); }); /** * Handle messages from popup */ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { console.log('[Service Worker] Message received:', request.action); // Get all PDFs (including completed ones for display) if (request.action === 'getAllPDFs') { getAllPDFs().then(entries => { console.log('[Service Worker] Returning all PDFs:', entries.length, 'entries'); sendResponse({ entries }); }); return true; } // Get live proxy documents (not archived) if (request.action === 'getLiveProxies') { getLiveProxies().then(entries => { console.log('[Service Worker] Returning live proxies:', entries.length, 'entries'); sendResponse({ entries }); }); return true; } // Get archived proxy documents if (request.action === 'getArchivedProxies') { getArchivedProxies().then(entries => { console.log('[Service Worker] Returning archived proxies:', entries.length, 'entries'); sendResponse({ entries }); }); return true; } // Add a PDF to the queue (from popup discovery) if (request.action === 'addPDF') { addPDF(request.pdf).then(entry => { if (entry) { console.log('[Service Worker] PDF added via message:', entry.filename); } return updateBadge().then(() => entry); }).then(entry => { sendResponse({ entry }); }); return true; } // Legacy: Get only actionable PDFs if (request.action === 'getPDFQueue') { getPendingPDFs().then(entries => { console.log('[Service Worker] Returning PDF queue:', entries.length, 'entries'); sendResponse({ entries }); }); return true; } if (request.action === 'updatePDFStatus') { const { id, status, meta } = request as { id: string; status: PDFStatus; meta?: PDFStatusMeta }; updatePDFStatus(id, status, meta).then(() => { return updateBadge(); }).then(() => { sendResponse({ success: true }); }); return true; } if (request.action === 'dismissPDF') { dismissPDF(request.id).then(() => { return updateBadge(); }).then(() => { sendResponse({ success: true }); }); return true; } // Archive a proxy document (move to archive view) if (request.action === 'archiveProxy') { archiveProxy(request.id).then(() => { return updateBadge(); }).then(() => { sendResponse({ success: true }); }); return true; } // Restore a proxy document (move back to live view) if (request.action === 'restoreProxy') { restoreProxy(request.id).then(() => { return updateBadge(); }).then(() => { sendResponse({ success: true }); }); return true; } if (request.action === 'removePDF') { removePDF(request.id).then(() => { return updateBadge(); }).then(() => { sendResponse({ success: true }); }); return true; } // Ship a document (place order for production) if (request.action === 'shipDocument') { const { documentId, username, password } = request as { documentId: number; username: string; password: string; }; shipDocument(documentId, username, password) .then(result => { sendResponse({ success: true, ...result }); }) .catch(error => { sendResponse({ success: false, error: error instanceof Error ? error.message : 'Failed to ship document' }); }); return true; } // Get document status from Binect if (request.action === 'getDocumentStatus') { const { documentId, username, password } = request as { documentId: number; username: string; password: string; }; getDocumentStatus(documentId, username, password) .then(result => { sendResponse({ success: true, ...result }); }) .catch(error => { // Include error code for 404 detection const errorCode = (error as { statusCode?: number }).statusCode; sendResponse({ success: false, error: error instanceof Error ? error.message : 'Failed to get status', errorCode }); }); return true; } // Delete a document from the server if (request.action === 'deleteServerDocument') { const { documentId, username, password } = request as { documentId: number; username: string; password: string; }; deleteDocument(documentId, username, password) .then(() => { sendResponse({ success: true }); }) .catch(error => { sendResponse({ success: false, error: error instanceof Error ? error.message : 'Failed to delete document' }); }); return true; } // List all documents from the server (for sync) if (request.action === 'listServerDocuments') { const { username, password } = request as { username: string; password: string; }; listServerDocuments(username, password) .then(documents => { sendResponse({ success: true, documents }); }) .catch(error => { sendResponse({ success: false, error: error instanceof Error ? error.message : 'Failed to list documents' }); }); return true; } // Sync a server document to local proxy (create or update) if (request.action === 'syncFromServer') { const { binectDocumentId, filename, binectStatusCode, binectStatusText, price, recipientAddress, errorDetails } = request as { binectDocumentId: number; filename: string; binectStatusCode: number; binectStatusText: string; price?: number; recipientAddress?: string; errorDetails?: string; }; syncFromServer(binectDocumentId, filename, binectStatusCode, binectStatusText, price, recipientAddress, errorDetails) .then(proxy => { sendResponse({ success: true, proxy }); }) .catch(error => { sendResponse({ success: false, error: error instanceof Error ? error.message : 'Failed to sync document' }); }); return true; } // Clear server fields from a proxy (when deleted from server) if (request.action === 'clearServerFields') { clearServerFields(request.id).then(() => { return updateBadge(); }).then(() => { sendResponse({ success: true }); }); return true; } // Attach a server document to a local proxy if (request.action === 'attachServerDocument') { const { id, binectDocumentId, binectStatusCode, binectStatusText, price, recipientAddress, errorMessage } = request as { id: string; binectDocumentId: number; binectStatusCode: number; binectStatusText: string; price?: number; recipientAddress?: string; errorMessage?: string; }; attachServerDocument(id, binectDocumentId, binectStatusCode, binectStatusText, price, recipientAddress, errorMessage) .then(() => { return updateBadge(); }) .then(() => { sendResponse({ success: true }); }) .catch(error => { sendResponse({ success: false, error: error instanceof Error ? error.message : 'Failed to attach document' }); }); return true; } // Legacy handlers for backward compatibility if (request.action === 'getLastPDF') { getPendingPDFs().then(entries => { const pdf = entries.length > 0 ? entries[0] : null; sendResponse({ pdf }); }); return true; } if (request.action === 'clearLastPDF' || request.action === 'pdfSent') { updateBadge().then(() => { sendResponse({ success: true }); }); return true; } return false; }); console.log('[Service Worker] ===== BinectChrome service worker loaded ====='); console.log('[Service Worker] Timestamp:', new Date().toISOString());