This commit is contained in:
Mateusz Gruszczyński
2026-09-17 13:01:41 +02:00
parent 4bdcf1b8dd
commit cafbf6e5fa
15 changed files with 85 additions and 45 deletions
+2 -1
View File
@@ -2918,7 +2918,8 @@ button.outside-pill:focus-visible {
box-shadow: 0 0 0 3px rgba(239, 91, 91, .14), 0 0 10px rgba(239, 91, 91, .24);
}
.connection-dot.connecting {
.connection-dot.connecting,
.connection-dot.reconnecting {
background: #e2a93b;
box-shadow: 0 0 0 3px rgba(226, 169, 59, .14);
animation: connection-pulse 1.25s ease-in-out infinite;
+1 -1
View File
@@ -26,7 +26,7 @@ const preferredTheme = ['system', 'light', 'dark'].includes(getCookie('gree_cont
const app = {
devices: [], zones: [], groups: [], deviceGroups: [], deviceGroupEnergy: {}, schedules: [], automations: [], flows: [], accessTokens: [], settings: null, system: {}, outdoorTemperature: null,
token: localStorage.getItem('gree_controller_token') || '', authTokenSubmitted: false, ws: null, wsTimer: null,
token: localStorage.getItem('gree_controller_token') || '', authTokenSubmitted: false, ws: null, wsTimer: null, wsErrorTimer: null,
currentView: 'dashboard', loading: false, bootstrapReloadPending: false, language: preferredLanguage, theme: preferredTheme, connectionStatus: 'connecting',
languages: [], translations: {}, locales: {}, defaultLanguage: DEFAULT_LANGUAGE,
historyTab: 'overview', historyData: { zones: [], devices: [], sensors: [] }, historyCounts: {},
+37 -4
View File
@@ -131,17 +131,50 @@ async function handleWebSocketMessage(event) {
} catch (_) { }
}
const WS_RECONNECT_DELAY_MS = 3000;
const WS_CONNECTION_ERROR_DELAY_MS = 15000;
function clearWebSocketErrorTimer() {
clearTimeout(app.wsErrorTimer);
app.wsErrorTimer = null;
}
function startWebSocketErrorTimer() {
if (app.wsErrorTimer) return;
app.wsErrorTimer = setTimeout(() => {
app.wsErrorTimer = null;
if (!app.ws || app.ws.readyState !== WebSocket.OPEN) updateConnectionIndicator('connectionError');
}, WS_CONNECTION_ERROR_DELAY_MS);
}
function connectWebSocket() {
if (app.ws && [WebSocket.OPEN, WebSocket.CONNECTING].includes(app.ws.readyState)) return;
clearTimeout(app.wsTimer);
app.wsTimer = null;
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const query = app.token ? `?token=${encodeURIComponent(app.token)}` : '';
const ws = new WebSocket(`${protocol}//${location.host}${withBase('/ws')}${query}`); app.ws = ws;
ws.onopen = () => updateConnectionIndicator('connected');
ws.onclose = () => { app.controlPlanPushReady = false; updateConnectionIndicator('disconnected'); scheduleControlPlanLoad(0); app.wsTimer = setTimeout(connectWebSocket, 3000); };
ws.onerror = () => updateConnectionIndicator('connectionError');
const ws = new WebSocket(`${protocol}//${location.host}${withBase('/ws')}${query}`);
app.ws = ws;
ws.onopen = () => {
if (app.ws !== ws) return;
clearWebSocketErrorTimer();
updateConnectionIndicator('connected');
};
ws.onclose = () => {
if (app.ws !== ws) return;
app.ws = null;
app.controlPlanPushReady = false;
if (app.connectionStatus !== 'connectionError') updateConnectionIndicator('reconnecting');
startWebSocketErrorTimer();
scheduleControlPlanLoad(0);
app.wsTimer = setTimeout(connectWebSocket, WS_RECONNECT_DELAY_MS);
};
// onclose owns the visible connection state. A transient onerror must not
// present an application failure while HTTP fallback and reconnect still work.
ws.onerror = () => { };
let messageQueue = Promise.resolve();
ws.onmessage = event => {
if (app.ws !== ws) return;
messageQueue = messageQueue.then(() => handleWebSocketMessage(event)).catch(() => { });
};
}