修复撤回消息在对方界面显示"无效内容"而非"消息已删除"

现象:真实调用撤回接口成功后(recall_window_seconds=120秒窗口内,长按菜单
"撤回"),撤回发起方自己的消息列表里这条消息直接消失,没有问题;但对方
(消息接收方)的聊天界面上,这条消息位置渲染成一个灰色警告图标+"无效内容"
(invalid content),不是预期的"此消息已被撤回/删除"占位提示。

根因:Tinode原生协议里,一条消息被撤回后,服务端把这条消息的content原地
置为null(seq位置保留,其余内容清空)。chat-message.jsx渲染消息内容时按
Drafty富文本、纯字符串、其余情况三段分支处理,content为null时两段判断
(Drafty.isValid和typeof=='string')都不成立,直接落到兜底的"invalid
content"分支——这段代码从未加过null-content(撤回)的专门判断。这跟
messages-view.jsx里另一套依赖msg.hi标记位的"删除"消息渲染(走独立的
<MetaMessage deletedText=.../>组件)是两码事:撤回消息还是走的普通
ChatMessage组件,从未接入deletedText这个机制。

修复:chat-message.jsx新增一个content==null的分支,复用
messages-view.jsx里messages.message_deleted已经注册好的同一个i18n
id/文案("消息已删除",非新增翻译串),渲染为灰色图标+"消息已删除",
不再落到"无效内容"兜底分支。

验证:真实浏览器操作,两个全新注册账号互为好友,A发一条文本消息后
立即长按撤回(120秒窗口内),B的聊天界面上这条消息正确显示"消息已删除"
(此前是"无效内容")。

第14+轮最小粒度测试 Ch4 发现(撤回消息渲染场景)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-06 16:12:38 +08:00
co-authored by Claude Sonnet 5
parent b200dc2525
commit 8cb96e9254
+17
View File
@@ -374,6 +374,23 @@ class BaseChatMessage extends React.PureComponent {
// Content consists of 1-5 emoji characters. Count how many and use it to increase the font size.
textSizeClass += ' emoji-' + (content || '').match(/\p{RGI_Emoji}/vg).length;
}
} else if (content == null) {
// Tinode's wire protocol marks a recalled message by nulling its `content` in place (the seq
// slot stays, everything else is gone) -- see server's message.recalled handling and
// IM-前后端对接文档.md 1256行. That's a *different* deletion shape from the msg.hi-flagged kind
// messages-view.jsx already handles by rendering a dedicated <MetaMessage deleted deletedText=.../>
// instead of a ChatMessage at all (deletedText is that component's own prop, not this one's --
// there's no equivalent plumbed in here). Recalled messages still go through ordinary ChatMessage,
// so without this branch a null content fell all the way through to the generic "invalid content"
// case below -- confirmed live: recalling a message left the *other* party's chat showing a bare
// "⚠ invalid content" (无效内容) row where "消息已删除" belongs (the recalling side just drops the
// row from its own list locally, unaffected by this). Reuses the exact same i18n id/defaultMessage
// messages-view.jsx's `messages.message_deleted` already registers (translated zh string:
// "消息已删除"), not a new string. `content == null` (not `===`) intentionally also catches
// `undefined`, same as every other falsy-content branch here.
content = <><i className="material-icons gray">block</i> <i className="gray">
<FormattedMessage id="message_deleted" defaultMessage="message deleted"
description="Placeholder shown in the timeline where a message was recalled or deleted" /></i></>;
} else {
content = <><i className="material-icons gray">warning_amber</i> <i className="gray">
<FormattedMessage id="invalid_content"