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:
parent
d2a229ade2
commit
0368963bff
|
|
@ -537,19 +537,77 @@
|
||||||
setTimeout(() => toast.remove(), 3000);
|
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() {
|
function connectWebSocket() {
|
||||||
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
ws = new WebSocket(`${protocol}//${location.host}`);
|
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 = () => {
|
ws.onopen = () => {
|
||||||
|
clearTimeout(wsTimeout);
|
||||||
|
usePolling = false;
|
||||||
|
if (pollInterval) {
|
||||||
|
clearInterval(pollInterval);
|
||||||
|
pollInterval = null;
|
||||||
|
}
|
||||||
document.getElementById('status-badge').textContent = 'Live';
|
document.getElementById('status-badge').textContent = 'Live';
|
||||||
document.getElementById('status-badge').classList.remove('offline');
|
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 = () => {
|
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');
|
document.getElementById('status-badge').classList.add('offline');
|
||||||
setTimeout(connectWebSocket, 3000);
|
if (!usePolling) {
|
||||||
|
setTimeout(connectWebSocket, 3000);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onmessage = (event) => {
|
ws.onmessage = (event) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue