pediatric-ai-scribe-v3/mobile/ios/App/App/public/launcher.js
Daniel 7d55e64ca1 Add Capacitor native mobile app (PedScribe) for iOS + Android
New mobile/ directory with Capacitor project:
- Configurable server URL launcher (default: app.pedshub.com)
- Android: foreground service + wake lock for background recording
  (AudioRecordingService preserved from existing TWA)
- iOS: background audio mode + microphone permission
- App ID: com.pedshub.scribe
- Both platforms initialized and synced

Existing android/ TWA project untouched — this is a separate project.
Build: cd mobile && npx cap open android (or ios)
2026-04-11 02:18:06 +02:00

116 lines
3.8 KiB
JavaScript

// PedScribe Mobile Launcher
// Handles configurable server URL and auto-redirect
(function() {
var STORAGE_KEY = 'pedscribe_server_url';
var DEFAULT_URL = 'https://app.pedshub.com';
var setupScreen = document.getElementById('setup-screen');
var connectingScreen = document.getElementById('connecting-screen');
var urlInput = document.getElementById('server-url');
var connectBtn = document.getElementById('btn-connect');
var changeBtn = document.getElementById('btn-change-server');
// Check for saved server URL
var savedUrl = localStorage.getItem(STORAGE_KEY);
if (savedUrl) {
// Auto-connect to saved server
showConnecting(savedUrl);
} else {
// Show setup screen
urlInput.value = DEFAULT_URL;
setupScreen.style.display = '';
connectingScreen.style.display = 'none';
}
// 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...';
// Test the server is reachable
testServer(url, function(ok) {
if (ok) {
localStorage.setItem(STORAGE_KEY, url);
navigateToServer(url);
} else {
connectBtn.disabled = false;
connectBtn.textContent = 'Connect';
showError('Could not reach server. Check the URL and try again.');
}
});
});
// Enter key
urlInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter') connectBtn.click();
});
// Change server button (from connecting screen)
changeBtn.addEventListener('click', function() {
localStorage.removeItem(STORAGE_KEY);
setupScreen.style.display = '';
connectingScreen.style.display = 'none';
urlInput.value = savedUrl || DEFAULT_URL;
urlInput.focus();
});
function showConnecting(url) {
setupScreen.style.display = 'none';
connectingScreen.style.display = '';
// Brief delay so the user sees the connecting screen
setTimeout(function() {
testServer(url, function(ok) {
if (ok) {
navigateToServer(url);
} else {
// Server not reachable — show setup screen
setupScreen.style.display = '';
connectingScreen.style.display = 'none';
urlInput.value = url;
showError('Server not reachable. Check your connection or change the URL.');
}
});
}, 500);
}
function testServer(url, callback) {
// Try to reach the health endpoint
var controller = new AbortController();
var timeout = setTimeout(function() { controller.abort(); }, 8000);
fetch(url + '/api/health', { signal: controller.signal, mode: 'no-cors' })
.then(function() { clearTimeout(timeout); callback(true); })
.catch(function() {
clearTimeout(timeout);
// no-cors mode always succeeds for reachable servers (opaque response)
// If it fails, the server is truly unreachable
// Try again with a simple image load as fallback
var img = new Image();
img.onload = function() { callback(true); };
img.onerror = function() { callback(true); }; // Even errors mean server responded
img.src = url + '/favicon.ico?t=' + Date.now();
setTimeout(function() { callback(false); }, 5000);
});
}
function navigateToServer(url) {
window.location.href = url;
}
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;
connectBtn.parentNode.insertBefore(div, connectBtn.nextSibling);
setTimeout(function() { div.remove(); }, 8000);
}
})();