* core: include channel link and name when forwarding messages * wip * simplify * add member ID * refactor * refactor * refactor * update api types * store forward source group type * rename * api types * simpler layout * layout, translations * refactor ios * public * simpler * refactor kotlin * padding * padding --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
9.2 KiB
Forward attribution: forwardLink in MsgContainer
When a message is forwarded from a channel (public group), the sending client attaches the source channel's name, join link, identity and message id; recipients see "forwarded from <name>" and can open or join the channel.
- The link is attached whenever the source is a public group; for other sources
only
forward: trueis sent. forward = Just Trueis always set alongsideforwardLink, so old clients show plain "forwarded".- The simplex name is not included: paired with a forwarder-chosen link it would be an unverifiable claim. It can be added later as a verifiable claim.
- When a forwarded message is received in a group that prohibits SimpleX links for the sender, the link is removed.
Protocol
Protocol.hs. aeson ignores unknown fields and parses an absent field as
Nothing, so the addition is compatible in both directions.
data ForwardLink = ForwardLink
{ displayName :: Text,
groupLink :: ShortLinkContact,
publicGroupId :: B64UrlByteString, -- the recipient looks up the local group by this id, then compares groupLink with the stored link
memberId :: Maybe MemberId, -- the author, only for items the author sent as themselves
msgId :: SharedMsgId -- the original item's SharedMsgId
}
memberId is absent for items sent as the channel: their authorship is the
channel's, and subscribers do not see the author's member id. The fill rule is
chatItemMember (Messages.hs:369): the member for received authored items,
the membership for own items sent as themselves, absent otherwise.
- New field
forwardLink :: Maybe ForwardLinkinMsgContainer(Protocol.hs:678) afterforward;mcSimple(:695) setsforwardLink = Nothing. mcForward(:716) takesMaybe ForwardLink:mcForward fl c = (mcSimple c) {forward = Just True, forwardLink = fl}.- JSON instances:
deriveJSON defaultJSON ''ForwardLinkbefore the''MsgContainersplice (:899).
CIForwardedFrom
Messages.hs:1319:
| CIFFGroup {chatName :: Text, msgDir :: MsgDirection, groupId :: Maybe GroupId,
chatItemId :: Maybe ChatItemId, memberId :: Maybe MemberId,
sharedMsgId_ :: Maybe SharedMsgId, groupType :: Maybe GroupType}
| CIFFGroupLink {chatName :: Text, msgDir :: MsgDirection,
groupLink :: ShortLinkContact, publicGroupId :: B64UrlByteString,
memberId :: Maybe MemberId, sharedMsgId :: SharedMsgId,
groupType :: Maybe GroupType}
Both variants retain the wire memberId and sharedMsgId, so a re-forward
re-serializes ForwardLink from the CIFF without item lookups.
groupTypeinCIFFGroupis present exactly when the sent message included the link, so it doubles as that marker; the apps read it for the source type icon without lookups. InCIFFGroupLinkit mirrors the link's type so the apps avoid inspecting the URI.CIFFGroupLinkis the recipient's variant for an unknown channel; the user opens it via the connection plan.- New tag
CIFFGroupLink_/"groupLink"inCIForwardedFromTag(:1325).
Sending
Commands.hs APIForwardChatItems, prepareForward group branch (:1094-1110):
- Local
ciff:CIFFGroupwithmemberId = memberId' <$> chatItemMember gInfo ci, the item'sitemSharedMsgId, andgroupType = itemSharedMsgId *> sourceGroupType gInfo-Justthe source profile's type under the same condition in whichciffForwardLinklater returns a link (the link value is computed at themcForwardcall site, after theciffis built). - The two
mcForwardcall sites -sendContactContentMessages.prepareMsgs(Commands.hs:4772) andprepareGroupMsg(Internal.hs:208-209), both matching(Nothing, Just _) -> pure (mcForward mc, Nothing)on(quotedItemId, itemForwarded)- compute the link from theCIForwardedFromin scope:ciffForwardLink db ciffreturns the link forCIFFGroupwithgroupIdandsharedMsgIdset, reading the group profile (current name and link), forCIFFGroupLinkfrom its stored fields, andNothingfor other variants. Deriving from the storedCIForwardedFromattributes a re-forwarded message to the original source. forwardCIFF(:1130) already returns the originalCIForwardedFromwhen a forwarded item is forwarded again, so a receivedCIFFGroupLinkitem is sent onwards with the same link.
Receiving
Store/Messages.hs createNewRcvChatItem (:563-572), inside the existing DB
transaction:
itemForwarded = case chatMsgEvent of
ACME _ (XMsgNew MsgContainer {forward, forwardLink}) | forward == Just True -> ...
forwardLink = Nothing->CIFFUnknown(today's behavior).- Destination is a group where SimpleX links are prohibited for the sender ->
remove the link: store
CIFFGroupwith only the name andmsgDir = MDRcv- attribution text only. The check: the sender's role (the member's forCDGroupRcv,GROwnerforCDChannelRcv- a channel message is posted with owner authority) against the group's SimplexLinks feature. Direct chats: the link is kept. - Lookup by
publicGroupId:group_profiles.public_group_idis a column with an existing query that filters on it (Store/Groups.hs:2009-2015). New querygetGroupViaPublicGroupId; on a match, compare the receivedgroupLinkwith the stored one (sameShortLinkContact); when both match ->CIFFGroupwithgroupId, the wirememberIdandmsgId,groupTypefrom the link'sContactConnType(equal to the stored link's type -sameShortLinkContactcompares it), andchatItemId = ciId_resolved by the id query factored out ofgetGroupChatItemBySharedMsgId(getGroupChatItemBySharedMsgId_). The author scope: wirememberIdabsent ->Nothing(items sent as the channel and own items are stored withgroup_member_idNULL); present -> the member resolved bymember_id, with the user's own membership mapped toNothing; an unknown member -> no item. - Lookup miss, or the link differs from the stored one ->
CIFFGroupLinkwith the wire fields.
DB
chat_items persists CIForwardedFrom as columns (fwd_from_tag, fwd_from_chat_name, fwd_from_msg_dir, fwd_from_contact_id, fwd_from_group_id, fwd_from_chat_item_id, Store/Messages.hs:606). Migration (SQLite + Postgres,
same shape) adds:
fwd_from_group_type TEXT(GroupType'sTextEncoding)fwd_from_group_link BLOB/BYTEA(theToField (ConnShortLink c)instance storesBinary . strEncode, matchingshort_link_contact)fwd_from_public_group_id BLOB/BYTEAfwd_from_member_id BLOB/BYTEAfwd_from_shared_msg_id BLOB/BYTEA
Code changes: the CIFF-to-row tuple (Store/Messages.hs:657-660), the
row-to-CIFF case (:2343-2344), the three SELECT lists (:2696, :3085, :3197),
and the INSERT statement in createNewChatItem_. Binary columns use Binary
on both backends.
View / UI
View.hs:1010: render the source name forCIFFGroupandCIFFGroupLink./item inforenders "forwarded from: #<chatName>" fromitemForwardedwhen the source item is not stored locally (CIFFGroupLinkand link-removedCIFFGroup).- The
CIForwardedFromJSON reaches the apps inCIMeta: the iOS (ChatTypes.swift) and Kotlin (ChatModel.kt) mirrors are extended with the new field and variant. - The sender and the recipient of a forwarded message see the same header; the
only difference between them is the goto arrow, shown where the original
item exists locally (
chatTypeApiIdMsgId), in notes too. - Two-row header at double the single-header height - row 1: forward icon +
"forwarded from" ("saved from" in notes); row 2: the name in the header text
style, starting under the forward icon. Rendered when the attribution is
part of the message -
CIFFGroupLink, andCIFFGroupwithgroupTypepresent - and in notes whenever navigation is possible (a local target or a link), items saved from contacts and p2p groups included. The whole header opens the source: known - the chat, positioned at the original item whenchatItemIdis present; unknown -planAndConnectwithgroupLink. - All other forwards keep the single-line header: "forwarded" (p2p forwards
without attribution, the link-removed name-only
CIFFGroup) or "saved" (non-navigable notes items). Theforwarded_from_descriptionandsaved_from_descriptionstrings are removed; "forwarded from" and "saved from" are added. - The goto arrow beside the bubble applies only to locally resolved items
(
chatTypeApiIdMsgId), never to joining.
Tests
ChatTests/Groups.hs:
- Forward from a channel to a direct chat: the recipient item includes
CIFFGroupLinkwith name/link/publicGroupId/msgId; the view shows "forwarded from" with the name. - Forward to a group where the recipient is a member of the source channel:
the recipient stores
CIFFGroupwith the local groupId. - Destination group with SimpleX links prohibited: the link is removed; attribution text only.
- Forwarding a received forwarded item again sends the original channel's link.
- Old-client compatibility: a container with
forward: trueand noforwardLinkparses toCIFFUnknown. - Private (non-public) source group: the container includes no
forwardLink.