feat: add HTTP fallback when WebSocket unavailable

WebSocket connections may not work through Cloudflare Tunnel.
Added HTTP polling fallback that:
- Loads initial data via /api/projects and /api/tasks
- Falls back to 5-second polling if WebSocket fails
- Shows "Polling" status badge when in fallback mode

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2025-12-04 05:40:33 -08:00
parent d2a229ade2
commit 0368963bff
1 changed files with 60 additions and 2 deletions

View File

@ -537,19 +537,77 @@
setTimeout(() => toast.remove(), 3000);
}
let usePolling = false;
let pollInterval = null;
// HTTP fallback to load initial data
async function loadDataViaHTTP() {
try {
const [projectsRes, tasksRes] = await Promise.all([
fetch('/api/projects'),
fetch('/api/tasks')
]);
if (projectsRes.ok && tasksRes.ok) {
projects = await projectsRes.json();
tasks = await tasksRes.json();
renderProjects();
renderTasks();
updateProjectDropdown();
return true;
}
} catch (e) {
console.error('HTTP load failed:', e);
}
return false;
}
// Start polling for updates (fallback when WebSocket unavailable)
function startPolling() {
if (pollInterval) return;
usePolling = true;
document.getElementById('status-badge').textContent = 'Polling';
pollInterval = setInterval(loadDataViaHTTP, 5000);
}
function connectWebSocket() {
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
ws = new WebSocket(`${protocol}//${location.host}`);
// Timeout - if WS doesn't connect in 5s, fall back to HTTP
const wsTimeout = setTimeout(async () => {
if (ws.readyState !== WebSocket.OPEN) {
console.log('WebSocket timeout, falling back to HTTP');
ws.close();
await loadDataViaHTTP();
startPolling();
}
}, 5000);
ws.onopen = () => {
clearTimeout(wsTimeout);
usePolling = false;
if (pollInterval) {
clearInterval(pollInterval);
pollInterval = null;
}
document.getElementById('status-badge').textContent = 'Live';
document.getElementById('status-badge').classList.remove('offline');
};
ws.onerror = async () => {
clearTimeout(wsTimeout);
console.log('WebSocket error, falling back to HTTP');
await loadDataViaHTTP();
startPolling();
};
ws.onclose = () => {
document.getElementById('status-badge').textContent = 'Reconnecting...';
clearTimeout(wsTimeout);
document.getElementById('status-badge').textContent = usePolling ? 'Polling' : 'Reconnecting...';
document.getElementById('status-badge').classList.add('offline');
setTimeout(connectWebSocket, 3000);
if (!usePolling) {
setTimeout(connectWebSocket, 3000);
}
};
ws.onmessage = (event) => {