Skip to content

Commit 177d0e6

Browse files
committed
more robust reconnect after longer timeout
1 parent 885fee5 commit 177d0e6

1 file changed

Lines changed: 100 additions & 15 deletions

File tree

assets/js/channels.js

Lines changed: 100 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,67 @@ Genie.initWebChannel = function(channel = Genie.Settings.webchannels_default_rou
4444
}
4545

4646
var WebChannel = {};
47+
WebChannel._isUnloading = false;
48+
WebChannel._reconnectAttempts = 0;
49+
WebChannel._reconnectMinDelay = Genie.Settings.webchannels_reconnect_delay;
50+
WebChannel._reconnectMaxDelay = Math.max(30000, Genie.Settings.webchannels_reconnect_delay * 16);
51+
52+
const logDev = (...args) => {
53+
if (isDev()) console.info('[Genie.WebChannels]', ...args);
54+
};
55+
56+
const warnDev = (...args) => {
57+
if (isDev()) console.warn('[Genie.WebChannels]', ...args);
58+
};
59+
60+
const nextReconnectDelay = () => {
61+
const expDelay = Math.min(
62+
WebChannel._reconnectMaxDelay,
63+
WebChannel._reconnectMinDelay * Math.pow(2, WebChannel._reconnectAttempts)
64+
);
65+
// Add jitter so multiple clients do not reconnect in lockstep.
66+
const jitter = Math.floor(expDelay * 0.25 * Math.random());
67+
return expDelay + jitter;
68+
};
69+
70+
const scheduleReconnect = (reason = 'close') => {
71+
if (WebChannel._isUnloading || !Genie.Settings.webchannels_autosubscribe) {
72+
return;
73+
}
74+
75+
clearTimeout(WebChannel._reconnectTimer);
76+
const delay = nextReconnectDelay();
77+
warnDev('Scheduling reconnect', {
78+
channel: WebChannel.channel,
79+
reason,
80+
attempt: WebChannel._reconnectAttempts + 1,
81+
delay
82+
});
83+
84+
WebChannel._reconnectTimer = setTimeout(function() {
85+
if (WebChannel._isUnloading) return;
86+
WebChannel._openConnectionPromise = null;
87+
WebChannel._reconnectAttempts++;
88+
logDev('Reconnecting now', {
89+
channel: WebChannel.channel,
90+
attempt: WebChannel._reconnectAttempts
91+
});
92+
WebChannel.socket = newSocketConnection(WebChannel);
93+
}, delay);
94+
};
95+
96+
const triggerImmediateReconnect = (reason = 'manual') => {
97+
if (WebChannel._isUnloading || !Genie.Settings.webchannels_autosubscribe) {
98+
return;
99+
}
100+
if (!WebChannel.socket || WebChannel.socket.readyState > 1) {
101+
warnDev('Immediate reconnect trigger', { channel: WebChannel.channel, reason });
102+
clearTimeout(WebChannel._reconnectTimer);
103+
WebChannel._openConnectionPromise = null;
104+
WebChannel.socket = newSocketConnection(WebChannel);
105+
}
106+
};
107+
47108
WebChannel.sendMessageTo = async (channel, message, payload = {}) => {
48109
let msg = JSON.stringify({
49110
'channel': channel,
@@ -140,33 +201,55 @@ Genie.initWebChannel = function(channel = Genie.Settings.webchannels_default_rou
140201
});
141202

142203
WebChannel.closeHandlers.push(event => {
143-
if (isDev()) {
144-
console.warn('WebSocket connection closed: ' + event.code + ' ' + event.reason + ' ' + event.wasClean);
145-
}
204+
warnDev('WebSocket closed', {
205+
channel: WebChannel.channel,
206+
code: event.code,
207+
reason: event.reason,
208+
wasClean: event.wasClean
209+
});
146210
});
147211

148212
WebChannel.closeHandlers.push(event => {
149213
displayAlert(WebChannel);
150-
if (Genie.Settings.webchannels_autosubscribe) {
151-
if (isDev()) console.info('Attempting to reconnect! ');
152-
clearTimeout(WebChannel._reconnectTimer);
153-
WebChannel._reconnectTimer = setTimeout(function() {
154-
WebChannel._openConnectionPromise = null;
155-
WebChannel.socket = newSocketConnection(WebChannel);
156-
}, Genie.Settings.webchannels_reconnect_delay);
157-
}
214+
scheduleReconnect('socket-close');
158215
});
159216

160217
WebChannel.openHandlers.push(event => {
218+
WebChannel._reconnectAttempts = 0;
219+
clearTimeout(WebChannel._reconnectTimer);
220+
logDev('WebSocket opened', { channel: WebChannel.channel });
161221
if (Genie.Settings.webchannels_autosubscribe) {
162222
subscribe(WebChannel);
163223
}
164224
});
225+
226+
WebChannel.errorHandlers.push(event => {
227+
warnDev('WebSocket error observed', {
228+
channel: WebChannel.channel,
229+
readyState: WebChannel.socket?.readyState,
230+
type: event.type
231+
});
232+
});
233+
234+
WebChannel._onWindowFocus = _ => triggerImmediateReconnect('window-focus');
235+
WebChannel._onWindowOnline = _ => triggerImmediateReconnect('browser-online');
236+
WebChannel._onVisibilityChange = _ => {
237+
if (document.visibilityState === 'visible') {
238+
triggerImmediateReconnect('visibility-visible');
239+
}
240+
};
241+
window.addEventListener('focus', WebChannel._onWindowFocus);
242+
window.addEventListener('online', WebChannel._onWindowOnline);
243+
document.addEventListener('visibilitychange', WebChannel._onVisibilityChange);
165244

166245
window.addEventListener('beforeunload', _ => {
167-
if (isDev()) {
168-
console.info('Preparing to unload');
169-
}
246+
WebChannel._isUnloading = true;
247+
logDev('Preparing to unload', { channel: WebChannel.channel });
248+
clearTimeout(WebChannel._reconnectTimer);
249+
250+
if (WebChannel._onWindowFocus) window.removeEventListener('focus', WebChannel._onWindowFocus);
251+
if (WebChannel._onWindowOnline) window.removeEventListener('online', WebChannel._onWindowOnline);
252+
if (WebChannel._onVisibilityChange) document.removeEventListener('visibilitychange', WebChannel._onVisibilityChange);
170253

171254
if (Genie.Settings.webchannels_autosubscribe) {
172255
unsubscribe(WebChannel);
@@ -401,12 +484,14 @@ function subscription_ready(WebChannel) {
401484

402485
function subscribe(WebChannel, trial = 1) {
403486
if (WebChannel.socket.readyState == 1 && (document.readyState === 'complete' || document.readyState === 'interactive')) {
487+
if (isDev()) console.info('[Genie.WebChannels] Sending subscription request', { channel: WebChannel.channel, trial });
404488
WebChannel.sendMessageTo(WebChannel.channel, window.Genie.Settings.webchannels_subscribe_channel);
405489
} else if (trial < Genie.Settings.webchannels_subscription_trials) {
406-
if (isDev()) console.warn('Queuing subscription');
490+
if (isDev()) console.warn('[Genie.WebChannels] Queuing subscription', { channel: WebChannel.channel, trial, state: WebChannel.socket.readyState });
407491
clearTimeout(WebChannel._subscribeTimer);
408492
WebChannel._subscribeTimer = setTimeout(subscribe.bind(this, WebChannel, trial + 1), Genie.Settings.webchannels_timeout);
409493
} else {
494+
if (isDev()) console.warn('[Genie.WebChannels] Subscription retries exhausted; waiting for reconnect triggers', { channel: WebChannel.channel, trial });
410495
displayAlert(WebChannel);
411496
}
412497
};

0 commit comments

Comments
 (0)