Fix Android app connectivity issues and add detailed logging
- Add CORS headers to backend server to allow mobile app requests - Implement request timeout (10s) in capacitor-bridge.js to prevent hanging - Add comprehensive logging throughout authentication flow - Add detailed error reporting in initApp for better debugging - Log all API requests with request IDs for traceability This fixes the 'Loading Plugin Compass...' infinite loop issue caused by missing CORS headers and unhandled network timeouts.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user