From 9b476278013ae6c97b0f6a83c643fc0903fed245 Mon Sep 17 00:00:00 2001 From: Michael Hollister Date: Tue, 29 Apr 2025 11:57:25 -0500 Subject: [PATCH] Receivers: Fixed logic issues with connection monitor --- receivers/common/web/ConnectionMonitor.ts | 32 +++++++++++-------- receivers/common/web/FCastSession.ts | 7 ++-- receivers/common/web/TcpListenerService.ts | 26 +++------------ .../common/web/WebSocketListenerService.ts | 21 +++--------- receivers/common/web/main/Preload.ts | 1 + receivers/common/web/main/Renderer.ts | 2 -- receivers/common/web/player/Preload.ts | 1 + receivers/common/web/player/Renderer.ts | 2 -- receivers/electron/src/Main.ts | 4 +++ 9 files changed, 39 insertions(+), 57 deletions(-) diff --git a/receivers/common/web/ConnectionMonitor.ts b/receivers/common/web/ConnectionMonitor.ts index 39ea395..f769ec5 100644 --- a/receivers/common/web/ConnectionMonitor.ts +++ b/receivers/common/web/ConnectionMonitor.ts @@ -13,26 +13,30 @@ export function setUiUpdateCallbacks(callbacks: any) { } // Window might be re-created while devices are still connected -window.targetAPI.onPing((_event, value: any) => { +function onPingPong(value: any) { if (value) { - heartbeatRetries[value.id] = 0; + heartbeatRetries[value.sessionId] = 0; - if (!connections.includes(value.id)) { - connections.push(value.id); - uiUpdateCallbacks.onConnect(connections, value.id); + if (!connections.includes(value.sessionId)) { + connections.push(value.sessionId); + uiUpdateCallbacks.onConnect(connections, value.sessionId); } } -}); +} +window.targetAPI.onPing((_event, value: any) => onPingPong(value)); +window.targetAPI.onPong((_event, value: any) => onPingPong(value)); + window.targetAPI.onConnect((_event, value: any) => { - connections.push(value.id); + console.log(`Device connected: ${JSON.stringify(value)}`); + connections.push(value.sessionId); uiUpdateCallbacks.onConnect(connections, value); }); window.targetAPI.onDisconnect((_event, value: any) => { console.log(`Device disconnected: ${JSON.stringify(value)}`); - const index = connections.indexOf(value.id); + const index = connections.indexOf(value.sessionId); if (index != -1) { connections.splice(index, 1); - uiUpdateCallbacks.onDisconnect(connections, value.id); + uiUpdateCallbacks.onDisconnect(connections, value.sessionId); } }); @@ -40,13 +44,13 @@ setInterval(() => { if (connections.length > 0) { window.targetAPI.sendSessionMessage(Opcode.Ping, null); - for (const session of connections) { - if (heartbeatRetries[session] > 3) { - console.warn(`Could not ping device with connection id ${session}. Disconnecting...`); - window.targetAPI.disconnectDevice(session); + for (const sessionId of connections) { + if (heartbeatRetries[sessionId] > 3) { + console.warn(`Could not ping device with connection id ${sessionId}. Disconnecting...`); + window.targetAPI.disconnectDevice(sessionId); } - heartbeatRetries[session] = heartbeatRetries[session] === undefined ? 1 : heartbeatRetries[session] + 1; + heartbeatRetries[sessionId] = heartbeatRetries[sessionId] === undefined ? 1 : heartbeatRetries[sessionId] + 1; } } }, connectionPingTimeout); diff --git a/receivers/common/web/FCastSession.ts b/receivers/common/web/FCastSession.ts index 4c1080c..22fd70c 100644 --- a/receivers/common/web/FCastSession.ts +++ b/receivers/common/web/FCastSession.ts @@ -3,6 +3,7 @@ import * as log4js from "modules/log4js"; import { EventEmitter } from 'events'; import { Opcode, PlaybackErrorMessage, PlaybackUpdateMessage, PlayMessage, SeekMessage, SetSpeedMessage, SetVolumeMessage, VersionMessage, VolumeUpdateMessage } from 'common/Packets'; import { WebSocket } from 'modules/ws'; +import { v4 as uuidv4 } from 'modules/uuid'; const logger = log4js.getLogger(); enum SessionState { @@ -16,6 +17,7 @@ const LENGTH_BYTES = 4; const MAXIMUM_PACKET_LENGTH = 32000; export class FCastSession { + public sessionId: string; buffer: Buffer = Buffer.alloc(MAXIMUM_PACKET_LENGTH); bytesRead = 0; packetLength = 0; @@ -25,6 +27,7 @@ export class FCastSession { emitter = new EventEmitter(); constructor(socket: net.Socket | WebSocket, writer: (data: Buffer) => void) { + this.sessionId = uuidv4(); this.socket = socket; this.writer = writer; this.state = SessionState.WaitingForLength; @@ -220,7 +223,7 @@ export class FCastSession { this.emitter.on("setvolume", (body: SetVolumeMessage) => { emitter.emit("setvolume", body) }); this.emitter.on("setspeed", (body: SetSpeedMessage) => { emitter.emit("setspeed", body) }); this.emitter.on("version", (body: VersionMessage) => { emitter.emit("version", body) }); - this.emitter.on("ping", () => { emitter.emit("ping") }); - this.emitter.on("pong", () => { emitter.emit("pong") }); + this.emitter.on("ping", () => { emitter.emit("ping", { sessionId: this.sessionId }) }); + this.emitter.on("pong", () => { emitter.emit("pong", { sessionId: this.sessionId }) }); } } diff --git a/receivers/common/web/TcpListenerService.ts b/receivers/common/web/TcpListenerService.ts index ef98273..7b193aa 100644 --- a/receivers/common/web/TcpListenerService.ts +++ b/receivers/common/web/TcpListenerService.ts @@ -3,7 +3,6 @@ import { FCastSession } from 'common/FCastSession'; import { Opcode } from 'common/Packets'; import { EventEmitter } from 'events'; import { Main, errorHandler } from 'src/Main'; -import { v4 as uuidv4 } from 'modules/uuid'; export class TcpListenerService { public static PORT = 46899; @@ -47,8 +46,8 @@ export class TcpListenerService { }); } - disconnect(connectionId: string) { - this.sessionMap[connectionId]?.socket.destroy(); + disconnect(sessionId: string) { + this.sessionMap[sessionId]?.socket.destroy(); } private async handleServerError(err: NodeJS.ErrnoException) { @@ -61,9 +60,7 @@ export class TcpListenerService { const session = new FCastSession(socket, (data) => socket.write(data)); session.bindEvents(this.emitter); this.sessions.push(session); - - const connectionId = uuidv4(); - this.sessionMap[connectionId] = session; + this.sessionMap[session.sessionId] = session; socket.on("error", (err) => { Main.logger.warn(`Error from ${socket.remoteAddress}:${socket.remotePort}.`, err); @@ -84,23 +81,10 @@ export class TcpListenerService { if (index != -1) { this.sessions.splice(index, 1); } - if (!this.sessions.some(e => e.socket.remoteAddress === socket.remoteAddress)) { - this.emitter.emit('disconnect', { id: connectionId, type: 'tcp', data: { address: socket.remoteAddress, port: socket.remotePort }}); - } - this.emitter.removeListener('ping', pingListener); + this.emitter.emit('disconnect', { sessionId: session.sessionId, type: 'tcp', data: { address: socket.remoteAddress, port: socket.remotePort }}); }); - // Sometimes the sender may reconnect under a different port, so suppress connect/disconnect event emission - if (!this.sessions.some(e => e.socket.remoteAddress === socket.remoteAddress)) { - this.emitter.emit('connect', { id: connectionId, type: 'tcp', data: { address: socket.remoteAddress, port: socket.remotePort }}); - } - const pingListener = (message: any) => { - if (!message) { - this.emitter.emit('ping', { id: connectionId }); - } - } - this.emitter.prependListener('ping', pingListener); - + this.emitter.emit('connect', { sessionId: session.sessionId, type: 'tcp', data: { address: socket.remoteAddress, port: socket.remotePort }}); try { Main.logger.info('Sending version'); session.send(Opcode.Version, {version: 2}); diff --git a/receivers/common/web/WebSocketListenerService.ts b/receivers/common/web/WebSocketListenerService.ts index b36c4e7..33593e5 100644 --- a/receivers/common/web/WebSocketListenerService.ts +++ b/receivers/common/web/WebSocketListenerService.ts @@ -3,7 +3,6 @@ import { Opcode } from 'common/Packets'; import { EventEmitter } from 'events'; import { WebSocket, WebSocketServer } from 'modules/ws'; import { Main, errorHandler } from 'src/Main'; -import { v4 as uuidv4 } from 'modules/uuid'; export class WebSocketListenerService { public static PORT = 46898; @@ -46,8 +45,8 @@ export class WebSocketListenerService { }); } - disconnect(connectionId: string) { - this.sessionMap[connectionId]?.close(); + disconnect(sessionId: string) { + this.sessionMap[sessionId]?.close(); } private async handleServerError(err: NodeJS.ErrnoException) { @@ -58,11 +57,9 @@ export class WebSocketListenerService { Main.logger.info('New WebSocket connection'); const session = new FCastSession(socket, (data) => socket.send(data)); - const connectionId = uuidv4(); - this.sessionMap[connectionId] = session; - session.bindEvents(this.emitter); this.sessions.push(session); + this.sessionMap[session.sessionId] = session; socket.on("error", (err) => { Main.logger.warn(`Error.`, err); @@ -89,18 +86,10 @@ export class WebSocketListenerService { if (index != -1) { this.sessions.splice(index, 1); } - this.emitter.emit('disconnect', { id: connectionId, type: 'ws', data: { url: socket.url }}); - this.emitter.removeListener('ping', pingListener); + this.emitter.emit('disconnect', { sessionId: session.sessionId, type: 'ws', data: { url: socket.url }}); }); - this.emitter.emit('connect', { id: connectionId, type: 'ws', data: { url: socket.url }}); - const pingListener = (message: any) => { - if (!message) { - this.emitter.emit('ping', { id: connectionId }); - } - } - this.emitter.prependListener('ping', pingListener); - + this.emitter.emit('connect', { sessionId: session.sessionId, type: 'ws', data: { url: socket.url }}); try { Main.logger.info('Sending version'); session.send(Opcode.Version, {version: 2}); diff --git a/receivers/common/web/main/Preload.ts b/receivers/common/web/main/Preload.ts index ba0b9d2..b0fdbdd 100644 --- a/receivers/common/web/main/Preload.ts +++ b/receivers/common/web/main/Preload.ts @@ -30,6 +30,7 @@ if (TARGET === 'electron') { onConnect: (callback: any) => electronAPI.ipcRenderer.on('connect', callback), onDisconnect: (callback: any) => electronAPI.ipcRenderer.on('disconnect', callback), onPing: (callback: any) => electronAPI.ipcRenderer.on('ping', callback), + onPong: (callback: any) => electronAPI.ipcRenderer.on('pong', callback), }); // @ts-ignore diff --git a/receivers/common/web/main/Renderer.ts b/receivers/common/web/main/Renderer.ts index 02dc581..834a037 100644 --- a/receivers/common/web/main/Renderer.ts +++ b/receivers/common/web/main/Renderer.ts @@ -16,13 +16,11 @@ window.addEventListener('resize', (event) => calculateQRCodeWidth()); connectionMonitor.setUiUpdateCallbacks({ onConnect: (connections: string[], connectionInfo: any) => { - console.log(`Device connected: ${JSON.stringify(connectionInfo)}`); connectionStatusText.textContent = connections.length > 1 ? 'Multiple devices connected:\r\n Ready to cast' : 'Connected: Ready to cast'; connectionStatusSpinner.style.display = 'none'; connectionStatusCheck.style.display = 'inline-block'; }, onDisconnect: (connections: string[], connectionInfo: any) => { - console.log(`Device disconnected: ${JSON.stringify(connectionInfo)}`); if (connections.length === 0) { connectionStatusText.textContent = 'Waiting for a connection'; connectionStatusSpinner.style.display = 'inline-block'; diff --git a/receivers/common/web/player/Preload.ts b/receivers/common/web/player/Preload.ts index c0989c5..3b7b715 100644 --- a/receivers/common/web/player/Preload.ts +++ b/receivers/common/web/player/Preload.ts @@ -33,6 +33,7 @@ if (TARGET === 'electron') { onConnect: (callback: any) => electronAPI.ipcRenderer.on('connect', callback), onDisconnect: (callback: any) => electronAPI.ipcRenderer.on('disconnect', callback), onPing: (callback: any) => electronAPI.ipcRenderer.on('ping', callback), + onPong: (callback: any) => electronAPI.ipcRenderer.on('pong', callback), }); // @ts-ignore diff --git a/receivers/common/web/player/Renderer.ts b/receivers/common/web/player/Renderer.ts index a8c8d6b..07bc3e6 100644 --- a/receivers/common/web/player/Renderer.ts +++ b/receivers/common/web/player/Renderer.ts @@ -334,11 +334,9 @@ function onPlay(_event, value: PlayMessage) { connectionMonitor.setUiUpdateCallbacks({ onConnect: (connections: string[], connectionInfo: any) => { - console.log(`Device connected: ${JSON.stringify(connectionInfo)}`); toast('Device connected', ToastIcon.INFO); }, onDisconnect: (connections: string[], connectionInfo: any) => { - console.log(`Device disconnected: ${JSON.stringify(connectionInfo)}`); toast('Device disconnected. If you experience playback issues, please reconnect.', ToastIcon.INFO); }, }); diff --git a/receivers/electron/src/Main.ts b/receivers/electron/src/Main.ts index fa56f0b..64df2ff 100644 --- a/receivers/electron/src/Main.ts +++ b/receivers/electron/src/Main.ts @@ -200,6 +200,10 @@ export class Main { Main.mainWindow?.webContents?.send('ping', message); Main.playerWindow?.webContents?.send('ping', message); }); + l.emitter.on('pong', (message) => { + Main.mainWindow?.webContents?.send('pong', message); + Main.playerWindow?.webContents?.send('pong', message); + }); l.start(); ipcMain.on('send-playback-error', (event: IpcMainEvent, value: PlaybackErrorMessage) => {