sse iphone fix 2
This commit is contained in:
@@ -737,12 +737,27 @@
|
|||||||
|
|
||||||
// API_BASE injected from backend (supports Docker/K8s environments)
|
// API_BASE injected from backend (supports Docker/K8s environments)
|
||||||
window.API_BASE = '{{ api_base }}';
|
window.API_BASE = '{{ api_base }}';
|
||||||
|
window.RUNTIME_CONFIG = window.RUNTIME_CONFIG || {};
|
||||||
|
|
||||||
// Helper function to construct API URLs
|
// Helper function to construct API URLs
|
||||||
function api(url) {
|
function api(url) {
|
||||||
return `${window.API_BASE}${url}`;
|
return `${window.API_BASE}${url}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// iOS/Safari Polyfill laden (nur wenn nötig)
|
||||||
|
(function() {
|
||||||
|
var isIOS = /iP(hone|od|ad)/.test(navigator.platform) ||
|
||||||
|
(navigator.userAgent.includes("Mac") && "ontouchend" in document);
|
||||||
|
if (isIOS && typeof window.EventSourcePolyfill === "undefined") {
|
||||||
|
var s = document.createElement("script");
|
||||||
|
s.src = "https://cdn.jsdelivr.net/npm/event-source-polyfill@1.0.31/src/eventsource.min.js";
|
||||||
|
s.onerror = function() {
|
||||||
|
console.warn("EventSource polyfill konnte nicht geladen werden");
|
||||||
|
};
|
||||||
|
document.head.appendChild(s);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
let eventSource = null;
|
let eventSource = null;
|
||||||
let currentState = {};
|
let currentState = {};
|
||||||
let thermostatTargets = {};
|
let thermostatTargets = {};
|
||||||
@@ -999,35 +1014,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect to SSE
|
// Safari/iOS-kompatibler SSE Client mit Auto-Reconnect
|
||||||
let reconnectAttempts = 0;
|
let reconnectDelay = 2500;
|
||||||
const maxReconnectDelay = 30000; // Max 30 seconds
|
let reconnectTimer = null;
|
||||||
|
|
||||||
function connectSSE() {
|
// Global handleSSE function für SSE-Nachrichten
|
||||||
// Close existing connection if any
|
window.handleSSE = function(data) {
|
||||||
if (eventSource) {
|
|
||||||
try {
|
|
||||||
eventSource.close();
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Error closing EventSource:', e);
|
|
||||||
}
|
|
||||||
eventSource = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Connecting to SSE... (attempt ${reconnectAttempts + 1})`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
eventSource = new EventSource(api('/realtime'));
|
|
||||||
|
|
||||||
eventSource.onopen = () => {
|
|
||||||
console.log('SSE connected successfully');
|
|
||||||
reconnectAttempts = 0; // Reset counter on successful connection
|
|
||||||
document.getElementById('connection-status').textContent = 'Verbunden';
|
|
||||||
document.getElementById('connection-status').className = 'status connected';
|
|
||||||
};
|
|
||||||
|
|
||||||
eventSource.addEventListener('message', (e) => {
|
|
||||||
const data = JSON.parse(e.data);
|
|
||||||
console.log('SSE message:', data);
|
console.log('SSE message:', data);
|
||||||
|
|
||||||
addEvent(data);
|
addEvent(data);
|
||||||
@@ -1066,80 +1058,104 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
eventSource.addEventListener('ping', (e) => {
|
|
||||||
console.log('Heartbeat received');
|
|
||||||
});
|
|
||||||
|
|
||||||
eventSource.onerror = (error) => {
|
|
||||||
console.error('SSE error:', error, 'readyState:', eventSource?.readyState);
|
|
||||||
document.getElementById('connection-status').textContent = 'Getrennt';
|
|
||||||
document.getElementById('connection-status').className = 'status disconnected';
|
|
||||||
|
|
||||||
|
function cleanupSSE() {
|
||||||
if (eventSource) {
|
if (eventSource) {
|
||||||
try {
|
try {
|
||||||
eventSource.close();
|
eventSource.close();
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.error('Error closing EventSource on error:', e);
|
console.error('Error closing EventSource:', e);
|
||||||
}
|
}
|
||||||
eventSource = null;
|
eventSource = null;
|
||||||
}
|
}
|
||||||
|
if (reconnectTimer) {
|
||||||
|
clearTimeout(reconnectTimer);
|
||||||
|
reconnectTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Exponential backoff with max delay
|
function scheduleReconnect() {
|
||||||
reconnectAttempts++;
|
if (reconnectTimer) return;
|
||||||
const delay = Math.min(
|
console.log(`Reconnecting in ${reconnectDelay}ms...`);
|
||||||
1000 * Math.pow(2, reconnectAttempts - 1),
|
reconnectTimer = setTimeout(() => {
|
||||||
maxReconnectDelay
|
reconnectTimer = null;
|
||||||
);
|
connectSSE();
|
||||||
|
// Backoff bis 10s
|
||||||
|
reconnectDelay = Math.min(reconnectDelay * 2, 10000);
|
||||||
|
}, reconnectDelay);
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`Reconnecting in ${delay}ms... (attempt ${reconnectAttempts})`);
|
function connectSSE() {
|
||||||
setTimeout(connectSSE, delay);
|
cleanupSSE();
|
||||||
|
|
||||||
|
const REALTIME_URL = (window.RUNTIME_CONFIG && window.RUNTIME_CONFIG.REALTIME_URL)
|
||||||
|
? window.RUNTIME_CONFIG.REALTIME_URL
|
||||||
|
: api('/realtime');
|
||||||
|
|
||||||
|
console.log('Connecting to SSE:', REALTIME_URL);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Verwende Polyfill wenn verfügbar, sonst native EventSource
|
||||||
|
const EventSourceImpl = window.EventSourcePolyfill || window.EventSource;
|
||||||
|
eventSource = new EventSourceImpl(REALTIME_URL, {
|
||||||
|
withCredentials: false
|
||||||
|
});
|
||||||
|
|
||||||
|
eventSource.onopen = function() {
|
||||||
|
console.log('SSE connected successfully');
|
||||||
|
reconnectDelay = 2500; // Reset backoff
|
||||||
|
document.getElementById('connection-status').textContent = 'Verbunden';
|
||||||
|
document.getElementById('connection-status').className = 'status connected';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
eventSource.onmessage = function(evt) {
|
||||||
|
if (!evt || !evt.data) return;
|
||||||
|
|
||||||
|
// Heartbeats beginnen mit ":" -> ignorieren
|
||||||
|
if (typeof evt.data === "string" && evt.data.charAt(0) === ":") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(evt.data);
|
||||||
|
if (window.handleSSE) {
|
||||||
|
window.handleSSE(data);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error parsing SSE message:', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
eventSource.onerror = function(error) {
|
||||||
|
console.error('SSE error:', error, 'readyState:', eventSource?.readyState);
|
||||||
|
document.getElementById('connection-status').textContent = 'Getrennt';
|
||||||
|
document.getElementById('connection-status').className = 'status disconnected';
|
||||||
|
|
||||||
|
// Safari/iOS verliert Netz beim App-Switch: ruhig reconnecten
|
||||||
|
scheduleReconnect();
|
||||||
|
};
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to create EventSource:', error);
|
console.error('Failed to create EventSource:', error);
|
||||||
document.getElementById('connection-status').textContent = 'Getrennt';
|
document.getElementById('connection-status').textContent = 'Getrennt';
|
||||||
document.getElementById('connection-status').className = 'status disconnected';
|
document.getElementById('connection-status').className = 'status disconnected';
|
||||||
|
scheduleReconnect();
|
||||||
reconnectAttempts++;
|
|
||||||
const delay = Math.min(
|
|
||||||
1000 * Math.pow(2, reconnectAttempts - 1),
|
|
||||||
maxReconnectDelay
|
|
||||||
);
|
|
||||||
|
|
||||||
setTimeout(connectSSE, delay);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safari/iOS specific: Reconnect when page becomes visible
|
// Visibility-Change Handler für iOS App-Switch
|
||||||
document.addEventListener('visibilitychange', () => {
|
document.addEventListener('visibilitychange', function() {
|
||||||
if (document.visibilityState === 'visible') {
|
if (!document.hidden) {
|
||||||
console.log('Page visible, checking SSE connection...');
|
// Wenn wieder sichtbar & keine offene Verbindung: verbinden
|
||||||
// Only reconnect if connection is actually dead (CLOSED = 2)
|
if (!eventSource || eventSource.readyState !== 1) {
|
||||||
if (!eventSource || eventSource.readyState === EventSource.CLOSED) {
|
console.log('Page visible again, reconnecting SSE...');
|
||||||
console.log('SSE connection dead, forcing reconnect...');
|
|
||||||
reconnectAttempts = 0; // Reset for immediate reconnect
|
|
||||||
connectSSE();
|
connectSSE();
|
||||||
} else {
|
|
||||||
console.log('SSE connection OK, readyState:', eventSource.readyState);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Safari/iOS specific: Reconnect on page focus
|
// Start SSE connection
|
||||||
window.addEventListener('focus', () => {
|
|
||||||
console.log('Window focused, checking SSE connection...');
|
|
||||||
// Only reconnect if connection is actually dead (CLOSED = 2)
|
|
||||||
if (!eventSource || eventSource.readyState === EventSource.CLOSED) {
|
|
||||||
console.log('SSE connection dead, forcing reconnect...');
|
|
||||||
reconnectAttempts = 0; // Reset for immediate reconnect
|
|
||||||
connectSSE();
|
|
||||||
} else {
|
|
||||||
console.log('SSE connection OK, readyState:', eventSource.readyState);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize
|
|
||||||
connectSSE();
|
connectSSE();
|
||||||
|
|
||||||
// Load initial device states
|
// Load initial device states
|
||||||
|
|||||||
Reference in New Issue
Block a user