const DEFAULT_HOST = 'https://rspace.online'; // --- Helpers --- async function getSettings() { const result = await chrome.storage.sync.get(['rspaceHost', 'rspaceSlug']); return { host: result.rspaceHost || DEFAULT_HOST, slug: result.rspaceSlug || '', }; } function apiBase(settings) { return settings.slug ? `${settings.host}/${settings.slug}/rnotes` : `${settings.host}/rnotes`; } function decodeToken(token) { try { const payload = JSON.parse(atob(token.split('.')[1])); if (payload.exp && payload.exp * 1000 < Date.now()) return null; return payload; } catch { return null; } } function showStatus(message, type) { const el = document.getElementById('status'); el.textContent = message; el.className = `status ${type}`; if (type === 'success') { setTimeout(() => { el.className = 'status'; }, 3000); } } // --- Auth UI --- async function updateAuthUI() { const { encryptid_token } = await chrome.storage.local.get(['encryptid_token']); const claims = encryptid_token ? decodeToken(encryptid_token) : null; const authStatus = document.getElementById('authStatus'); const loginSection = document.getElementById('loginSection'); const loggedInSection = document.getElementById('loggedInSection'); if (claims) { const username = claims.username || claims.sub?.slice(0, 20) || 'Authenticated'; authStatus.textContent = `Signed in as ${username}`; authStatus.className = 'auth-status authed'; loginSection.style.display = 'none'; loggedInSection.style.display = 'block'; } else { authStatus.textContent = 'Not signed in'; authStatus.className = 'auth-status not-authed'; loginSection.style.display = 'block'; loggedInSection.style.display = 'none'; } } async function populateNotebooks() { const { encryptid_token } = await chrome.storage.local.get(['encryptid_token']); if (!encryptid_token) return; const settings = await getSettings(); if (!settings.slug) return; try { const response = await fetch(`${apiBase(settings)}/api/notebooks`, { headers: { 'Authorization': `Bearer ${encryptid_token}` }, }); if (!response.ok) return; const data = await response.json(); const notebooks = data.notebooks || (Array.isArray(data) ? data : []); const select = document.getElementById('defaultNotebook'); // Clear existing options (keep first) while (select.options.length > 1) { select.remove(1); } for (const nb of notebooks) { const option = document.createElement('option'); option.value = nb.id; option.textContent = nb.title; select.appendChild(option); } // Restore saved default const { lastNotebookId } = await chrome.storage.local.get(['lastNotebookId']); if (lastNotebookId) select.value = lastNotebookId; } catch (err) { console.error('Failed to load notebooks:', err); } } // --- Load settings --- async function loadSettings() { const result = await chrome.storage.sync.get(['rspaceHost', 'rspaceSlug']); document.getElementById('host').value = result.rspaceHost || DEFAULT_HOST; document.getElementById('slug').value = result.rspaceSlug || ''; await updateAuthUI(); await populateNotebooks(); } // --- Event handlers --- // Open rSpace sign-in document.getElementById('openSigninBtn').addEventListener('click', () => { const host = document.getElementById('host').value.replace(/\/+$/, '') || DEFAULT_HOST; const slug = document.getElementById('slug').value.trim(); const signinUrl = slug ? `${host}/${slug}/auth/signin?extension=true` : `${host}/auth/signin?extension=true`; chrome.tabs.create({ url: signinUrl }); }); // Save token document.getElementById('saveTokenBtn').addEventListener('click', async () => { const tokenInput = document.getElementById('tokenInput').value.trim(); if (!tokenInput) { showStatus('Please paste a token', 'error'); return; } const claims = decodeToken(tokenInput); if (!claims) { showStatus('Invalid or expired token', 'error'); return; } await chrome.storage.local.set({ encryptid_token: tokenInput }); document.getElementById('tokenInput').value = ''; showStatus(`Signed in as ${claims.username || claims.sub}`, 'success'); await updateAuthUI(); await populateNotebooks(); }); // Logout document.getElementById('logoutBtn').addEventListener('click', async () => { await chrome.storage.local.remove(['encryptid_token']); showStatus('Signed out', 'success'); await updateAuthUI(); }); // Save settings document.getElementById('saveBtn').addEventListener('click', async () => { const host = document.getElementById('host').value.trim().replace(/\/+$/, ''); const slug = document.getElementById('slug').value.trim(); const notebookId = document.getElementById('defaultNotebook').value; await chrome.storage.sync.set({ rspaceHost: host || DEFAULT_HOST, rspaceSlug: slug, }); await chrome.storage.local.set({ lastNotebookId: notebookId }); showStatus('Settings saved', 'success'); }); // Test connection document.getElementById('testBtn').addEventListener('click', async () => { const settings = { host: document.getElementById('host').value.trim().replace(/\/+$/, '') || DEFAULT_HOST, slug: document.getElementById('slug').value.trim(), }; if (!settings.slug) { showStatus('Configure a space slug first', 'error'); return; } const { encryptid_token } = await chrome.storage.local.get(['encryptid_token']); try { const headers = {}; if (encryptid_token) { headers['Authorization'] = `Bearer ${encryptid_token}`; } const response = await fetch(`${apiBase(settings)}/api/notebooks`, { headers }); if (response.ok) { const data = await response.json(); const notebooks = data.notebooks || (Array.isArray(data) ? data : []); showStatus(`Connected! Found ${notebooks.length} notebooks.`, 'success'); } else if (response.status === 401) { showStatus('Connected but not authenticated. Sign in first.', 'error'); } else { showStatus(`Connection failed: ${response.status}`, 'error'); } } catch (err) { showStatus(`Cannot connect: ${err.message}`, 'error'); } }); // Default notebook change document.getElementById('defaultNotebook').addEventListener('change', async (e) => { await chrome.storage.local.set({ lastNotebookId: e.target.value }); }); // Init document.addEventListener('DOMContentLoaded', loadSettings);