A Chrome extension to quickly capture URLs into Semble Collections at https://semble.so semble.so
at-proto semble chrome-extension
at main 187 lines 4.6 kB view raw
1/** 2 * Background Service Worker 3 * Handles session management and Semble API communication 4 */ 5 6// Import Semble API client functions 7importScripts('../lib/atproto.js'); 8 9// Session state 10let session = null; 11 12/** 13 * Initialize session on extension startup 14 */ 15chrome.runtime.onStartup.addListener(async () => { 16 await loadSession(); 17}); 18 19/** 20 * Load session from storage 21 */ 22async function loadSession() { 23 try { 24 const result = await chrome.storage.local.get(['session']); 25 if (result.session) { 26 session = result.session; 27 console.log('Session loaded from storage'); 28 } 29 } catch (error) { 30 console.error('Failed to load session:', error); 31 } 32} 33 34/** 35 * Save session to storage 36 */ 37async function saveSession(sessionData) { 38 try { 39 session = sessionData; 40 await chrome.storage.local.set({ session: sessionData }); 41 console.log('Session saved to storage'); 42 } catch (error) { 43 console.error('Failed to save session:', error); 44 throw error; 45 } 46} 47 48/** 49 * Clear session from storage 50 */ 51async function clearSession() { 52 try { 53 session = null; 54 await chrome.storage.local.remove('session'); 55 console.log('Session cleared'); 56 } catch (error) { 57 console.error('Failed to clear session:', error); 58 } 59} 60 61/** 62 * Authenticate with Semble using Bluesky credentials 63 */ 64async function authenticate(identifier, password) { 65 try { 66 const sessionData = await createSession(identifier, password); 67 await saveSession(sessionData); 68 return { success: true, session: sessionData }; 69 } catch (error) { 70 console.error('Authentication failed:', error); 71 return { success: false, error: error.message }; 72 } 73} 74 75/** 76 * Ensure we have a valid session 77 * Refreshes token if expired 78 */ 79async function ensureValidSession() { 80 if (!session) { 81 await loadSession(); 82 } 83 84 if (!session) { 85 throw new Error('Not authenticated. Please log in.'); 86 } 87 88 // TODO: Add token expiration check and refresh logic using refreshSession() 89 // For now, we'll assume the token is valid 90 91 return session; 92} 93 94/** 95 * Get user's collections from Semble 96 */ 97async function getCollections() { 98 try { 99 const session = await ensureValidSession(); 100 const collections = await listCollections(session.accessToken); 101 return { success: true, collections }; 102 } catch (error) { 103 console.error('Failed to fetch collections:', error); 104 return { success: false, error: error.message }; 105 } 106} 107 108/** 109 * Save URL card to Semble collection 110 */ 111async function saveCard(url, metadata, note, collectionId) { 112 try { 113 const session = await ensureValidSession(); 114 115 // Semble API handles everything in one call: 116 // - Fetches URL metadata 117 // - Creates URL card 118 // - Creates note card (if note provided) 119 // - Adds to collection(s) 120 // - Publishes to ATproto 121 const collectionIds = collectionId ? [collectionId] : []; 122 123 const result = await addUrlToLibrary( 124 session.accessToken, 125 url, 126 note, 127 collectionIds 128 ); 129 130 console.log('Card saved to Semble:', result); 131 132 return { 133 success: true, 134 urlCardId: result.urlCardId, 135 noteCardId: result.noteCardId, 136 }; 137 } catch (error) { 138 console.error('Failed to save card:', error); 139 return { success: false, error: error.message }; 140 } 141} 142 143/** 144 * Handle messages from popup and content scripts 145 */ 146chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { 147 console.log('Background received message:', request); 148 149 switch (request.action) { 150 case 'authenticate': 151 authenticate(request.identifier, request.password) 152 .then(sendResponse); 153 return true; // Keep channel open for async response 154 155 case 'getSession': 156 ensureValidSession() 157 .then(session => sendResponse({ success: true, session })) 158 .catch(error => sendResponse({ success: false, error: error.message })); 159 return true; 160 161 case 'clearSession': 162 clearSession() 163 .then(() => sendResponse({ success: true })) 164 .catch(error => sendResponse({ success: false, error: error.message })); 165 return true; 166 167 case 'getCollections': 168 getCollections().then(sendResponse); 169 return true; 170 171 case 'saveCard': 172 saveCard( 173 request.url, 174 request.metadata, 175 request.note, 176 request.collectionId 177 ).then(sendResponse); 178 return true; 179 180 default: 181 console.warn('Unknown action:', request.action); 182 sendResponse({ success: false, error: 'Unknown action' }); 183 } 184}); 185 186// Load session on script initialization 187loadSession();