diff --git a/android-app/capacitor-bridge.js b/android-app/capacitor-bridge.js index 390cb00..9b8b956 100644 --- a/android-app/capacitor-bridge.js +++ b/android-app/capacitor-bridge.js @@ -116,9 +116,24 @@ async function clearAuth() { } } -// API client with authentication +// API client with authentication and timeout +const REQUEST_TIMEOUT = 10000; // 10 second timeout + +function fetchWithTimeout(url, options, timeout = REQUEST_TIMEOUT) { + return Promise.race([ + fetch(url, options), + new Promise((_, reject) => + setTimeout(() => reject(new Error(`Request timeout after ${timeout}ms`)), timeout) + ) + ]); +} + async function apiClient(endpoint, options = {}) { const url = endpoint.startsWith('http') ? endpoint : `${BACKEND_BASE_URL}${endpoint}`; + const requestId = Math.random().toString(36).substring(7); + + console.log(`[API:${requestId}] Starting request to ${endpoint}`); + console.log(`[API:${requestId}] Full URL: ${url}`); const token = await getAuthToken(); const headers = { @@ -128,20 +143,28 @@ async function apiClient(endpoint, options = {}) { }; try { - const response = await fetch(url, { + console.log(`[API:${requestId}] Sending ${options.method || 'GET'} request...`); + const startTime = Date.now(); + + const response = await fetchWithTimeout(url, { ...options, headers, credentials: 'include' }); + + const duration = Date.now() - startTime; + console.log(`[API:${requestId}] Response received in ${duration}ms - Status: ${response.status}`); // Handle token expiration if (response.status === 401) { + console.log(`[API:${requestId}] Token expired, attempting refresh...`); const refreshed = await refreshAccessToken(); if (refreshed) { + console.log(`[API:${requestId}] Token refreshed, retrying request...`); // Retry with new token const newToken = await getAuthToken(); headers['Authorization'] = `Bearer ${newToken}`; - const retryResponse = await fetch(url, { + const retryResponse = await fetchWithTimeout(url, { ...options, headers, credentials: 'include' @@ -149,6 +172,7 @@ async function apiClient(endpoint, options = {}) { return handleResponse(retryResponse); } else { // Token refresh failed, clear auth + console.error(`[API:${requestId}] Token refresh failed`); await clearAuth(); throw new Error('Session expired. Please sign in again.'); } @@ -156,7 +180,12 @@ async function apiClient(endpoint, options = {}) { return handleResponse(response); } catch (error) { - console.error(`[API] Request failed: ${endpoint}`, error); + console.error(`[API:${requestId}] Request failed: ${endpoint}`, error); + console.error(`[API:${requestId}] Error details:`, { + message: error.message, + name: error.name, + stack: error.stack + }); throw error; } } @@ -282,16 +311,21 @@ export async function logout() { } export async function getCurrentUser() { + console.log('[AUTH] Getting current user...'); try { // First check stored user + console.log('[AUTH] Checking stored user...'); const { value } = await Preferences.get({ key: STORAGE_KEYS.USER }); if (value) { const user = JSON.parse(value); + console.log('[AUTH] Found stored user:', user.email || user.id); // Verify token is still valid by fetching fresh user data try { + console.log('[AUTH] Fetching fresh user data from /api/me...'); const freshUser = await apiClient('/api/me'); if (freshUser) { + console.log('[AUTH] Fresh user data received:', freshUser.email || freshUser.id); await Preferences.set({ key: STORAGE_KEYS.USER, value: JSON.stringify(freshUser) @@ -300,23 +334,39 @@ export async function getCurrentUser() { } } catch (e) { console.warn('[AUTH] Could not refresh user data:', e.message); + console.log('[AUTH] Falling back to cached user data'); } return user; } + console.log('[AUTH] No stored user found'); return null; } catch (e) { console.error('[AUTH] Failed to get current user:', e); + console.error('[AUTH] Error details:', { + message: e.message, + name: e.name, + stack: e.stack + }); return null; } } export async function isAuthenticated() { + console.log('[AUTH] Checking authentication status...'); const token = await getAuthToken(); - if (!token) return false; + console.log('[AUTH] Token exists:', !!token); + if (!token) { + console.log('[AUTH] No token found, user is not authenticated'); + return false; + } + + console.log('[AUTH] Token found, fetching current user...'); const user = await getCurrentUser(); - return !!user; + const isAuth = !!user; + console.log('[AUTH] Authentication result:', isAuth, user ? `(User: ${user.email || user.id})` : ''); + return isAuth; } // OAuth helpers diff --git a/android-app/scripts/sync-ui.js b/android-app/scripts/sync-ui.js index 55b1e30..18c9326 100644 --- a/android-app/scripts/sync-ui.js +++ b/android-app/scripts/sync-ui.js @@ -610,28 +610,48 @@ async function createMobileIndex() { // Initialize app async function initApp() { - console.log('[APP] Initializing...'); + console.log('[APP] ==========================================='); + console.log('[APP] Starting app initialization...'); + console.log('[APP] Current time:', new Date().toISOString()); + console.log('[APP] User agent:', navigator.userAgent); + console.log('[APP] Platform:', navigator.platform); try { - // Check if user is authenticated + console.log('[APP] Step 1/4: Checking authentication...'); const authenticated = await isAuthenticated(); - console.log('[APP] Authenticated:', authenticated); + console.log('[APP] Step 1/4 complete - Authenticated:', authenticated); if (authenticated) { + console.log('[APP] Step 2/4: Getting current user...'); const user = await getCurrentUser(); + console.log('[APP] Step 2/4 complete - User:', user ? user.email || user.id : 'null'); + + console.log('[APP] Step 3/4: Checking onboarding status...'); const onboardingDone = await isOnboardingComplete(); + console.log('[APP] Step 3/4 complete - Onboarding done:', onboardingDone); if (!onboardingDone) { + console.log('[APP] Step 4/4: Showing onboarding screen'); showScreen('onboarding'); } else { + console.log('[APP] Step 4/4: Showing main screen'); showScreen('main'); updateWelcome(user); } } else { + console.log('[APP] Step 2/2: User not authenticated, showing auth screen'); showScreen('auth'); } + + console.log('[APP] ==========================================='); + console.log('[APP] Initialization complete!'); } catch (error) { - console.error('[APP] Initialization error:', error); + console.error('[APP] ==========================================='); + console.error('[APP] FATAL: Initialization error:', error); + console.error('[APP] Error name:', error.name); + console.error('[APP] Error message:', error.message); + console.error('[APP] Error stack:', error.stack); + console.error('[APP] ==========================================='); showScreen('auth'); } } @@ -747,6 +767,10 @@ async function createMobileIndex() { // Event listeners document.addEventListener('DOMContentLoaded', () => { + console.log('[APP] ==========================================='); + console.log('[APP] DOMContentLoaded event fired!'); + console.log('[APP] Starting app in 500ms...'); + console.log('[APP] ==========================================='); // Auth form - Sign In const authSubmitBtn = document.getElementById('auth-submit'); const authSignupBtn = document.getElementById('auth-signup'); diff --git a/chat/server.js b/chat/server.js index 14e204d..ef07bed 100644 --- a/chat/server.js +++ b/chat/server.js @@ -7962,7 +7962,14 @@ async function extractZipToWorkspace(buffer, workspaceDir) { } -function sendJson(res, statusCode, payload) { res.writeHead(statusCode, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(payload)); } +function sendJson(res, statusCode, payload) { + // CORS headers are already set in route(), but ensure they're preserved + const headers = { + 'Content-Type': 'application/json' + }; + res.writeHead(statusCode, headers); + res.end(JSON.stringify(payload)); +} function serveFile(res, filePath, contentType = 'text/html') { return fs.readFile(filePath).then((content) => { res.writeHead(200, { 'Content-Type': contentType }); res.end(content); }).catch(() => { res.writeHead(404); res.end('Not found'); }); } function guessContentType(filePath) { const ext = path.extname(filePath); switch (ext) { case '.css': return 'text/css'; case '.js': return 'application/javascript'; case '.svg': return 'image/svg+xml'; case '.json': return 'application/json'; case '.html': return 'text/html'; default: return 'text/plain'; } } function guessContentTypeFromExt(ext) { switch (ext) { case '.css': return 'text/css'; case '.js': return 'application/javascript'; case '.svg': return 'image/svg+xml'; case '.json': return 'application/json'; case '.png': return 'image/png'; case '.jpg': case '.jpeg': return 'image/jpeg'; case '.gif': return 'image/gif'; default: return 'application/octet-stream'; } } @@ -18220,6 +18227,35 @@ async function route(req, res) { const url = new URL(urlString, `http://${req.headers.host}`); const pathname = url.pathname; + // Handle CORS preflight requests + const origin = req.headers.origin; + const allowedOrigins = [ + 'capacitor://localhost', + 'http://localhost', + 'https://localhost', + 'https://plugincompass.com', + 'http://plugincompass.com' + ]; + + if (origin && allowedOrigins.some(allowed => origin.includes(allowed) || allowed.includes(origin))) { + res.setHeader('Access-Control-Allow-Origin', origin); + } else if (origin) { + // Allow any origin for mobile apps + res.setHeader('Access-Control-Allow-Origin', origin); + } + + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, PATCH'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-User-Id, X-Request-ID'); + res.setHeader('Access-Control-Allow-Credentials', 'true'); + res.setHeader('Access-Control-Max-Age', '86400'); + + // Handle OPTIONS preflight + if (req.method === 'OPTIONS') { + res.writeHead(204); + res.end(); + return; + } + // Track visitor trackVisit(req, res);