1
0
Fork 0
mirror of https://gitlab.com/futo-org/fcast.git synced 2025-06-24 21:25:23 +00:00

Receivers: Suppress excessive toasts from quick successive sender connect/disconnect/reconnect events

This commit is contained in:
Michael Hollister 2025-05-02 15:42:44 -05:00
parent dd88edae7d
commit 42d17e2fe7
5 changed files with 114 additions and 56 deletions

View file

@ -7,14 +7,20 @@ export function setUiUpdateCallbacks(callbacks: any) {
let frontendConnections = [];
window.targetAPI.onConnect((_event, value: any) => {
frontendConnections.push(value.sessionId);
const idMapping = value.type === 'ws' ? value.sessionId : value.data.address;
logger.debug(`Processing connect event for ${idMapping} with current connections:`, frontendConnections);
frontendConnections.push(idMapping);
callbacks.onConnect(frontendConnections);
});
window.targetAPI.onDisconnect((_event, value: any) => {
const index = frontendConnections.indexOf(value.sessionId);
const idMapping = value.type === 'ws' ? value.sessionId : value.data.address;
logger.debug(`Processing disconnect event for ${idMapping} with current connections:`, frontendConnections);
const index = frontendConnections.indexOf(idMapping);
if (index != -1) {
frontendConnections.splice(index, 1);
callbacks.onDisconnect(frontendConnections, value.sessionId);
callbacks.onDisconnect(frontendConnections);
}
});
@ -29,11 +35,14 @@ export function setUiUpdateCallbacks(callbacks: any) {
}
export class ConnectionMonitor {
private static logger: Logger;
private static initialized = false;
private static connectionPingTimeout = 2500;
private static heartbeatRetries = new Map();
private static backendConnections = new Map();
private static logger;
private static uiConnectUpdateTimeout = 100;
private static uiDisconnectUpdateTimeout = 2000; // Senders may reconnect, but generally need more time
private static uiUpdateMap = new Map();
constructor() {
if (!ConnectionMonitor.initialized) {
@ -49,7 +58,7 @@ export class ConnectionMonitor {
ConnectionMonitor.logger.debug(`Pinging session ${sessionId} with ${ConnectionMonitor.heartbeatRetries.get(sessionId)} retries left`);
ConnectionMonitor.backendConnections.get(sessionId).send(Opcode.Ping, null, sessionId);
ConnectionMonitor.heartbeatRetries.set(sessionId, ConnectionMonitor.heartbeatRetries.get(sessionId) === undefined ? 1 : ConnectionMonitor.heartbeatRetries.get(sessionId) + 1);
ConnectionMonitor.heartbeatRetries.set(sessionId, ConnectionMonitor.heartbeatRetries.get(sessionId) + 1);
}
}
}, ConnectionMonitor.connectionPingTimeout);
@ -58,18 +67,86 @@ export class ConnectionMonitor {
}
}
public static onPingPong(value: any) {
public static onPingPong(value: any, isWebsockets: boolean) {
ConnectionMonitor.logger.debug(`Received response from ${value.sessionId}`);
ConnectionMonitor.heartbeatRetries.set(value.sessionId, 0);
// Websocket clients currently don't support ping-pong commands
if (!isWebsockets) {
ConnectionMonitor.heartbeatRetries.set(value.sessionId, 0);
}
}
public static onConnect(listener: any, value: any) {
public static onConnect(listener: any, value: any, isWebsockets: boolean, uiUpdateCallback: any) {
ConnectionMonitor.logger.info(`Device connected: ${JSON.stringify(value)}`);
ConnectionMonitor.backendConnections.set(value.sessionId, listener);
const idMapping = isWebsockets ? value.sessionId : value.data.address;
if (!ConnectionMonitor.uiUpdateMap.has(idMapping)) {
ConnectionMonitor.uiUpdateMap.set(idMapping, []);
}
if (!isWebsockets) {
ConnectionMonitor.backendConnections.set(value.sessionId, listener);
ConnectionMonitor.heartbeatRetries.set(value.sessionId, 0);
}
// Occasionally senders seem to instantaneously disconnect and reconnect, so suppress those ui updates
const senderUpdateQueue = ConnectionMonitor.uiUpdateMap.get(idMapping);
senderUpdateQueue.push({ event: 'connect', uiUpdateCallback: uiUpdateCallback });
ConnectionMonitor.uiUpdateMap.set(idMapping, senderUpdateQueue);
if (senderUpdateQueue.length === 1) {
setTimeout(() => { ConnectionMonitor.processUiUpdateCallbacks(idMapping); }, ConnectionMonitor.uiConnectUpdateTimeout);
}
}
public static onDisconnect(listener: any, value: any) {
public static onDisconnect(listener: any, value: any, isWebsockets: boolean, uiUpdateCallback: any) {
ConnectionMonitor.logger.info(`Device disconnected: ${JSON.stringify(value)}`);
ConnectionMonitor.backendConnections.delete(value.sessionId);
if (!isWebsockets) {
ConnectionMonitor.backendConnections.delete(value.sessionId);
ConnectionMonitor.heartbeatRetries.delete(value.sessionId);
}
const idMapping = isWebsockets ? value.sessionId : value.data.address;
const senderUpdateQueue = ConnectionMonitor.uiUpdateMap.get(idMapping);
senderUpdateQueue.push({ event: 'disconnect', uiUpdateCallback: uiUpdateCallback });
ConnectionMonitor.uiUpdateMap.set(idMapping, senderUpdateQueue);
if (senderUpdateQueue.length === 1) {
setTimeout(() => { ConnectionMonitor.processUiUpdateCallbacks(idMapping); }, ConnectionMonitor.uiDisconnectUpdateTimeout);
}
}
private static processUiUpdateCallbacks(mapId: string) {
const updateQueue = ConnectionMonitor.uiUpdateMap.get(mapId);
let lastConnectCb: any;
let lastDisconnectCb: any;
let messageCount = 0;
updateQueue.forEach(update => {
ConnectionMonitor.logger.debug(`Processing update event '${update.event}' for ${mapId}`);
if (update.event === 'connect') {
messageCount += 1;
lastConnectCb = update.uiUpdateCallback;
}
else if (update.event === 'disconnect') {
messageCount -= 1;
lastDisconnectCb = update.uiUpdateCallback;
}
else {
ConnectionMonitor.logger.warn('Unrecognized UI update event:', update.event)
}
});
if (messageCount > 0) {
ConnectionMonitor.logger.debug(`Sending connect event for ${mapId}`);
lastConnectCb();
}
else if (messageCount < 0) {
ConnectionMonitor.logger.debug(`Sending disconnect event for ${mapId}`);
lastDisconnectCb();
}
ConnectionMonitor.uiUpdateMap.set(mapId, []);
}
}