// PedScribe Mobile Launcher // Handles server URL config, biometric auth, and native features (function() { var STORAGE_KEY = 'pedscribe_server_url'; var BIOMETRIC_KEY = 'pedscribe_biometric_enabled'; var DEFAULT_URL = 'https://app.pedshub.com'; var setupScreen = document.getElementById('setup-screen'); var connectingScreen = document.getElementById('connecting-screen'); var biometricScreen = document.getElementById('biometric-screen'); var urlInput = document.getElementById('server-url'); var connectBtn = document.getElementById('btn-connect'); var changeBtn = document.getElementById('btn-change-server'); var unlockBtn = document.getElementById('btn-unlock'); var skipBioBtn = document.getElementById('btn-skip-biometric'); var savedUrl = localStorage.getItem(STORAGE_KEY); var biometricEnabled = localStorage.getItem(BIOMETRIC_KEY) === 'true'; // ── Launch flow ── if (savedUrl) { if (biometricEnabled && isCapacitor()) { showBiometricScreen(savedUrl); } else { showConnecting(savedUrl); } } else { urlInput.value = DEFAULT_URL; showScreen('setup'); } // ── Connect button ── connectBtn.addEventListener('click', function() { var url = (urlInput.value || DEFAULT_URL).trim().replace(/\/+$/, ''); if (!url.startsWith('http')) url = 'https://' + url; connectBtn.disabled = true; connectBtn.textContent = 'Connecting...'; haptic(); testServer(url, function(ok) { if (ok) { localStorage.setItem(STORAGE_KEY, url); // Enable biometric on first connect if available checkBiometricAvailable(function(available) { if (available) localStorage.setItem(BIOMETRIC_KEY, 'true'); }); navigateToServer(url); } else { connectBtn.disabled = false; connectBtn.textContent = 'Connect'; showError('Could not reach server. Check the URL and try again.'); } }); }); urlInput.addEventListener('keydown', function(e) { if (e.key === 'Enter') connectBtn.click(); }); // ── Change server ── changeBtn.addEventListener('click', function() { localStorage.removeItem(STORAGE_KEY); urlInput.value = savedUrl || DEFAULT_URL; showScreen('setup'); urlInput.focus(); }); // ── Biometric unlock ── if (unlockBtn) { unlockBtn.addEventListener('click', function() { doBiometricAuth(function(ok) { if (ok) { showConnecting(savedUrl); } else { showError('Authentication failed. Try again.'); } }); }); } if (skipBioBtn) { skipBioBtn.addEventListener('click', function() { showConnecting(savedUrl); }); } // ── Screen management ── function showScreen(which) { setupScreen.style.display = which === 'setup' ? '' : 'none'; connectingScreen.style.display = which === 'connecting' ? '' : 'none'; biometricScreen.style.display = which === 'biometric' ? '' : 'none'; } function showBiometricScreen(url) { showScreen('biometric'); // Auto-trigger biometric prompt setTimeout(function() { doBiometricAuth(function(ok) { if (ok) showConnecting(url); // If fails, user sees the manual Unlock button }); }, 300); } function showConnecting(url) { showScreen('connecting'); setTimeout(function() { testServer(url, function(ok) { if (ok) { navigateToServer(url); } else { urlInput.value = url; showScreen('setup'); showError('Server not reachable. Check your connection or change the URL.'); } }); }, 500); } // ── Server check ── function testServer(url, callback) { var done = false; function respond(ok) { if (done) return; done = true; callback(ok); } fetch(url + '/api/health', { mode: 'no-cors', signal: AbortSignal.timeout(8000) }) .then(function() { respond(true); }) .catch(function() { var fallbackTimer = setTimeout(function() { respond(false); }, 5000); var img = new Image(); img.onload = function() { clearTimeout(fallbackTimer); respond(true); }; img.onerror = function() { clearTimeout(fallbackTimer); respond(false); }; img.src = url + '/favicon.ico?t=' + Date.now(); }); } function navigateToServer(url) { window.location.href = url; } // ── Biometric auth ── function isCapacitor() { return !!(window.Capacitor && window.Capacitor.isNativePlatform && window.Capacitor.isNativePlatform()); } function checkBiometricAvailable(callback) { if (!isCapacitor()) { callback(false); return; } try { var BiometricAuth = window.Capacitor.Plugins.BiometricAuth; if (!BiometricAuth) { callback(false); return; } BiometricAuth.checkBiometry().then(function(result) { callback(result.isAvailable); }).catch(function() { callback(false); }); } catch(e) { callback(false); } } function doBiometricAuth(callback) { if (!isCapacitor()) { callback(true); return; } // Skip on web try { var BiometricAuth = window.Capacitor.Plugins.BiometricAuth; if (!BiometricAuth) { callback(true); return; } BiometricAuth.authenticate({ reason: 'Unlock PedScribe', cancelTitle: 'Cancel', allowDeviceCredential: true, // Allow PIN/password fallback iosFallbackTitle: 'Use Passcode', androidTitle: 'PedScribe', androidSubtitle: 'Verify your identity to continue', androidConfirmationRequired: false }).then(function() { callback(true); }).catch(function() { callback(false); }); } catch(e) { callback(true); } // Fail open if plugin not available } // ── Helpers ── function haptic() { try { if (window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Haptics) { window.Capacitor.Plugins.Haptics.impact({ style: 'medium' }); } } catch(e) {} } function showError(msg) { var existing = document.querySelector('.error-msg'); if (existing) existing.remove(); var div = document.createElement('div'); div.className = 'error-msg'; div.style.display = 'block'; div.textContent = msg; var parent = connectBtn ? connectBtn.parentNode : document.querySelector('.launcher'); if (parent) parent.appendChild(div); setTimeout(function() { if (div.parentNode) div.remove(); }, 8000); } })();