修复消息首页会话预览恒为"暂无消息",即使会话有真实历史消息

message-center-view.jsx 的预览文字读 topic.latestMessage(),这个方法
只会从话题自己的本地消息缓存里取值——而这个缓存只有在话题真正被
subscribe 过一次数据拉取后才会填充。me.contacts()(resetContactList)
给出的是每个联系人真实的、已缓存的Topic实例,包括用户这次会话里
从未打开过的——这些话题的latestMessage()恒为undefined,即使同一个
对象自己的touched/seq字段能证明服务端真实存在历史消息。

新增prefetchLatestMessagePreviews():对chatList里seq>0但本地缓存为空
的话题,一次性拉取最新1条消息填充预览。用一个Set跟踪已尝试过的话题,
避免resetContactList()(几乎每个{meta}/{pres}事件都会触发一次)反复
重新拉取同一个话题。

调试过程中发现并修正了自己犯的两个错误:(1) resetContactList()在
websocket握手完成前就会被本地持久化缓存恢复触发一次,此时的subscribe
必然503失败,加了isConnected()判断跳过,避免过早失败把话题永久标记
为"已尝试";(2) Topic.subscribe(getParams, setParams)的参数顺序搞反,
把查询对象传成了第二个参数(setParams),导致subscribe成功但发出的
{sub}包完全没有get子句,从未真正请求过消息数据。

已用真实浏览器验证:好友kcg051307会话(真实71条历史消息)刷新页面
后预览从"暂无消息"变为真实的最新消息内容,未读角标数字不受影响
(确认没有误将未读消息标记为已读)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-06 10:16:48 +08:00
co-authored by Claude Sonnet 5
parent ca71667975
commit 2ee8bac6a0
+48
View File
@@ -216,6 +216,17 @@ class TinodeWeb extends React.Component {
this.selfRef = React.createRef(); this.selfRef = React.createRef();
// message-center-view.jsx's conversation-list preview reads topic.latestMessage(), which the
// vendored SDK only ever populates from a topic's own local message cache -- itself only filled
// in once that specific topic has actually been subscribed with a data fetch. me.contacts()
// (resetContactList below) yields the real, already-cached Topic instances for every contact --
// including ones the user has never opened this session -- so most rows fall straight through to
// the '暂无消息' fallback despite `touched`/`seq` on the same object proving real history exists
// server-side. This tracks which topics we've already kicked off a one-shot "just the last
// message" fetch for, so repeated resetContactList() calls (it fires on nearly every {meta}/{pres}
// event, not just once at boot) don't re-subscribe the same topic over and over.
this.previewPrefetchAttempted = new Set();
this.state = this.getBlankState(); this.state = this.getBlankState();
this.handleResize = this.handleResize.bind(this); this.handleResize = this.handleResize.bind(this);
@@ -1284,6 +1295,8 @@ class TinodeWeb extends React.Component {
} }
}); });
this.prefetchLatestMessagePreviews(newState.chatList);
const past = new Date(0); const past = new Date(0);
newState.chatList.sort((a, b) => { newState.chatList.sort((a, b) => {
// Pinned topics take precedence. // Pinned topics take precedence.
@@ -1297,6 +1310,41 @@ class TinodeWeb extends React.Component {
this.setState(newState); this.setState(newState);
} }
// See the comment on this.previewPrefetchAttempted in the constructor for why this exists.
// seq > 0 is the same 'does this topic actually have any messages at all' signal
// message-center-view.jsx would otherwise have no way to distinguish from 'genuinely empty' --
// skip topics with no history instead of firing a subscribe that would just come back empty.
//
// !this.tinode.isConnected() live repro: resetContactList() fires as soon as the persisted local
// cache restores 'me's last-known contact list (log line "Persistent cache initialized"), which is
// *before* the websocket has even sent {hi} -- every subscribe() call made at that point instantly
// rejects with a 503 "Websocket is not connected". Skip attempting entirely while disconnected
// (rather than catching the failure) so these topics *aren't* added to previewPrefetchAttempted yet
// -- resetContactList() runs again moments later once the real {meta topic:'me'} response arrives
// over the now-live connection, and this gets a genuine second try then.
prefetchLatestMessagePreviews(chatList) {
if (!this.tinode.isConnected()) {
return;
}
for (const c of chatList) {
if (!c.topic || c.seq <= 0 || c.latestMessage() || this.previewPrefetchAttempted.has(c.topic)) {
continue;
}
this.previewPrefetchAttempted.add(c.topic);
// Topic.subscribe(getParams, setParams) -- the query is the *first* argument. Passing it
// second (as setParams) was silently swallowed: the resulting {sub} packet had no `get`
// clause at all (confirmed live: `out: {"sub":{"id":...,"topic":"..."}}`, no `get` key), so
// subscribe succeeded but never actually requested -- let alone received -- any message data.
c.subscribe(c.startMetaQuery().withLaterData(1).build()).catch(err => {
// Leave it in previewPrefetchAttempted either way -- a topic that fails once we're
// definitely connected (e.g. a stale contact the user no longer has access to) isn't going
// to succeed on the next resetContactList() a moment later either, and would otherwise retry
// on every single {meta}/{pres} event for the rest of the session.
console.warn(`Failed to prefetch latest message for '${c.topic}'`, err);
});
}
}
static checkIfPinsUpdated(me, chatList) { static checkIfPinsUpdated(me, chatList) {
for (let i = 0; i < chatList.length; i++) { for (let i = 0; i < chatList.length; i++) {
const c = chatList[i]; const c = chatList[i];