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) <noreply@anthropic.com>
This commit is contained in:
jolavillette 2026-08-18 09:26:52 +02:00
parent 6f2b72c3f1
commit 00459bbbcb
3 changed files with 66 additions and 28 deletions

View File

@ -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'),
])),
];
},

View File

@ -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));
}
);
}

View File

@ -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,
};