mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-09-27 15:48:54 +00:00
core: do not copy customer profile description to business chat welcome message
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
# Customer profile description must not become the business chat welcome message
|
||||
|
||||
## Problem
|
||||
|
||||
A support bot running a business address had a customer's personal introduction ("Hi, I'm …") show up in that customer's business chat as a message from the business ("Ask SimpleX Team", owner). The customer never wrote it into the chat, and team members added to the chat saw it as the business's own message. We confirmed the text was the customer's profile description.
|
||||
|
||||
## Cause
|
||||
|
||||
`Profile.description` was added in #7256. The same PR also copied it into the group profile of a business chat. This happens in `updateGroupProfileFromMember`, called from `updateBusinessChatProfile` in `processMemberProfileUpdate` when the main business member's profile changes.
|
||||
|
||||
That copy is correct on the customer side (`BCBusiness`), where the business's description becomes the chat's welcome message. On the business side (`BCCustomer`), the same code copied the customer's description into `GroupProfile.description`. That field serves as the group welcome message (`Types.hs`: "this has been repurposed as welcome message").
|
||||
|
||||
The business then passes it on as its own message:
|
||||
|
||||
- When the business adds a member who is not the customer (a team member, or a bot's assistant), `sendHistory` runs.
|
||||
- `sendHistory` appends a welcome element: a new `XMsgNew (MCText description)` authored by the host with a fresh random shared message ID. Invited members have no join-request welcome message ID, so the element is always included.
|
||||
- Every member added later therefore sees the customer's bio as a message from the business.
|
||||
- The welcome element is re-sent to each member added later, even after the customer changes or removes the bio.
|
||||
|
||||
To trigger it, the customer changes their profile description while in the business chat and then sends a message there. The profile update goes out lazily with the next message.
|
||||
|
||||
## Fix
|
||||
|
||||
`updateGroupProfileFromMember` now takes the `BusinessChatType`:
|
||||
|
||||
- **`BCBusiness`** (customer side): unchanged. The business's description is copied as the welcome message.
|
||||
- **`BCCustomer`** (business side): the customer's display name, full name, short description and image are still copied. The chat's existing description (welcome message) is kept.
|
||||
|
||||
The business side keeps its existing description rather than clearing it, so a welcome message the business set itself is preserved. `createBusinessRequestGroup` never copies the customer's description either, so after the fix the business side never takes its welcome message from the customer.
|
||||
|
||||
Existing chats that already have a customer description stored as their welcome message are left unchanged. No migration is included.
|
||||
|
||||
## Test
|
||||
|
||||
`testBusinessCustomerDescription` (`tests/ChatTests/Profiles.hs`):
|
||||
|
||||
1. The customer connects to a business address.
|
||||
2. The customer sets a profile description and sends a message.
|
||||
3. The business adds a team member.
|
||||
4. The test checks that the business side reports no welcome message change, and that the team member receives only the real history.
|
||||
|
||||
Without the fix, the test fails with `welcome message changed to:` on the business side.
|
||||
@@ -2862,8 +2862,8 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage =
|
||||
p'' = redactedMemberProfile gInfo m p'
|
||||
contentChanged = not (sameProfileContent (redactedMemberProfile gInfo m (fromLocalProfile p)) p'')
|
||||
updateBusinessChatProfile g@GroupInfo {businessChat} = case businessChat of
|
||||
Just bc | isMainBusinessMember bc m -> do
|
||||
g' <- withStore $ \db -> updateGroupProfileFromMember db user g p'
|
||||
Just bc@BusinessChatInfo {chatType} | isMainBusinessMember bc m -> do
|
||||
g' <- withStore $ \db -> updateGroupProfileFromMember db user g chatType p'
|
||||
toView $ CEvtGroupUpdated user g g' (Just m) Nothing
|
||||
_ -> pure ()
|
||||
isMainBusinessMember BusinessChatInfo {chatType, businessId, customerId} GroupMember {memberId} = case chatType of
|
||||
|
||||
@@ -2797,11 +2797,14 @@ updateGroupPreferences db User {userId} g@GroupInfo {groupId, groupProfile = p}
|
||||
(ps, currentTs, userId, groupId)
|
||||
pure (g :: GroupInfo) {groupProfile = p {groupPreferences = Just ps}, fullGroupPreferences = mergeGroupPreferences $ Just ps}
|
||||
|
||||
updateGroupProfileFromMember :: DB.Connection -> User -> GroupInfo -> Profile -> ExceptT StoreError IO GroupInfo
|
||||
updateGroupProfileFromMember db user g@GroupInfo {groupId} Profile {displayName = n, fullName = fn, shortDescr = sd, description = descr, image = img} = do
|
||||
p <- getGroupProfile -- to avoid any race conditions with UI
|
||||
updateGroupProfileFromMember :: DB.Connection -> User -> GroupInfo -> BusinessChatType -> Profile -> ExceptT StoreError IO GroupInfo
|
||||
updateGroupProfileFromMember db user g@GroupInfo {groupId} chatType Profile {displayName = n, fullName = fn, shortDescr = sd, description = descr, image = img} = do
|
||||
p@GroupProfile {description = gDescr} <- getGroupProfile -- to avoid any race conditions with UI
|
||||
let g' = g {groupProfile = p} :: GroupInfo
|
||||
p' = p {displayName = n, fullName = fn, shortDescr = sd, description = descr, image = img} :: GroupProfile
|
||||
descr' = case chatType of
|
||||
BCBusiness -> descr
|
||||
BCCustomer -> gDescr
|
||||
p' = p {displayName = n, fullName = fn, shortDescr = sd, description = descr', image = img} :: GroupProfile
|
||||
updateGroupProfile db user g' p'
|
||||
where
|
||||
getGroupProfile =
|
||||
|
||||
@@ -74,6 +74,7 @@ chatProfileTests = do
|
||||
describe "business address" $ do
|
||||
it "create and connect via business address" testBusinessAddress
|
||||
it "update profiles with business address" testBusinessUpdateProfiles
|
||||
it "customer description is not sent as business welcome message" testBusinessCustomerDescription
|
||||
describe "contact address connection plan" $ do
|
||||
it "contact address ok to connect; known contact" testPlanAddressOkKnown
|
||||
it "own contact address" testPlanAddressOwn
|
||||
@@ -1318,6 +1319,44 @@ testBusinessUpdateProfiles = testChat4 businessProfile aliceProfile bobProfile c
|
||||
bob #$> ("/_get chat #1 count=1", chat, [(0, "Full deletion: on")])
|
||||
cath #$> ("/_get chat #1 count=1", chat, [(0, "Full deletion: on")])
|
||||
|
||||
testBusinessCustomerDescription :: HasCallStack => TestParams -> IO ()
|
||||
testBusinessCustomerDescription = testChat3 businessProfile aliceProfile cathProfile $
|
||||
\biz alice cath -> do
|
||||
biz ##> "/ad"
|
||||
cLink <- getContactLink biz True
|
||||
biz ##> "/auto_accept on business"
|
||||
biz <## "auto_accept on, business"
|
||||
alice ##> ("/c " <> cLink)
|
||||
alice <## "connection request sent!"
|
||||
biz <## "#alice (Alice): accepting business address request..."
|
||||
alice <## "#biz: joining the group..."
|
||||
biz <## "#alice: alice_1 joined the group"
|
||||
alice <## "#biz: you joined the group"
|
||||
alice ##> "/_profile 1 {\"displayName\": \"alice\", \"fullName\": \"\", \"shortDescr\": \"Alice\", \"description\": \"Hi, I'm Alice\"}"
|
||||
alice <## "user description changed to Hi, I'm Alice (your 0 contacts are notified)"
|
||||
alice #> "#biz hello" -- profile update is sent with message
|
||||
biz <# "#alice alice_1> hello"
|
||||
connectUsers biz cath
|
||||
biz ##> "/a #alice cath"
|
||||
biz <## "invitation to join the group #alice sent to cath"
|
||||
cath <## "#alice (Alice): biz invites you to join the group as member"
|
||||
cath <## "use /j alice to accept"
|
||||
cath ##> "/j alice"
|
||||
concurrentlyN_
|
||||
[ do
|
||||
cath <## "#alice: you joined the group"
|
||||
cath <### [WithTime "#alice alice_1> hello [>>]"]
|
||||
cath <## "#alice: member alice_1 (Alice) is connected",
|
||||
biz <## "#alice: cath joined the group",
|
||||
do
|
||||
alice <## "#biz: biz_1 added cath (Catherine) to the group (connecting...)"
|
||||
alice <## "#biz: new member cath is connected"
|
||||
]
|
||||
cath #> "#alice hi"
|
||||
concurrently_
|
||||
(alice <# "#biz cath> hi")
|
||||
(biz <# "#alice cath> hi")
|
||||
|
||||
testPlanAddressOkKnown :: HasCallStack => TestParams -> IO ()
|
||||
testPlanAddressOkKnown =
|
||||
testChat2 aliceProfile bobProfile $
|
||||
|
||||
Reference in New Issue
Block a user