diff --git a/webui-src/app/main.js b/webui-src/app/main.js index 6c03884..6804436 100644 --- a/webui-src/app/main.js +++ b/webui-src/app/main.js @@ -1,7 +1,7 @@ const m = require('mithril'); // Bumped at every change of the web UI (two places used to carry it). -const WEBUI_VERSION = 'v166'; +const WEBUI_VERSION = 'v167'; const login = require('login'); const rs = require('rswebui'); diff --git a/webui-src/app/people/people.js b/webui-src/app/people/people.js index cf7dac4..9dc87e9 100644 --- a/webui-src/app/people/people.js +++ b/webui-src/app/people/people.js @@ -13,6 +13,7 @@ const { startStatusPolling, stopStatusPolling, initializeDistantChat, + selectChatContact, getDistantChatSession, drainBufferedChatMessages, markDistantChatRead, @@ -67,12 +68,13 @@ const PeopleLayout = () => { }); window.addEventListener('click', dismissMenu); - if (State.chatPid && !State.chatDisconnected) { + // Only poll a tunnel that is the selected contact's own; anything + // else is left over from a previous selection. + const selectedSession = State.selectedId ? getDistantChatSession(State.selectedId) : null; + if (State.chatPid && !State.chatDisconnected && selectedSession && selectedSession.pid === State.chatPid) { // Messages received while the tab was unmounted sit in the event // queue buffer: pick them up before the first redraw. - if (State.selectedId) { - drainBufferedChatMessages(getDistantChatSession(State.selectedId)); - } + drainBufferedChatMessages(selectedSession); startStatusPolling(); } }, @@ -190,6 +192,7 @@ PeopleLayout.setSelectedId = (id, activeTab = 'details', showCompose = false) => State.activeFilter = filter; State.selectedId = id; + selectChatContact(id); State.activeTab = activeTab; State.pendingChatOpen = activeTab === 'chat' ? id : null; State.mobilePane = 'detail'; diff --git a/webui-src/app/people/people_chat_tab.js b/webui-src/app/people/people_chat_tab.js index 4dba5b1..93ff967 100644 --- a/webui-src/app/people/people_chat_tab.js +++ b/webui-src/app/people/people_chat_tab.js @@ -121,7 +121,9 @@ const ChatTab = () => { m('h4', 'Conversation Ended'), 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.'), + : State.chatEndedByPoll + ? 'The tunnel went away: closed by your contact, or dropped by the core. 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(), diff --git a/webui-src/app/people/people_state.js b/webui-src/app/people/people_state.js index 1e79ed4..9a85823 100644 --- a/webui-src/app/people/people_state.js +++ b/webui-src/app/people/people_state.js @@ -31,6 +31,7 @@ const State = { isHistoryLoading: false, pendingChatOpen: null, // gxsId a chat was explicitly asked for from another page chatCloseFoundNothing: false, // the core had no connection left to close + chatEndedByPoll: false, // the status poll saw the tunnel go, we did not close it statusPollFailures: 0, // consecutive getDistantChatStatus answers of false showEmojiPicker: false, attachPath: '', // file being hashed for a retroshare:// link @@ -365,14 +366,20 @@ function getStatusTooltip(status) { function pollDistantChatStatus() { if (!State.chatPid) return; - const session = State.selectedId ? getDistantChatSession(State.selectedId) : null; + // Captured now: the answer lands seconds later on a slow link, and by then + // the user may be on another contact, or the page on another tunnel. An + // answer about a stale pid used to mark the new conversation as ended. + const pid = State.chatPid; + const askedFor = State.selectedId; + const session = askedFor ? getDistantChatSession(askedFor) : null; rs.rsJsonApiRequest( '/rsChats/getDistantChatStatus', { - pid: State.chatPid, + pid, }, (detail, success) => { + if (State.chatPid !== pid || State.selectedId !== askedFor) return; // 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 @@ -388,6 +395,7 @@ function pollDistantChatStatus() { State.distantChatStatus = null; State.chatDisconnected = true; State.chatCloseFoundNothing = false; + State.chatEndedByPoll = true; stopStatusPolling(); m.redraw(); } @@ -530,6 +538,7 @@ function openDistantChat(session) { State.distantChatStatus = null; State.chatDisconnected = false; State.chatCloseFoundNothing = false; + State.chatEndedByPoll = false; State.statusPollFailures = 0; State.chatInputMsg = session.inputMsg || ''; m.redraw(); @@ -695,6 +704,7 @@ function leaveDistantChat(closed) { State.distantChatStatus = null; State.chatDisconnected = true; State.chatCloseFoundNothing = !closed; + State.chatEndedByPoll = false; State.statusPollFailures = 0; stopStatusPolling(); m.redraw(); @@ -721,6 +731,23 @@ function findDistantChatSession(msgPid) { return { session, targetGxsId }; } +// The page-wide chat fields (pid, status, messages) belong to one contact at +// a time. Selecting another one must not leave them pointing at the previous +// tunnel: the mount-time poll then asked about a pid the core may have +// dropped, and its "gone" answer ended the new conversation before it began. +function selectChatContact(gxsId) { + stopStatusPolling(); + const session = gxsId ? getDistantChatSession(gxsId) : null; + State.chatPid = session ? session.pid : null; + State.chatMessages = session ? session.messages : []; + State.distantChatStatus = session ? session.status : null; + State.chatDisconnected = session ? Boolean(session.disconnected) : false; + State.chatCloseFoundNothing = false; + State.chatEndedByPoll = false; + State.statusPollFailures = 0; + State.chatInputMsg = session ? (session.inputMsg || '') : ''; +} + function isDistantChatActive(gxsId) { const session = gxsId && State.activeDistantChats[gxsId]; return Boolean( @@ -1078,6 +1105,7 @@ module.exports = { loadChatMessages, sendDistantChatMessage, leaveDistantChat, + selectChatContact, setChatDraft, switchChatIdentity, refreshSelectedIdDetails,