From 00459bbbcb3bcde742f4a10d9fa0c7bf7aec56ce Mon Sep 17 00:00:00 2001 From: jolavillette Date: Tue, 18 Aug 2026 09:26:52 +0200 Subject: [PATCH] webui v150: Leave Chat reported a closed tunnel whatever the core answered rsJsonApiRequest hands the callback the HTTP status, not the answer, and the button read that as "the tunnel is closed": any reply at all, including one that says nothing was closed, ended the conversation on screen. The core does report it, in retval, so that is what is read now, and the ended card says which of the two happened -- closed on request, or already gone before the click. The status poll had the same deafness and is what let it happen. Once a tunnel is gone from the core -- died of inaction, closed by the peer -- getDistantChatStatus answers false, and the poll only ever looked at the branch where it answers true. So the last known status stayed on screen for good: a conversation whose tunnel had been dead for a while kept its green dot and its "You can talk", and Leave Chat then had, quite correctly, nothing to close. Two consecutive false answers now end the conversation, with a line saying so, which is the state the user was in. Needs the libretroshare side to be meaningful: fix/distant-chat-close-keeps-contact makes closeDistantChatConnexion report whether it closed anything, and drop the distant chat contact it used to leave behind. Co-Authored-By: Claude Opus 5 (1M context) --- webui-src/app/main.js | 4 +- webui-src/app/people/people_chat_tab.js | 22 ++++---- webui-src/app/people/people_state.js | 68 ++++++++++++++++++++----- 3 files changed, 66 insertions(+), 28 deletions(-) diff --git a/webui-src/app/main.js b/webui-src/app/main.js index 22bed62..12e7247 100644 --- a/webui-src/app/main.js +++ b/webui-src/app/main.js @@ -114,7 +114,7 @@ const navbar = () => { ? 'Connected to RetroShare Core' : 'Connection Lost', }), - m('span.webui-version', { style: { fontSize: '0.7em' } }, 'v149'), + m('span.webui-version', { style: { fontSize: '0.7em' } }, 'v150'), m('i.fas.fa-sync-alt.refresh-icon', { style: { cursor: 'pointer', fontSize: '0.8em' }, onclick: () => window.location.reload(true), @@ -236,7 +236,7 @@ const MobileStatus = () => { m('small', statusbar.formatBytes(state.totalOut)), ]), ]), - m('.mobile-status-sheet__version', 'WebUI v149'), + m('.mobile-status-sheet__version', 'WebUI v150'), ])), ]; }, diff --git a/webui-src/app/people/people_chat_tab.js b/webui-src/app/people/people_chat_tab.js index 7896f35..d5a7f01 100644 --- a/webui-src/app/people/people_chat_tab.js +++ b/webui-src/app/people/people_chat_tab.js @@ -7,7 +7,7 @@ const { getStatusTooltip, initializeDistantChat, sendDistantChatMessage, - stopStatusPolling, + leaveDistantChat, loadAllHistoryForSelectedPeer, } = require('people/people_state'); const { renderChatMessage } = require('chat/chat_state'); @@ -112,7 +112,9 @@ const ChatTab = () => { return m('.chat-warning', [ m('i.fas.fa-unlink', { style: 'font-size: 2rem; color: #ef4444; margin-bottom: 1rem;' }), m('h4', 'Conversation Ended'), - m('p', 'You have closed the distant chat tunnel. Click below to reconnect.'), + m('p', State.chatCloseFoundNothing + ? 'The tunnel was already gone: the core had no connection left to close. Click below to open a new one.' + : 'You have closed the distant chat tunnel. Click below to reconnect.'), m('button.blue', { style: 'margin-top: 1rem; padding: 0.5rem 1.5rem; border-radius: 0.375rem; border: none; font-weight: 600; cursor: pointer;', onclick: () => initializeDistantChat(), @@ -191,17 +193,11 @@ const ChatTab = () => { pid: State.chatPid, }, (data, success) => { - if (success) { - if (State.selectedId && State.activeDistantChats[State.selectedId]) { - delete State.activeDistantChats[State.selectedId]; - } - State.chatPid = null; - State.chatMessages = []; - State.distantChatStatus = null; - State.chatDisconnected = true; - stopStatusPolling(); - m.redraw(); - } + // `success` is the HTTP status, not the answer: the core + // says in retval whether it had anything to close. Taking + // 200 for a closed tunnel is how this button could report + // a conversation as ended while the tunnel lived on. + leaveDistantChat(Boolean(success && data && data.retval)); } ); } diff --git a/webui-src/app/people/people_state.js b/webui-src/app/people/people_state.js index 418960b..efdd795 100644 --- a/webui-src/app/people/people_state.js +++ b/webui-src/app/people/people_state.js @@ -29,6 +29,8 @@ const State = { fullHistoryMessages: [], isHistoryLoading: false, pendingChatOpen: null, // gxsId a chat was explicitly asked for from another page + chatCloseFoundNothing: false, // the core had no connection left to close + statusPollFailures: 0, // consecutive getDistantChatStatus answers of false }; function getDistantChatSession(gxsId) { @@ -315,22 +317,42 @@ function pollDistantChatStatus() { pid: State.chatPid, }, (detail, success) => { - if (success && detail.retval) { - State.distantChatStatus = detail.info; - if (session) session.status = detail.info; - - if (session) { - if (detail.info.status === 2) { - addSessionSystemMessage(session, 'Tunnel is secured. You can talk!'); - // The tunnel just went up: anything the peer sent while it was - // still pending is waiting in the event buffer. - drainBufferedChatMessages(session); - } else if (detail.info.status === 3) { - addSessionSystemMessage(session, 'Your partner closed the conversation.'); + // getDistantChatStatus answers false once the tunnel is gone from the + // core -- died of inaction, closed by the peer, closed by us. Ignoring + // that answer left the last known status on screen for good: a dead + // conversation kept its green dot and its "You can talk", and the Leave + // button then had nothing left to close. + if (!success || !detail || !detail.retval) { + State.statusPollFailures += 1; + if (State.statusPollFailures >= 2) { + if (session) { + addSessionSystemMessage(session, 'The distant chat tunnel is gone.'); + session.disconnected = true; } + State.distantChatStatus = null; + State.chatDisconnected = true; + State.chatCloseFoundNothing = false; + stopStatusPolling(); + m.redraw(); } - m.redraw(); + return; } + + State.statusPollFailures = 0; + State.distantChatStatus = detail.info; + if (session) { + session.status = detail.info; + + if (detail.info.status === 2) { + addSessionSystemMessage(session, 'Tunnel is secured. You can talk!'); + // The tunnel just went up: anything the peer sent while it was still + // pending is waiting in the event buffer. + drainBufferedChatMessages(session); + } else if (detail.info.status === 3) { + addSessionSystemMessage(session, 'Your partner closed the conversation.'); + } + } + m.redraw(); } ); } @@ -396,6 +418,8 @@ function initializeDistantChat(force = false) { State.chatMessages = session.messages; State.distantChatStatus = null; State.chatDisconnected = false; + State.chatCloseFoundNothing = false; + State.statusPollFailures = 0; m.redraw(); rs.rsJsonApiRequest( @@ -524,6 +548,23 @@ function sendDistantChatMessage() { ); } +// Ending the conversation on our side. `closed` is what the core answered: +// false means it had no connection left for that tunnel id, which the card +// then says rather than claiming the user just closed something. +function leaveDistantChat(closed) { + if (State.selectedId && State.activeDistantChats[State.selectedId]) { + delete State.activeDistantChats[State.selectedId]; + } + State.chatPid = null; + State.chatMessages = []; + State.distantChatStatus = null; + State.chatDisconnected = true; + State.chatCloseFoundNothing = !closed; + State.statusPollFailures = 0; + stopStatusPolling(); + m.redraw(); +} + // Live incoming distant chat message, coming from the rsEvents stream. function receiveDistantChatMessage(chatMessage) { const msgCid = chatMessage && chatMessage.chat_id; @@ -830,5 +871,6 @@ module.exports = { initializeDistantChat, loadChatMessages, sendDistantChatMessage, + leaveDistantChat, };