A Chrome extension to quickly capture URLs into Semble Collections at https://semble.so semble.so
at-proto semble chrome-extension
at main 159 lines 4.4 kB view raw
1/** 2 * Semble API Client Library 3 * Handles authentication and communication with Semble backend 4 */ 5 6const SEMBLE_API_URL = 'https://api.semble.so'; 7// For local development: 'http://127.0.0.1:3000' 8 9/** 10 * Login with Bluesky app password to get Semble tokens 11 * @param {string} identifier - User handle (e.g., user.bsky.social) 12 * @param {string} password - Bluesky app password 13 * @returns {Promise<{accessToken: string, refreshToken: string}>} 14 */ 15async function createSession(identifier, password) { 16 const response = await fetch(`${SEMBLE_API_URL}/api/users/login/app-password`, { 17 method: 'POST', 18 headers: { 19 'Content-Type': 'application/json', 20 }, 21 body: JSON.stringify({ 22 identifier, 23 appPassword: password, 24 }), 25 }); 26 27 if (!response.ok) { 28 const error = await response.json().catch(() => ({})); 29 throw new Error(`Authentication failed: ${error.message || response.statusText}`); 30 } 31 32 return await response.json(); 33} 34 35/** 36 * Refresh Semble access token 37 * @param {string} refreshToken - Refresh token 38 * @returns {Promise<{accessToken: string, refreshToken: string}>} 39 */ 40async function refreshSession(refreshToken) { 41 const response = await fetch(`${SEMBLE_API_URL}/api/users/oauth/refresh`, { 42 method: 'POST', 43 headers: { 44 'Content-Type': 'application/json', 45 }, 46 body: JSON.stringify({ 47 refreshToken, 48 }), 49 }); 50 51 if (!response.ok) { 52 throw new Error('Token refresh failed'); 53 } 54 55 return await response.json(); 56} 57 58/** 59 * Get user's Semble collections 60 * @param {string} accessToken - Semble access token 61 * @returns {Promise<Array>} 62 */ 63async function listCollections(accessToken) { 64 const response = await fetch(`${SEMBLE_API_URL}/api/collections`, { 65 headers: { 66 'Authorization': `Bearer ${accessToken}`, 67 }, 68 }); 69 70 if (!response.ok) { 71 const error = await response.json().catch(() => ({})); 72 throw new Error(`Failed to fetch collections: ${error.message || response.statusText}`); 73 } 74 75 const data = await response.json(); 76 return data.collections || []; 77} 78 79/** 80 * Add URL to library via Semble API (creates card, fetches metadata, adds to collections) 81 * @param {string} accessToken - Semble access token 82 * @param {string} url - URL to add 83 * @param {string} [note] - Optional note 84 * @param {string[]} [collectionIds] - Optional collection IDs to add card to 85 * @returns {Promise<{urlCardId: string, noteCardId?: string}>} 86 */ 87async function addUrlToLibrary(accessToken, url, note, collectionIds) { 88 const payload = { 89 url, 90 }; 91 92 if (note && note.trim()) { 93 payload.note = note.trim(); 94 } 95 96 if (collectionIds && collectionIds.length > 0) { 97 payload.collectionIds = collectionIds; 98 } 99 100 console.log('Adding URL to Semble library:', { 101 url, 102 hasNote: !!payload.note, 103 collectionCount: collectionIds?.length || 0, 104 }); 105 106 const response = await fetch(`${SEMBLE_API_URL}/api/cards/library/urls`, { 107 method: 'POST', 108 headers: { 109 'Authorization': `Bearer ${accessToken}`, 110 'Content-Type': 'application/json', 111 }, 112 body: JSON.stringify(payload), 113 }); 114 115 if (!response.ok) { 116 const error = await response.json().catch(() => ({})); 117 console.error('Failed to add URL to library:', { 118 status: response.status, 119 statusText: response.statusText, 120 error: error, 121 }); 122 throw new Error(`Failed to add URL: ${error.message || response.statusText}`); 123 } 124 125 const result = await response.json(); 126 console.log('URL added to Semble successfully:', result); 127 return result; 128} 129 130// Legacy function names for compatibility - no longer used 131async function createRecord() { 132 throw new Error('createRecord is deprecated - use addUrlToLibrary instead'); 133} 134 135async function createUrlCard() { 136 throw new Error('createUrlCard is deprecated - use addUrlToLibrary instead'); 137} 138 139async function createNoteCard() { 140 throw new Error('createNoteCard is deprecated - note is now part of addUrlToLibrary'); 141} 142 143async function createCollectionLink() { 144 throw new Error('createCollectionLink is deprecated - collectionIds is now part of addUrlToLibrary'); 145} 146 147// Export functions for use in other scripts 148if (typeof module !== 'undefined' && module.exports) { 149 module.exports = { 150 createSession, 151 refreshSession, 152 listCollections, 153 addUrlToLibrary, 154 createRecord, 155 createUrlCard, 156 createNoteCard, 157 createCollectionLink, 158 }; 159}