/** * Semble API Client Library * Handles authentication and communication with Semble backend */ const SEMBLE_API_URL = 'https://api.semble.so'; // For local development: 'http://127.0.0.1:3000' /** * Login with Bluesky app password to get Semble tokens * @param {string} identifier - User handle (e.g., user.bsky.social) * @param {string} password - Bluesky app password * @returns {Promise<{accessToken: string, refreshToken: string}>} */ async function createSession(identifier, password) { const response = await fetch(`${SEMBLE_API_URL}/api/users/login/app-password`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ identifier, appPassword: password, }), }); if (!response.ok) { const error = await response.json().catch(() => ({})); throw new Error(`Authentication failed: ${error.message || response.statusText}`); } return await response.json(); } /** * Refresh Semble access token * @param {string} refreshToken - Refresh token * @returns {Promise<{accessToken: string, refreshToken: string}>} */ async function refreshSession(refreshToken) { const response = await fetch(`${SEMBLE_API_URL}/api/users/oauth/refresh`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ refreshToken, }), }); if (!response.ok) { throw new Error('Token refresh failed'); } return await response.json(); } /** * Get user's Semble collections * @param {string} accessToken - Semble access token * @returns {Promise} */ async function listCollections(accessToken) { const response = await fetch(`${SEMBLE_API_URL}/api/collections`, { headers: { 'Authorization': `Bearer ${accessToken}`, }, }); if (!response.ok) { const error = await response.json().catch(() => ({})); throw new Error(`Failed to fetch collections: ${error.message || response.statusText}`); } const data = await response.json(); return data.collections || []; } /** * Add URL to library via Semble API (creates card, fetches metadata, adds to collections) * @param {string} accessToken - Semble access token * @param {string} url - URL to add * @param {string} [note] - Optional note * @param {string[]} [collectionIds] - Optional collection IDs to add card to * @returns {Promise<{urlCardId: string, noteCardId?: string}>} */ async function addUrlToLibrary(accessToken, url, note, collectionIds) { const payload = { url, }; if (note && note.trim()) { payload.note = note.trim(); } if (collectionIds && collectionIds.length > 0) { payload.collectionIds = collectionIds; } console.log('Adding URL to Semble library:', { url, hasNote: !!payload.note, collectionCount: collectionIds?.length || 0, }); const response = await fetch(`${SEMBLE_API_URL}/api/cards/library/urls`, { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify(payload), }); if (!response.ok) { const error = await response.json().catch(() => ({})); console.error('Failed to add URL to library:', { status: response.status, statusText: response.statusText, error: error, }); throw new Error(`Failed to add URL: ${error.message || response.statusText}`); } const result = await response.json(); console.log('URL added to Semble successfully:', result); return result; } // Legacy function names for compatibility - no longer used async function createRecord() { throw new Error('createRecord is deprecated - use addUrlToLibrary instead'); } async function createUrlCard() { throw new Error('createUrlCard is deprecated - use addUrlToLibrary instead'); } async function createNoteCard() { throw new Error('createNoteCard is deprecated - note is now part of addUrlToLibrary'); } async function createCollectionLink() { throw new Error('createCollectionLink is deprecated - collectionIds is now part of addUrlToLibrary'); } // Export functions for use in other scripts if (typeof module !== 'undefined' && module.exports) { module.exports = { createSession, refreshSession, listCollections, addUrlToLibrary, createRecord, createUrlCard, createNoteCard, createCollectionLink, }; }