转发消息的来源标记改用手机端同款字段,修复网页转发在手机端认不出"转发"的问题

网页版转发功能此前一直用自造的 head.forwarded: "topic:seq" 单字符串字段记录
来源,且这个字段在整个网页项目里从未被任何地方读取过(纯写入,无消费者)。

数据库排查(排查转发内容丢失bug时顺带确认)发现手机端自己的转发用的是完全
不同的字段:head['x-im-forwarded']:true + head['x-im-forward-source']:
{seq,from,topic},是真实双端数据。网页版这个自造格式手机端根本不认识,
意味着从网页转发出去的消息,即使内容显示正常,手机端也大概率认不出"这是
一条转发消息",不会显示对应的转发来源提示(如果手机端UI有这个功能的话)。

改成手机端同款的两字段格式。确认这不影响网页自己的任何逻辑(旧字段确认无
读取方)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-25 11:34:44 +08:00
co-authored by Claude Sonnet 5
parent ed2920275d
commit 1d383b4e93
+18 -3
View File
@@ -206,8 +206,19 @@ export function sendReply(topic: Topic, text: string, quoted: StoredMessage): Pr
* Single-message forward, sent directly to one target topic. Simplified from the old app's own * Single-message forward, sent directly to one target topic. Simplified from the old app's own
* flow (which stages the forwarded content and lets the user add a comment via the normal composer * flow (which stages the forwarded content and lets the user add a comment via the normal composer
* before sending) -- this sends immediately on picking a target, trading that one nicety for a * before sending) -- this sends immediately on picking a target, trading that one nicety for a
* much smaller "pick a contact" dialog. `head.forwarded` records the source for traceability; it * much smaller "pick a contact" dialog.
* has no reader anywhere yet, matching the old app (no confirmed call site reads it back either). *
* `head['x-im-forwarded']`/`head['x-im-forward-source']` (not the bare `forwarded: "topic:seq"`
* string this used to write): confirmed via direct DB inspection (the same investigation that found
* `flatForwardPayload`'s root cause) that mobile's OWN forwards use exactly this two-field shape --
* `{"x-im-forwarded":true,"x-im-forward-source":{"seq":N,"from":"usr...","topic":"..."}}` -- a real,
* live example from a genuine mobile-to-mobile forward chain, not a guess. The bare `forwarded`
* string this web app used to write instead had zero readers anywhere in this codebase (confirmed:
* grepped for it), so nothing here regresses by switching -- but writing it meant every message this
* app forwards was tagged in a shape mobile's own client has no reason to recognize, which would
* silently fail to show a "转发自 XXX" indicator (or whatever mobile's UI does with this) for any
* message forwarded from web, even though the message CONTENT itself displays fine either way
* (content and forward-provenance are two independent things carried in the same packet).
*/ */
export function forwardMessage(sourceTopic: string, msg: StoredMessage, target: Topic): Promise<unknown> { export function forwardMessage(sourceTopic: string, msg: StoredMessage, target: Topic): Promise<unknown> {
const flat = flatForwardPayload(msg); const flat = flatForwardPayload(msg);
@@ -216,6 +227,10 @@ export function forwardMessage(sourceTopic: string, msg: StoredMessage, target:
// lib/tinode/calls.ts's own identical cast) passes a plain object straight through as literal // lib/tinode/calls.ts's own identical cast) passes a plain object straight through as literal
// `content` -- true for the attachment/contact-card/call-result branches of `flatForwardPayload`. // `content` -- true for the attachment/contact-card/call-result branches of `flatForwardPayload`.
const newMsg = target.createMessage(content as unknown as DraftyContent, false); const newMsg = target.createMessage(content as unknown as DraftyContent, false);
newMsg.head = Object.assign(newMsg.head || {}, flat?.headOverrides, { forwarded: `${sourceTopic}:${msg.seq}`, 'x-im-client-mid': newClientMid() }); newMsg.head = Object.assign(newMsg.head || {}, flat?.headOverrides, {
'x-im-forwarded': true,
'x-im-forward-source': { seq: msg.seq, from: msg.from, topic: sourceTopic },
'x-im-client-mid': newClientMid(),
});
return publishWithReattach(target, newMsg); return publishWithReattach(target, newMsg);
} }