From da558e06ae4f8ffad6f1f4be7e05ea5fc55c00bf Mon Sep 17 00:00:00 2001 From: Jack Kingsman Date: Tue, 10 Feb 2026 19:25:35 -0800 Subject: [PATCH] Fix repeater comms coming back in different channel if a user sends and changes convos rapidly. --- frontend/src/App.tsx | 2 +- frontend/src/hooks/useRepeaterMode.ts | 40 ++++++++++++++++++--------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5c3f1ea..ab70849 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -127,7 +127,7 @@ export function App() { activeContactIsRepeater, handleTelemetryRequest, handleRepeaterCommand, - } = useRepeaterMode(activeConversation, contacts, setMessages); + } = useRepeaterMode(activeConversation, contacts, setMessages, activeConversationRef); // WebSocket handlers - memoized to prevent reconnection loops const wsHandlers = useMemo( diff --git a/frontend/src/hooks/useRepeaterMode.ts b/frontend/src/hooks/useRepeaterMode.ts index a1200d9..8b7c889 100644 --- a/frontend/src/hooks/useRepeaterMode.ts +++ b/frontend/src/hooks/useRepeaterMode.ts @@ -1,4 +1,4 @@ -import { useState, useCallback, useMemo, useEffect } from 'react'; +import { useState, useCallback, useMemo, useEffect, type RefObject } from 'react'; import { api } from '../api'; import type { Contact, @@ -118,7 +118,8 @@ export interface UseRepeaterModeResult { export function useRepeaterMode( activeConversation: Conversation | null, contacts: Contact[], - setMessages: React.Dispatch> + setMessages: React.Dispatch>, + activeConversationRef: RefObject ): UseRepeaterModeResult { const [repeaterLoggedIn, setRepeaterLoggedIn] = useState(false); const { handleAirtimeCommand, stopTracking } = useAirtimeTracking(setMessages); @@ -142,26 +143,31 @@ export function useRepeaterMode( if (!activeConversation || activeConversation.type !== 'contact') return; if (!activeContactIsRepeater) return; + const conversationId = activeConversation.id; + try { - const telemetry = await api.requestTelemetry(activeConversation.id, password); + const telemetry = await api.requestTelemetry(conversationId, password); + + // User may have switched conversations during the await + if (activeConversationRef.current?.id !== conversationId) return; // Create local messages to display the telemetry (not persisted to database) const telemetryMessage = createLocalMessage( - activeConversation.id, + conversationId, formatTelemetry(telemetry), false, 0 ); const neighborsMessage = createLocalMessage( - activeConversation.id, + conversationId, formatNeighbors(telemetry.neighbors), false, 1 ); const aclMessage = createLocalMessage( - activeConversation.id, + conversationId, formatAcl(telemetry.acl), false, 2 @@ -173,8 +179,9 @@ export function useRepeaterMode( // Mark as logged in for CLI command mode setRepeaterLoggedIn(true); } catch (err) { + if (activeConversationRef.current?.id !== conversationId) return; const errorMessage = createLocalMessage( - activeConversation.id, + conversationId, `Telemetry request failed: ${err instanceof Error ? err.message : 'Unknown error'}`, false, 0 @@ -182,7 +189,7 @@ export function useRepeaterMode( setMessages((prev) => [...prev, errorMessage]); } }, - [activeConversation, activeContactIsRepeater, setMessages] + [activeConversation, activeContactIsRepeater, setMessages, activeConversationRef] ); // Send CLI command to a repeater (after logged in) @@ -191,20 +198,25 @@ export function useRepeaterMode( if (!activeConversation || activeConversation.type !== 'contact') return; if (!activeContactIsRepeater || !repeaterLoggedIn) return; + const conversationId = activeConversation.id; + // Check for special airtime commands first (handled locally) - const handled = await handleAirtimeCommand(command, activeConversation.id); + const handled = await handleAirtimeCommand(command, conversationId); if (handled) return; // Show the command as an outgoing message - const commandMessage = createLocalMessage(activeConversation.id, `> ${command}`, true, 0); + const commandMessage = createLocalMessage(conversationId, `> ${command}`, true, 0); setMessages((prev) => [...prev, commandMessage]); try { - const response = await api.sendRepeaterCommand(activeConversation.id, command); + const response = await api.sendRepeaterCommand(conversationId, command); + + // User may have switched conversations during the await + if (activeConversationRef.current?.id !== conversationId) return; // Use the actual timestamp from the repeater if available const responseMessage = createLocalMessage( - activeConversation.id, + conversationId, response.response, false, 1 @@ -215,8 +227,9 @@ export function useRepeaterMode( setMessages((prev) => [...prev, responseMessage]); } catch (err) { + if (activeConversationRef.current?.id !== conversationId) return; const errorMessage = createLocalMessage( - activeConversation.id, + conversationId, `Command failed: ${err instanceof Error ? err.message : 'Unknown error'}`, false, 1 @@ -230,6 +243,7 @@ export function useRepeaterMode( repeaterLoggedIn, setMessages, handleAirtimeCommand, + activeConversationRef, ] );