// TaxPack Background Service Worker // Handles Gmail OAuth and message passing import { Receipt, extractReceiptFromEmail, exportToCSV, generateTaxSummary, } from './lib/receipt-utils'; // Store for extracted receipts const STORAGE_KEY = 'taxpack_receipts'; // Get auth token from Chrome identity API export async function getAuthToken(interactive: boolean = true): Promise { try { const token = await chrome.identity.getAuthToken({ interactive }); return token.token ?? null; } catch (error) { console.error('Auth error:', error); return null; } } // Fetch Gmail messages with receipt indicators export async function fetchReceiptEmails(token: string, maxResults: number = 50): Promise { const queries = [ 'subject:receipt', 'subject:invoice', 'subject:order confirmation', 'subject:payment confirmation', 'subject:purchase confirmation', 'from:receipts', 'from:noreply', ]; const allMessages: any[] = []; const seenIds = new Set(); for (const query of queries) { try { const url = `https://www.googleapis.com/gmail/v1/users/me/messages?q=${encodeURIComponent(query)}&maxResults=${Math.ceil(maxResults / queries.length)}`; const response = await fetch(url, { headers: { Authorization: `Bearer ${token}` }, }); if (!response.ok) { if (response.status === 401) { // Token expired, try to remove and re-auth await chrome.identity.removeCachedAuthToken({ token }); } continue; } const data = await response.json(); if (data.messages) { for (const msg of data.messages) { if (!seenIds.has(msg.id)) { seenIds.add(msg.id); allMessages.push(msg); } } } } catch (error) { console.error(`Query failed: ${query}`, error); } } return allMessages.slice(0, maxResults); } // Fetch full message details export async function fetchMessageDetails(token: string, messageId: string): Promise { try { const url = `https://www.googleapis.com/gmail/v1/users/me/messages/${messageId}?format=full`; const response = await fetch(url, { headers: { Authorization: `Bearer ${token}` }, }); if (!response.ok) return null; return await response.json(); } catch (error) { console.error('Fetch message error:', error); return null; } } // Save receipts to storage export async function saveReceipts(receipts: Receipt[]): Promise { const existing = await loadReceipts(); const merged = [...existing]; const seen = new Set(existing.map(r => r.id)); for (const receipt of receipts) { if (!seen.has(receipt.id)) { seen.add(receipt.id); merged.push(receipt); } } await chrome.storage.local.set({ [STORAGE_KEY]: merged }); } // Load receipts from storage export async function loadReceipts(): Promise { const result = await chrome.storage.local.get(STORAGE_KEY); return result[STORAGE_KEY] || []; } // Clear all receipts export async function clearReceipts(): Promise { await chrome.storage.local.remove(STORAGE_KEY); } // Listen for messages from popup chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => { (async () => { try { if (request.action === 'scanGmail') { const token = await getAuthToken(request.interactive ?? true); if (!token) { sendResponse({ success: false, error: 'Authentication failed' }); return; } const messages = await fetchReceiptEmails(token, request.maxResults || 50); const receipts: Receipt[] = []; for (const msg of messages.slice(0, 20)) { const details = await fetchMessageDetails(token, msg.id); if (details) { const receipt = extractReceiptFromEmail(details); if (receipt) receipts.push(receipt); } } await saveReceipts(receipts); sendResponse({ success: true, count: receipts.length, total: messages.length }); } else if (request.action === 'getReceipts') { const receipts = await loadReceipts(); sendResponse({ success: true, receipts }); } else if (request.action === 'clearReceipts') { await clearReceipts(); sendResponse({ success: true }); } else if (request.action === 'exportCSV') { const receipts = await loadReceipts(); const csv = exportToCSV(receipts); sendResponse({ success: true, csv }); } else if (request.action === 'getSummary') { const receipts = await loadReceipts(); const summary = generateTaxSummary(receipts); sendResponse({ success: true, summary, total: receipts.reduce((s, r) => s + r.amount, 0) }); } else { sendResponse({ success: false, error: 'Unknown action' }); } } catch (error) { sendResponse({ success: false, error: String(error) }); } })(); return true; // Keep channel open for async });