/** * Background Service Worker * Handles session management and Semble API communication */ // Import Semble API client functions importScripts('../lib/atproto.js'); // Session state let session = null; /** * Initialize session on extension startup */ chrome.runtime.onStartup.addListener(async () => { await loadSession(); }); /** * Load session from storage */ async function loadSession() { try { const result = await chrome.storage.local.get(['session']); if (result.session) { session = result.session; console.log('Session loaded from storage'); } } catch (error) { console.error('Failed to load session:', error); } } /** * Save session to storage */ async function saveSession(sessionData) { try { session = sessionData; await chrome.storage.local.set({ session: sessionData }); console.log('Session saved to storage'); } catch (error) { console.error('Failed to save session:', error); throw error; } } /** * Clear session from storage */ async function clearSession() { try { session = null; await chrome.storage.local.remove('session'); console.log('Session cleared'); } catch (error) { console.error('Failed to clear session:', error); } } /** * Authenticate with Semble using Bluesky credentials */ async function authenticate(identifier, password) { try { const sessionData = await createSession(identifier, password); await saveSession(sessionData); return { success: true, session: sessionData }; } catch (error) { console.error('Authentication failed:', error); return { success: false, error: error.message }; } } /** * Ensure we have a valid session * Refreshes token if expired */ async function ensureValidSession() { if (!session) { await loadSession(); } if (!session) { throw new Error('Not authenticated. Please log in.'); } // TODO: Add token expiration check and refresh logic using refreshSession() // For now, we'll assume the token is valid return session; } /** * Get user's collections from Semble */ async function getCollections() { try { const session = await ensureValidSession(); const collections = await listCollections(session.accessToken); return { success: true, collections }; } catch (error) { console.error('Failed to fetch collections:', error); return { success: false, error: error.message }; } } /** * Save URL card to Semble collection */ async function saveCard(url, metadata, note, collectionId) { try { const session = await ensureValidSession(); // Semble API handles everything in one call: // - Fetches URL metadata // - Creates URL card // - Creates note card (if note provided) // - Adds to collection(s) // - Publishes to ATproto const collectionIds = collectionId ? [collectionId] : []; const result = await addUrlToLibrary( session.accessToken, url, note, collectionIds ); console.log('Card saved to Semble:', result); return { success: true, urlCardId: result.urlCardId, noteCardId: result.noteCardId, }; } catch (error) { console.error('Failed to save card:', error); return { success: false, error: error.message }; } } /** * Handle messages from popup and content scripts */ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { console.log('Background received message:', request); switch (request.action) { case 'authenticate': authenticate(request.identifier, request.password) .then(sendResponse); return true; // Keep channel open for async response case 'getSession': ensureValidSession() .then(session => sendResponse({ success: true, session })) .catch(error => sendResponse({ success: false, error: error.message })); return true; case 'clearSession': clearSession() .then(() => sendResponse({ success: true })) .catch(error => sendResponse({ success: false, error: error.message })); return true; case 'getCollections': getCollections().then(sendResponse); return true; case 'saveCard': saveCard( request.url, request.metadata, request.note, request.collectionId ).then(sendResponse); return true; default: console.warn('Unknown action:', request.action); sendResponse({ success: false, error: 'Unknown action' }); } }); // Load session on script initialization loadSession();