// Copyright (c) 2026 Gordon Bolton. MIT License. import { state } from './state.js'; export async function checkAuthentication() { try { // Fetch both auth state and a fresh CSRF token in parallel const [meRes, csrfRes] = await Promise.all([ fetch('/api/auth/me'), fetch('/api/auth/csrf') ]); const data = await meRes.json(); const csrfData = await csrfRes.json(); if (csrfData.csrfToken) state.csrfToken = csrfData.csrfToken; if (data.authenticated) { state.currentUser = data.user; state.isAdmin = !!data.user.isAdmin; return { authenticated: true, user: data.user }; } else { return { authenticated: false }; } } catch (err) { console.error('Authentication check failed:', err); return { authenticated: false }; } } export async function handleLogin(username, password, rememberMe) { try { const response = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password, rememberMe }) }); const data = await response.json(); if (data.success) { state.currentUser = data.user; state.isAdmin = !!data.user.isAdmin; // Store CSRF token returned by login for use in subsequent requests if (data.csrfToken) state.csrfToken = data.csrfToken; return { success: true, user: data.user }; } else { return { success: false, error: data.error || 'Login failed' }; } } catch (err) { console.error(err); return { success: false, error: 'Login failed. Please try again.' }; } } export async function handleLogout() { try { await fetch('/api/auth/logout', { method: 'POST', headers: state.csrfToken ? { 'X-CSRF-Token': state.csrfToken } : {} }); state.currentUser = null; state.csrfToken = null; return { success: true }; } catch (err) { console.error('Logout failed:', err); return { success: false }; } } export async function loadHistory(forceRefresh = false) { try { const params = new URLSearchParams({ days: state.historyDays }); if (state.showAll) params.set('showAll', 'true'); if (forceRefresh) params.set('_t', Date.now()); const res = await fetch(`/api/history/recent?${params}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); return { success: true, history: data.history || [] }; } catch (err) { console.error('[History] Load error:', err); return { success: false, error: 'Failed to load history.' }; } } export async function handleBlocklistSearch(download) { try { const res = await fetch('/api/dashboard/blocklist-search', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': state.csrfToken }, body: JSON.stringify({ arrQueueId: download.arrQueueId, arrType: download.arrType, arrInstanceUrl: download.arrInstanceUrl, arrInstanceKey: download.arrInstanceKey, arrContentId: download.arrContentId, arrContentIds: download.arrContentIds, arrSeriesId: download.arrSeriesId, arrContentType: download.arrContentType }) }); if (!res.ok) { const data = await res.json().catch(() => ({})); throw new Error(data.error || `HTTP ${res.status}`); } return { success: true }; } catch (err) { console.error('[Blocklist] Error:', err); throw err; } } export async function loadAppVersion() { try { const res = await fetch('/health'); const data = await res.json(); return data.version || null; } catch (err) { return null; } } export async function fetchWebhookMetrics() { try { const res = await fetch('/api/dashboard/webhook-metrics'); if (!res.ok) return null; return await res.json(); } catch (err) { return null; } } export async function fetchWebhookStatus() { try { // Fetch metrics in parallel const metricsPromise = fetchWebhookMetrics(); // Fetch webhook configuration status (checks SOFARR_BASE_URL and SOFARR_WEBHOOK_SECRET) let webhookConfigValid = false; try { const configRes = await fetch('/api/webhook/config'); if (configRes.ok) { const configData = await configRes.json(); webhookConfigValid = configData.valid || false; } } catch (err) { // Config endpoint not available, assume invalid } // Fetch Sonarr notifications let sonarrEnabled = false; let sonarrTriggers = { onGrab: false, onDownload: false, onImport: false, onUpgrade: false }; try { const sonarrRes = await fetch('/api/sonarr/notifications'); if (sonarrRes.ok) { const sonarrData = await sonarrRes.json(); const sonarrSofarr = sonarrData.find(n => n.name === 'Sofarr'); sonarrEnabled = webhookConfigValid && !!sonarrSofarr; if (sonarrSofarr) { sonarrTriggers = { onGrab: sonarrSofarr.onGrab, onDownload: sonarrSofarr.onDownload, onImport: sonarrSofarr.onImport, onUpgrade: sonarrSofarr.onUpgrade }; } } } catch (err) { // Sonarr not configured } // Fetch Radarr notifications let radarrEnabled = false; let radarrTriggers = { onGrab: false, onDownload: false, onImport: false, onUpgrade: false }; try { const radarrRes = await fetch('/api/radarr/notifications'); if (radarrRes.ok) { const radarrData = await radarrRes.json(); const radarrSofarr = radarrData.find(n => n.name === 'Sofarr'); radarrEnabled = webhookConfigValid && !!radarrSofarr; if (radarrSofarr) { radarrTriggers = { onGrab: radarrSofarr.onGrab, onDownload: radarrSofarr.onDownload, onImport: radarrSofarr.onImport, onUpgrade: radarrSofarr.onUpgrade }; } } } catch (err) { // Radarr not configured } // Fetch Ombi webhook status let ombiEnabled = false; let ombiTriggers = { requestAvailable: false, requestApproved: false, requestDeclined: false, requestPending: false, requestProcessing: false }; let ombiStats = null; try { const ombiRes = await fetch('/api/ombi/webhook/status'); if (ombiRes.ok) { const ombiData = await ombiRes.json(); ombiEnabled = ombiData.enabled || false; ombiTriggers = ombiData.triggers || { requestAvailable: false, requestApproved: false, requestDeclined: false, requestPending: false, requestProcessing: false }; ombiStats = ombiData.stats || null; } } catch (err) { // Ombi not configured } state.webhookMetrics = await metricsPromise; // Find instance stats const instanceEntries = state.webhookMetrics ? Object.entries(state.webhookMetrics.instances || {}) : []; const sonarrStats = instanceEntries.find(([url]) => url.includes('sonarr'))?.[1] || null; const radarrStats = instanceEntries.find(([url]) => url.includes('radarr'))?.[1] || null; state.sonarrWebhook = { enabled: sonarrEnabled, triggers: sonarrTriggers, stats: sonarrStats }; state.radarrWebhook = { enabled: radarrEnabled, triggers: radarrTriggers, stats: radarrStats }; state.ombiWebhook = { enabled: ombiEnabled, triggers: ombiTriggers, stats: ombiStats }; return { success: true }; } catch (err) { console.error('Failed to fetch webhook status:', err); return { success: false }; } } export async function enableSonarrWebhook() { try { const res = await fetch('/api/sonarr/notifications/sofarr-webhook', { method: 'POST', headers: { 'X-CSRF-Token': state.csrfToken || '' } }); if (!res.ok) throw new Error('Failed to enable'); await fetchWebhookStatus(); return { success: true }; } catch (err) { console.error('Failed to enable Sonarr webhook:', err); return { success: false, error: err.message }; } } export async function enableRadarrWebhook() { try { const res = await fetch('/api/radarr/notifications/sofarr-webhook', { method: 'POST', headers: { 'X-CSRF-Token': state.csrfToken || '' } }); if (!res.ok) throw new Error('Failed to enable'); await fetchWebhookStatus(); return { success: true }; } catch (err) { console.error('Failed to enable Radarr webhook:', err); return { success: false, error: err.message }; } } export async function testSonarrWebhook() { try { const sonarrRes = await fetch('/api/sonarr/notifications'); if (!sonarrRes.ok) throw new Error('Failed to fetch notifications'); const sonarrData = await sonarrRes.json(); const sonarrSofarr = sonarrData.find(n => n.name === 'Sofarr'); if (!sonarrSofarr) throw new Error('Sofarr webhook not found'); const res = await fetch('/api/sonarr/notifications/test', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': state.csrfToken || '' }, body: JSON.stringify(sonarrSofarr) }); if (!res.ok) throw new Error('Test failed'); await fetchWebhookStatus(); return { success: true }; } catch (err) { console.error('Failed to test Sonarr webhook:', err); return { success: false, error: err.message }; } } export async function testRadarrWebhook() { try { const radarrRes = await fetch('/api/radarr/notifications'); if (!radarrRes.ok) throw new Error('Failed to fetch notifications'); const radarrData = await radarrRes.json(); const radarrSofarr = radarrData.find(n => n.name === 'Sofarr'); if (!radarrSofarr) throw new Error('Sofarr webhook not found'); const res = await fetch('/api/radarr/notifications/test', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': state.csrfToken || '' }, body: JSON.stringify(radarrSofarr) }); if (!res.ok) throw new Error('Test failed'); await fetchWebhookStatus(); return { success: true }; } catch (err) { console.error('Failed to test Radarr webhook:', err); return { success: false, error: err.message }; } } export async function enableOmbiWebhook() { try { const res = await fetch('/api/ombi/webhook/enable', { method: 'POST', headers: { 'X-CSRF-Token': state.csrfToken || '' } }); if (!res.ok) throw new Error('Failed to enable'); await fetchWebhookStatus(); return { success: true }; } catch (err) { console.error('Failed to enable Ombi webhook:', err); return { success: false, error: err.message }; } } export async function testOmbiWebhook() { try { const res = await fetch('/api/ombi/webhook/test', { method: 'POST', headers: { 'X-CSRF-Token': state.csrfToken || '' } }); if (!res.ok) throw new Error('Test failed'); await fetchWebhookStatus(); return { success: true }; } catch (err) { console.error('Failed to test Ombi webhook:', err); return { success: false, error: err.message }; } } export async function refreshStatusPanel() { try { const res = await fetch('/api/status'); if (!res.ok) throw new Error('Failed to fetch status: ' + res.status); const data = await res.json(); return { success: true, data }; } catch (err) { console.error('[Status] Error fetching status:', err); return { success: false, error: err.message }; } }