// Capacitor Bridge for Plugin Compass Mobile App // Connects to backend at plugincompass.com const BACKEND_BASE_URL = window.BACKEND_BASE_URL || 'https://plugincompass.com'; // Storage keys const STORAGE_KEYS = { USER: 'plugin_compass_user', TOKEN: 'plugin_compass_token', REFRESH_TOKEN: 'plugin_compass_refresh_token', SESSION: 'plugin_compass_session' }; let PreferencesImpl = null; async function getPreferences() { if (PreferencesImpl) return PreferencesImpl; try { const module = await import('@capacitor/preferences'); PreferencesImpl = module.Preferences; return PreferencesImpl; } catch { // Fallback to localStorage PreferencesImpl = { async get({ key }) { return { value: localStorage.getItem(key) }; }, async set({ key, value }) { localStorage.setItem(key, value); return; }, async remove({ key }) { localStorage.removeItem(key); return; }, async keys() { return { keys: Object.keys(localStorage) }; }, async clear() { localStorage.clear(); return; } }; return PreferencesImpl; } } export const Preferences = { async get(options) { const pref = await getPreferences(); return pref.get(options); }, async set(options) { const pref = await getPreferences(); return pref.set(options); }, async remove(options) { const pref = await getPreferences(); return pref.remove(options); }, async keys() { const pref = await getPreferences(); return pref.keys(); }, async clear() { const pref = await getPreferences(); return pref.clear(); } }; // Token management async function getAuthToken() { try { const { value } = await Preferences.get({ key: STORAGE_KEYS.TOKEN }); return value; } catch (e) { console.error('[AUTH] Failed to get token:', e); return null; } } async function setAuthToken(token) { try { await Preferences.set({ key: STORAGE_KEYS.TOKEN, value: token }); } catch (e) { console.error('[AUTH] Failed to set token:', e); } } async function getRefreshToken() { try { const { value } = await Preferences.get({ key: STORAGE_KEYS.REFRESH_TOKEN }); return value; } catch (e) { return null; } } async function setRefreshToken(token) { try { await Preferences.set({ key: STORAGE_KEYS.REFRESH_TOKEN, value: token }); } catch (e) { console.error('[AUTH] Failed to set refresh token:', e); } } async function clearAuth() { try { await Preferences.remove({ key: STORAGE_KEYS.TOKEN }); await Preferences.remove({ key: STORAGE_KEYS.REFRESH_TOKEN }); await Preferences.remove({ key: STORAGE_KEYS.USER }); await Preferences.remove({ key: STORAGE_KEYS.SESSION }); } catch (e) { console.error('[AUTH] Failed to clear auth:', e); } } // API client with authentication async function apiClient(endpoint, options = {}) { const url = endpoint.startsWith('http') ? endpoint : `${BACKEND_BASE_URL}${endpoint}`; const token = await getAuthToken(); const headers = { 'Content-Type': 'application/json', ...(token ? { 'Authorization': `Bearer ${token}` } : {}), ...(options.headers || {}) }; try { const response = await fetch(url, { ...options, headers, credentials: 'include' }); // Handle token expiration if (response.status === 401) { const refreshed = await refreshAccessToken(); if (refreshed) { // Retry with new token const newToken = await getAuthToken(); headers['Authorization'] = `Bearer ${newToken}`; const retryResponse = await fetch(url, { ...options, headers, credentials: 'include' }); return handleResponse(retryResponse); } else { // Token refresh failed, clear auth await clearAuth(); throw new Error('Session expired. Please sign in again.'); } } return handleResponse(response); } catch (error) { console.error(`[API] Request failed: ${endpoint}`, error); throw error; } } async function handleResponse(response) { const text = await response.text(); let json; try { json = text ? JSON.parse(text) : {}; } catch (parseErr) { throw new Error(`Invalid JSON response: ${parseErr.message}`); } if (!response.ok) { const err = new Error(json.error || json.message || `HTTP ${response.status}`); err.status = response.status; err.data = json; throw err; } return json; } async function refreshAccessToken() { try { const refreshToken = await getRefreshToken(); if (!refreshToken) return false; const response = await fetch(`${BACKEND_BASE_URL}/api/auth/refresh`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refreshToken }) }); if (!response.ok) { return false; } const data = await response.json(); if (data.token) { await setAuthToken(data.token); if (data.refreshToken) { await setRefreshToken(data.refreshToken); } return true; } return false; } catch (e) { console.error('[AUTH] Token refresh failed:', e); return false; } } // Authentication functions export async function loginWithEmail(email, password, remember = false) { try { const response = await apiClient('/api/login', { method: 'POST', body: JSON.stringify({ email, password, remember }) }); if (response.ok && response.user && response.token) { // Store tokens await setAuthToken(response.token); if (response.refreshToken) { await setRefreshToken(response.refreshToken); } // Store user info await Preferences.set({ key: STORAGE_KEYS.USER, value: JSON.stringify(response.user) }); return { success: true, user: response.user }; } else { return { success: false, error: response.error || 'Login failed' }; } } catch (error) { console.error('[AUTH] Login error:', error); return { success: false, error: error.message || 'Login failed' }; } } export async function signupWithEmail(email, password) { try { const response = await apiClient('/api/signup', { method: 'POST', body: JSON.stringify({ email, password }) }); if (response.ok && response.user && response.token) { await setAuthToken(response.token); if (response.refreshToken) { await setRefreshToken(response.refreshToken); } await Preferences.set({ key: STORAGE_KEYS.USER, value: JSON.stringify(response.user) }); return { success: true, user: response.user }; } else { return { success: false, error: response.error || 'Signup failed' }; } } catch (error) { console.error('[AUTH] Signup error:', error); return { success: false, error: error.message || 'Signup failed' }; } } export async function logout() { try { // Call logout endpoint if available await apiClient('/api/logout', { method: 'POST', headers: {} }).catch(() => {}); } finally { await clearAuth(); } } export async function getCurrentUser() { try { // First check stored user const { value } = await Preferences.get({ key: STORAGE_KEYS.USER }); if (value) { const user = JSON.parse(value); // Verify token is still valid by fetching fresh user data try { const freshUser = await apiClient('/api/me'); if (freshUser) { await Preferences.set({ key: STORAGE_KEYS.USER, value: JSON.stringify(freshUser) }); return freshUser; } } catch (e) { console.warn('[AUTH] Could not refresh user data:', e.message); } return user; } return null; } catch (e) { console.error('[AUTH] Failed to get current user:', e); return null; } } export async function isAuthenticated() { const token = await getAuthToken(); if (!token) return false; const user = await getCurrentUser(); return !!user; } // OAuth helpers export function getOAuthUrl(provider, redirectUri = null) { const params = new URLSearchParams(); if (redirectUri) { params.set('redirect_uri', redirectUri); } params.set('mobile', 'true'); return `${BACKEND_BASE_URL}/auth/${provider}?${params.toString()}`; } export async function handleOAuthCallback(provider, code, state) { try { const response = await apiClient(`/auth/${provider}/callback`, { method: 'POST', body: JSON.stringify({ code, state }) }); if (response.token) { await setAuthToken(response.token); if (response.refreshToken) { await setRefreshToken(response.refreshToken); } if (response.user) { await Preferences.set({ key: STORAGE_KEYS.USER, value: JSON.stringify(response.user) }); } return { success: true, user: response.user }; } else { return { success: false, error: response.error || 'OAuth failed' }; } } catch (error) { console.error('[AUTH] OAuth error:', error); return { success: false, error: error.message || 'OAuth failed' }; } } // Plugin/App management export async function getUserApps() { try { const apps = await apiClient('/api/apps'); return { success: true, apps: apps || [] }; } catch (error) { console.error('[APPS] Failed to load apps:', error); return { success: false, error: error.message, apps: [] }; } } export async function getApp(appId) { try { const app = await apiClient(`/api/apps/${appId}`); return { success: true, app }; } catch (error) { console.error('[APPS] Failed to load app:', error); return { success: false, error: error.message }; } } export async function createApp(appData) { try { const app = await apiClient('/api/apps', { method: 'POST', body: JSON.stringify(appData) }); return { success: true, app }; } catch (error) { console.error('[APPS] Failed to create app:', error); return { success: false, error: error.message }; } } export async function updateApp(appId, appData) { try { const app = await apiClient(`/api/apps/${appId}`, { method: 'PUT', body: JSON.stringify(appData) }); return { success: true, app }; } catch (error) { console.error('[APPS] Failed to update app:', error); return { success: false, error: error.message }; } } export async function deleteApp(appId) { try { await apiClient(`/api/apps/${appId}`, { method: 'DELETE' }); return { success: true }; } catch (error) { console.error('[APPS] Failed to delete app:', error); return { success: false, error: error.message }; } } export async function syncApp(appId) { if (!appId || appId.trim() === '') { throw new Error('appId is required'); } try { // Get app from backend instead of local storage const result = await getApp(appId); if (!result.success || !result.app) { throw new Error(`App not found: ${appId}`); } // Sync with backend const response = await apiClient('/desktop/apps/sync', { method: 'POST', body: JSON.stringify(result.app) }); return response; } catch (error) { console.error('[SYNC] Failed to sync app:', error); throw error; } } export async function runOpencodeTask(appId, taskName, args = []) { if (!appId || appId.trim() === '') { throw new Error('appId is required'); } if (!taskName || taskName.trim() === '') { throw new Error('taskName is required'); } throw new Error('OpenCode execution is not supported on mobile devices. Please use the desktop app for this feature.'); } // Session management export async function getSessions() { try { const sessions = await apiClient('/api/sessions'); return { success: true, sessions: sessions || [] }; } catch (error) { console.error('[SESSIONS] Failed to load sessions:', error); return { success: false, error: error.message, sessions: [] }; } } export async function getSession(sessionId) { try { const session = await apiClient(`/api/sessions/${sessionId}`); return { success: true, session }; } catch (error) { console.error('[SESSIONS] Failed to load session:', error); return { success: false, error: error.message }; } } export async function createSession(sessionData) { try { const session = await apiClient('/api/sessions', { method: 'POST', body: JSON.stringify(sessionData) }); return { success: true, session }; } catch (error) { console.error('[SESSIONS] Failed to create session:', error); return { success: false, error: error.message }; } } export async function sendMessage(sessionId, messageData) { try { const result = await apiClient(`/api/sessions/${sessionId}/messages`, { method: 'POST', body: JSON.stringify(messageData) }); return { success: true, ...result }; } catch (error) { console.error('[MESSAGES] Failed to send message:', error); return { success: false, error: error.message }; } } // User account export async function getUserAccount() { try { const account = await apiClient('/api/account'); return { success: true, account }; } catch (error) { console.error('[ACCOUNT] Failed to load account:', error); return { success: false, error: error.message }; } } export async function updateUserAccount(accountData) { try { const account = await apiClient('/api/account', { method: 'PUT', body: JSON.stringify(accountData) }); return { success: true, account }; } catch (error) { console.error('[ACCOUNT] Failed to update account:', error); return { success: false, error: error.message }; } } export async function getUsageSummary() { try { const usage = await apiClient('/api/account/usage'); return { success: true, usage }; } catch (error) { console.error('[USAGE] Failed to load usage:', error); return { success: false, error: error.message }; } } // Legacy exports for backward compatibility window.nativeBridge = { Preferences, syncApp, runOpencodeTask, loginWithEmail, signupWithEmail, logout, getCurrentUser, isAuthenticated, getUserApps, getApp, createApp, updateApp, deleteApp, getSessions, createSession, sendMessage, getOAuthUrl, handleOAuthCallback, getUserAccount, updateUserAccount, getUsageSummary, apiClient }; // Expose all functions globally for the web UI Object.assign(window, { loginWithEmail, signupWithEmail, logout, getCurrentUser, isAuthenticated, getUserApps, getApp, createApp, updateApp, deleteApp, getSessions, getSession, createSession, sendMessage, getOAuthUrl, handleOAuthCallback, getUserAccount, updateUserAccount, getUsageSummary, apiClient }); export { loginWithEmail, signupWithEmail, logout, getCurrentUser, isAuthenticated, getUserApps, getApp, createApp, updateApp, deleteApp, getSessions, getSession, createSession, sendMessage, getOAuthUrl, handleOAuthCallback, getUserAccount, updateUserAccount, getUsageSummary, apiClient, BACKEND_BASE_URL, STORAGE_KEYS };