Add PDF list view with upload status tracking

- Show all pending PDFs in a scrollable list instead of single PDF
- Track upload status (pending/uploading/uploaded/failed) per PDF
- Store queue in chrome.storage.local for persistence
- Prevent duplicate uploads by checking URL against uploaded PDFs
- Add Dismiss button to remove PDFs from queue
- Show badge with count of pending PDFs
- Auto-cleanup old entries (uploaded >7 days, failed >24h)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-15 14:55:32 +01:00
parent 5bde27dcdd
commit 3e86bb126b
5 changed files with 626 additions and 195 deletions

View File

@@ -1,13 +1,19 @@
/**
* Service Worker (Background Script)
* Handles PDF detection and credential expiry checks
* Handles PDF detection, queue management, and credential expiry checks
*/
import { startPDFDetection, DetectedPDF } from '../utils/pdf-detector';
import { loadCredentials } from '../utils/storage';
// Store last detected PDF in memory (ephemeral)
let lastDetectedPDF: DetectedPDF | null = null;
import {
addPDF,
getPendingCount,
getPendingPDFs,
updatePDFStatus,
removePDF,
cleanupOldEntries,
PDFStatus
} from '../utils/pdf-queue';
/**
* Initialize extension on install
@@ -16,9 +22,10 @@ chrome.runtime.onInstalled.addListener((details) => {
console.log('[Service Worker] onInstalled event:', details.reason);
if (details.reason === 'install') {
console.log('[Service Worker] BinectChrome installed');
setupCredentialExpiryAlarm();
setupAlarms();
} else if (details.reason === 'update') {
console.log('[Service Worker] BinectChrome updated');
setupAlarms();
}
});
@@ -27,16 +34,24 @@ chrome.runtime.onInstalled.addListener((details) => {
*/
chrome.runtime.onStartup.addListener(() => {
console.log('[Service Worker] onStartup event - BinectChrome started');
setupCredentialExpiryAlarm();
setupAlarms();
updateBadge();
});
/**
* Set up alarm to check credential expiry daily
* Set up alarms for periodic tasks
*/
function setupCredentialExpiryAlarm() {
function setupAlarms() {
// Credential expiry check
chrome.alarms.create('checkCredentialExpiry', {
delayInMinutes: 1, // First check in 1 minute
periodInMinutes: 24 * 60 // Then every 24 hours
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
});
}
@@ -47,6 +62,9 @@ chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === 'checkCredentialExpiry') {
checkAndDeleteExpiredCredentials();
}
if (alarm.name === 'cleanupPDFQueue') {
cleanupOldEntries();
}
});
/**
@@ -54,39 +72,43 @@ chrome.alarms.onAlarm.addListener((alarm) => {
*/
async function checkAndDeleteExpiredCredentials() {
const credentials = await loadCredentials();
// loadCredentials already handles expiry check and deletion
// If credentials are expired, it returns null and deletes them
if (credentials === null) {
console.log('Credentials expired and deleted');
console.log('[Service Worker] Credentials expired and deleted');
}
}
/**
* Initialize badge with default icon
* Update badge with pending PDF count
*/
function initializeBadge() {
// Set a default badge to make extension visible
chrome.action.setBadgeText({ text: '•' });
async function updateBadge() {
const count = await getPendingCount();
const text = count > 0 ? count.toString() : '•';
chrome.action.setBadgeText({ text });
chrome.action.setBadgeBackgroundColor({ color: '#4A90E2' }); // Binect Blue
console.log('[Service Worker] Default badge set');
console.log('[Service Worker] Badge updated:', text);
}
// Initialize badge on load
initializeBadge();
updateBadge();
/**
* Start PDF detection
*/
console.log('[Service Worker] Initializing PDF detection...');
startPDFDetection((pdf: DetectedPDF) => {
console.log('[Service Worker] PDF DETECTED CALLBACK:', pdf.filename);
lastDetectedPDF = pdf;
startPDFDetection(async (pdf: DetectedPDF) => {
console.log('[Service Worker] PDF DETECTED:', pdf.filename);
// Update badge to indicate PDF detected
chrome.action.setBadgeText({ text: '1' });
chrome.action.setBadgeBackgroundColor({ color: '#4A90E2' }); // Binect Blue
// Add to persistent queue
const entry = await addPDF(pdf);
console.log('[Service Worker] Badge updated, PDF stored in memory');
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();
});
/**
@@ -95,25 +117,47 @@ startPDFDetection((pdf: DetectedPDF) => {
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
console.log('[Service Worker] Message received:', request.action);
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?: object };
updatePDFStatus(id, status, meta).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;
}
// Legacy handlers for backward compatibility
if (request.action === 'getLastPDF') {
console.log('[Service Worker] Returning last PDF:', lastDetectedPDF ? lastDetectedPDF.filename : 'none');
sendResponse({ pdf: lastDetectedPDF });
// Return the first pending PDF for backward compatibility
getPendingPDFs().then(entries => {
const pdf = entries.length > 0 ? entries[0] : null;
sendResponse({ pdf });
});
return true;
}
if (request.action === 'clearLastPDF') {
console.log('[Service Worker] Clearing last PDF');
lastDetectedPDF = null;
chrome.action.setBadgeText({ text: '•' }); // Reset to default badge
sendResponse({ success: true });
return true;
}
if (request.action === 'pdfSent') {
console.log('[Service Worker] PDF sent, resetting badge');
// Reset badge after successful send
chrome.action.setBadgeText({ text: '•' }); // Reset to default badge
sendResponse({ success: true });
if (request.action === 'clearLastPDF' || request.action === 'pdfSent') {
updateBadge().then(() => {
sendResponse({ success: true });
});
return true;
}