mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-29 04:39:53 +00:00
core: support business addresses and chats (#5272)
* core: support business addresses and chats * types * connect plan, add link type * ios: toggle on address UI * make compile * todo * fix migration * types * comments * fix * remove * fix schema * comment * simplify * remove diff * comment * comment * diff * acceptBusinessJoinRequestAsync wip * comment * update * simplify types * remove business * wip * read/write columns * createBusinessRequestGroup * remove comments * read/write business_address column * validate that business address is not set to be incognito * replace contact card * update simplexmq * refactor * event when accepting business address request * sendGroupAutoReply * delete contact request earlier * test, fix * refactor * refactor2 --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
This commit is contained in:
parent
c488c4fcd5
commit
5f01dc1a3f
23 changed files with 454 additions and 97 deletions
|
@ -16,6 +16,8 @@ struct UserAddressView: View {
|
||||||
@EnvironmentObject var theme: AppTheme
|
@EnvironmentObject var theme: AppTheme
|
||||||
@State var shareViaProfile = false
|
@State var shareViaProfile = false
|
||||||
@State var autoCreate = false
|
@State var autoCreate = false
|
||||||
|
@State private var aas = AutoAcceptState()
|
||||||
|
@State private var savedAAS = AutoAcceptState()
|
||||||
@State private var showMailView = false
|
@State private var showMailView = false
|
||||||
@State private var mailViewResult: Result<MFMailComposeResult, Error>? = nil
|
@State private var mailViewResult: Result<MFMailComposeResult, Error>? = nil
|
||||||
@State private var alert: UserAddressAlert?
|
@State private var alert: UserAddressAlert?
|
||||||
|
@ -55,7 +57,15 @@ struct UserAddressView: View {
|
||||||
if chatModel.userAddress == nil, autoCreate {
|
if chatModel.userAddress == nil, autoCreate {
|
||||||
createAddress()
|
createAddress()
|
||||||
}
|
}
|
||||||
|
if let userAddress = chatModel.userAddress {
|
||||||
|
aas = AutoAcceptState(userAddress: userAddress)
|
||||||
|
savedAAS = aas
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.onChange(of: aas.enable) { aasEnabled in
|
||||||
|
if !aasEnabled { aas = AutoAcceptState() }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func userAddressView() -> some View {
|
private func userAddressView() -> some View {
|
||||||
|
@ -135,10 +145,23 @@ struct UserAddressView: View {
|
||||||
// if MFMailComposeViewController.canSendMail() {
|
// if MFMailComposeViewController.canSendMail() {
|
||||||
// shareViaEmailButton(userAddress)
|
// shareViaEmailButton(userAddress)
|
||||||
// }
|
// }
|
||||||
|
settingsRow("hand.wave", color: theme.colors.secondary) {
|
||||||
|
Toggle("Business address", isOn: $aas.business)
|
||||||
|
.onChange(of: aas.business) { ba in
|
||||||
|
if ba {
|
||||||
|
aas.enable = true
|
||||||
|
aas.incognito = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
addressSettingsButton(userAddress)
|
addressSettingsButton(userAddress)
|
||||||
} header: {
|
} header: {
|
||||||
Text("For social media")
|
Text("For social media")
|
||||||
.foregroundColor(theme.colors.secondary)
|
.foregroundColor(theme.colors.secondary)
|
||||||
|
} footer: {
|
||||||
|
if aas.business {
|
||||||
|
Text("Add your team members to the conversations").foregroundColor(theme.colors.secondary)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
|
@ -276,11 +299,13 @@ struct UserAddressView: View {
|
||||||
private struct AutoAcceptState: Equatable {
|
private struct AutoAcceptState: Equatable {
|
||||||
var enable = false
|
var enable = false
|
||||||
var incognito = false
|
var incognito = false
|
||||||
|
var business = false
|
||||||
var welcomeText = ""
|
var welcomeText = ""
|
||||||
|
|
||||||
init(enable: Bool = false, incognito: Bool = false, welcomeText: String = "") {
|
init(enable: Bool = false, incognito: Bool = false, business: Bool = false, welcomeText: String = "") {
|
||||||
self.enable = enable
|
self.enable = enable
|
||||||
self.incognito = incognito
|
self.incognito = incognito
|
||||||
|
self.business = business
|
||||||
self.welcomeText = welcomeText
|
self.welcomeText = welcomeText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,6 +313,7 @@ private struct AutoAcceptState: Equatable {
|
||||||
if let aa = userAddress.autoAccept {
|
if let aa = userAddress.autoAccept {
|
||||||
enable = true
|
enable = true
|
||||||
incognito = aa.acceptIncognito
|
incognito = aa.acceptIncognito
|
||||||
|
business = aa.businessAddress == true
|
||||||
if let msg = aa.autoReply {
|
if let msg = aa.autoReply {
|
||||||
welcomeText = msg.text
|
welcomeText = msg.text
|
||||||
} else {
|
} else {
|
||||||
|
@ -296,6 +322,7 @@ private struct AutoAcceptState: Equatable {
|
||||||
} else {
|
} else {
|
||||||
enable = false
|
enable = false
|
||||||
incognito = false
|
incognito = false
|
||||||
|
business = false
|
||||||
welcomeText = ""
|
welcomeText = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -305,7 +332,7 @@ private struct AutoAcceptState: Equatable {
|
||||||
var autoReply: MsgContent? = nil
|
var autoReply: MsgContent? = nil
|
||||||
let s = welcomeText.trimmingCharacters(in: .whitespacesAndNewlines)
|
let s = welcomeText.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
if s != "" { autoReply = .text(s) }
|
if s != "" { autoReply = .text(s) }
|
||||||
return AutoAccept(acceptIncognito: incognito, autoReply: autoReply)
|
return AutoAccept(businessAddress: business, acceptIncognito: incognito, autoReply: autoReply)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -373,7 +400,7 @@ struct UserAddressSettingsView: View {
|
||||||
List {
|
List {
|
||||||
Section {
|
Section {
|
||||||
shareWithContactsButton()
|
shareWithContactsButton()
|
||||||
autoAcceptToggle()
|
autoAcceptToggle().disabled(aas.business)
|
||||||
}
|
}
|
||||||
|
|
||||||
if aas.enable {
|
if aas.enable {
|
||||||
|
@ -450,7 +477,9 @@ struct UserAddressSettingsView: View {
|
||||||
|
|
||||||
private func autoAcceptSection() -> some View {
|
private func autoAcceptSection() -> some View {
|
||||||
Section {
|
Section {
|
||||||
acceptIncognitoToggle()
|
if !aas.business {
|
||||||
|
acceptIncognitoToggle()
|
||||||
|
}
|
||||||
welcomeMessageEditor()
|
welcomeMessageEditor()
|
||||||
saveAASButton()
|
saveAASButton()
|
||||||
.disabled(aas == savedAAS)
|
.disabled(aas == savedAAS)
|
||||||
|
|
|
@ -229,6 +229,7 @@
|
||||||
D741547A29AF90B00022400A /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D741547929AF90B00022400A /* PushKit.framework */; };
|
D741547A29AF90B00022400A /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D741547929AF90B00022400A /* PushKit.framework */; };
|
||||||
D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; };
|
D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; };
|
||||||
D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; };
|
D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; };
|
||||||
|
E504516F2CFA3BFB00DE3F74 /* ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = E504516E2CFA3BFB00DE3F74 /* ContextMenu.swift */; };
|
||||||
E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; };
|
E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; };
|
||||||
E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; };
|
E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; };
|
||||||
E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; };
|
E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; };
|
||||||
|
@ -575,6 +576,7 @@
|
||||||
D741547729AF89AF0022400A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; };
|
D741547729AF89AF0022400A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; };
|
D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
|
D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
|
||||||
|
E504516E2CFA3BFB00DE3F74 /* ContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenu.swift; sourceTree = "<group>"; };
|
||||||
E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = "<group>"; };
|
E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = "<group>"; };
|
||||||
E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = "<group>"; };
|
E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
|
@ -791,6 +793,7 @@
|
||||||
5C971E1F27AEBF7000C8A3CE /* Helpers */ = {
|
5C971E1F27AEBF7000C8A3CE /* Helpers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E504516E2CFA3BFB00DE3F74 /* ContextMenu.swift */,
|
||||||
5C971E2027AEBF8300C8A3CE /* ChatInfoImage.swift */,
|
5C971E2027AEBF8300C8A3CE /* ChatInfoImage.swift */,
|
||||||
5C7505A427B679EE00BE3227 /* NavLinkPlain.swift */,
|
5C7505A427B679EE00BE3227 /* NavLinkPlain.swift */,
|
||||||
5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */,
|
5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */,
|
||||||
|
@ -1445,6 +1448,7 @@
|
||||||
CE984D4B2C36C5D500E3AEFF /* ChatItemClipShape.swift in Sources */,
|
CE984D4B2C36C5D500E3AEFF /* ChatItemClipShape.swift in Sources */,
|
||||||
64D0C2C629FAC1EC00B38D5F /* AddContactLearnMore.swift in Sources */,
|
64D0C2C629FAC1EC00B38D5F /* AddContactLearnMore.swift in Sources */,
|
||||||
5C3A88D127DF57800060F1C2 /* FramedItemView.swift in Sources */,
|
5C3A88D127DF57800060F1C2 /* FramedItemView.swift in Sources */,
|
||||||
|
E504516F2CFA3BFB00DE3F74 /* ContextMenu.swift in Sources */,
|
||||||
5C65F343297D45E100B67AF3 /* VersionView.swift in Sources */,
|
5C65F343297D45E100B67AF3 /* VersionView.swift in Sources */,
|
||||||
64F1CC3B28B39D8600CD1FB1 /* IncognitoHelp.swift in Sources */,
|
64F1CC3B28B39D8600CD1FB1 /* IncognitoHelp.swift in Sources */,
|
||||||
5CB0BA90282713D900B3292C /* SimpleXInfo.swift in Sources */,
|
5CB0BA90282713D900B3292C /* SimpleXInfo.swift in Sources */,
|
||||||
|
|
|
@ -2103,10 +2103,12 @@ public struct UserContactLink: Decodable, Hashable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct AutoAccept: Codable, Hashable {
|
public struct AutoAccept: Codable, Hashable {
|
||||||
|
public var businessAddress: Bool? // make not nullable
|
||||||
public var acceptIncognito: Bool
|
public var acceptIncognito: Bool
|
||||||
public var autoReply: MsgContent?
|
public var autoReply: MsgContent?
|
||||||
|
|
||||||
public init(acceptIncognito: Bool, autoReply: MsgContent? = nil) {
|
public init(businessAddress: Bool, acceptIncognito: Bool, autoReply: MsgContent? = nil) {
|
||||||
|
self.businessAddress = businessAddress
|
||||||
self.acceptIncognito = acceptIncognito
|
self.acceptIncognito = acceptIncognito
|
||||||
self.autoReply = autoReply
|
self.autoReply = autoReply
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
|
||||||
source-repository-package
|
source-repository-package
|
||||||
type: git
|
type: git
|
||||||
location: https://github.com/simplex-chat/simplexmq.git
|
location: https://github.com/simplex-chat/simplexmq.git
|
||||||
tag: 601620bdde612ebdd33da2637d99b15ff32170c9
|
tag: 38ad3c046e1bd5eb1ffe696dd24b10dd69001ba2
|
||||||
|
|
||||||
source-repository-package
|
source-repository-package
|
||||||
type: git
|
type: git
|
||||||
|
|
29
docs/rfcs/2024-11-28-business-address.md
Normal file
29
docs/rfcs/2024-11-28-business-address.md
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# Business address
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
When business uses a communication system for support and other business scenarios, it's important for the customer:
|
||||||
|
- to be able to talk to multiple people in the business, and know who they are.
|
||||||
|
- potentially, add friends or relatives to the conversation if this is about a group purchase.
|
||||||
|
|
||||||
|
It's important for the business:
|
||||||
|
- to have bot accept incoming requests.
|
||||||
|
- to be able to add other people to the coversation, as transfer and as escalation.
|
||||||
|
|
||||||
|
This is how all messaging support system works, and how WeChat business accounts work, but no messenger provides it.
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Make current contact addresses to support business mode. We already have all the elements for that.
|
||||||
|
|
||||||
|
- connection requests will be accepted automatically (non-optionally), and auto-reply will be sent (if provided).
|
||||||
|
- the request sender will be made member, can be made admin later manually.
|
||||||
|
- the new group with the customer will be created on each request instead of direct conversation.
|
||||||
|
|
||||||
|
Group will function differently from a normal group:
|
||||||
|
- Show business name and avatar to customer, customer name and avatar to business.
|
||||||
|
- Use different icon for customer and for the business if the avatar is not provided.
|
||||||
|
- Possibly, a sub-icon on business avatar for customers.
|
||||||
|
- Members added by business are marked as business, by customer as customer (not MVP).
|
||||||
|
|
||||||
|
This functionality allows to develop support bots that automatically reply, potentially answer some questions, and add support agents as required, who can escalate further.
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"https://github.com/simplex-chat/simplexmq.git"."601620bdde612ebdd33da2637d99b15ff32170c9" = "0lgiphb9sf5i29d378pah24mhf7m8df75jk6asvw8ns527g4amj1";
|
"https://github.com/simplex-chat/simplexmq.git"."38ad3c046e1bd5eb1ffe696dd24b10dd69001ba2" = "0nq2a2lklbxpc049zjxa5w8c63l9l9nf08jb7pny42nmah0mlc20";
|
||||||
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
|
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
|
||||||
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
|
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
|
||||||
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
|
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
|
||||||
|
|
|
@ -153,6 +153,7 @@ library
|
||||||
Simplex.Chat.Migrations.M20241023_chat_item_autoincrement_id
|
Simplex.Chat.Migrations.M20241023_chat_item_autoincrement_id
|
||||||
Simplex.Chat.Migrations.M20241027_server_operators
|
Simplex.Chat.Migrations.M20241027_server_operators
|
||||||
Simplex.Chat.Migrations.M20241125_indexes
|
Simplex.Chat.Migrations.M20241125_indexes
|
||||||
|
Simplex.Chat.Migrations.M20241128_business_chats
|
||||||
Simplex.Chat.Mobile
|
Simplex.Chat.Mobile
|
||||||
Simplex.Chat.Mobile.File
|
Simplex.Chat.Mobile.File
|
||||||
Simplex.Chat.Mobile.Shared
|
Simplex.Chat.Mobile.Shared
|
||||||
|
|
|
@ -2065,6 +2065,8 @@ processChatCommand' vr = \case
|
||||||
SetProfileAddress onOff -> withUser $ \User {userId} ->
|
SetProfileAddress onOff -> withUser $ \User {userId} ->
|
||||||
processChatCommand $ APISetProfileAddress userId onOff
|
processChatCommand $ APISetProfileAddress userId onOff
|
||||||
APIAddressAutoAccept userId autoAccept_ -> withUserId userId $ \user -> do
|
APIAddressAutoAccept userId autoAccept_ -> withUserId userId $ \user -> do
|
||||||
|
forM_ autoAccept_ $ \AutoAccept {businessAddress, acceptIncognito} ->
|
||||||
|
when (businessAddress && acceptIncognito) $ throwChatError $ CECommandError "requests to business address cannot be accepted incognito"
|
||||||
contactLink <- withFastStore (\db -> updateUserAddressAutoAccept db user autoAccept_)
|
contactLink <- withFastStore (\db -> updateUserAddressAutoAccept db user autoAccept_)
|
||||||
pure $ CRUserContactLinkUpdated user contactLink
|
pure $ CRUserContactLinkUpdated user contactLink
|
||||||
AddressAutoAccept autoAccept_ -> withUser $ \User {userId} ->
|
AddressAutoAccept autoAccept_ -> withUser $ \User {userId} ->
|
||||||
|
@ -3007,7 +3009,7 @@ processChatCommand' vr = \case
|
||||||
groupMemberId <- getGroupMemberIdByName db user groupId groupMemberName
|
groupMemberId <- getGroupMemberIdByName db user groupId groupMemberName
|
||||||
pure (groupId, groupMemberId)
|
pure (groupId, groupMemberId)
|
||||||
sendGrpInvitation :: User -> Contact -> GroupInfo -> GroupMember -> ConnReqInvitation -> CM ()
|
sendGrpInvitation :: User -> Contact -> GroupInfo -> GroupMember -> ConnReqInvitation -> CM ()
|
||||||
sendGrpInvitation user ct@Contact {contactId, localDisplayName} gInfo@GroupInfo {groupId, groupProfile, membership} GroupMember {groupMemberId, memberId, memberRole = memRole} cReq = do
|
sendGrpInvitation user ct@Contact {contactId, localDisplayName} gInfo@GroupInfo {groupId, groupProfile, membership, businessChat} GroupMember {groupMemberId, memberId, memberRole = memRole} cReq = do
|
||||||
currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo
|
currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo
|
||||||
let GroupMember {memberRole = userRole, memberId = userMemberId} = membership
|
let GroupMember {memberRole = userRole, memberId = userMemberId} = membership
|
||||||
groupInv =
|
groupInv =
|
||||||
|
@ -3016,6 +3018,7 @@ processChatCommand' vr = \case
|
||||||
invitedMember = MemberIdRole memberId memRole,
|
invitedMember = MemberIdRole memberId memRole,
|
||||||
connRequest = cReq,
|
connRequest = cReq,
|
||||||
groupProfile,
|
groupProfile,
|
||||||
|
businessChat,
|
||||||
groupLinkId = Nothing,
|
groupLinkId = Nothing,
|
||||||
groupSize = Just currentMemCount
|
groupSize = Just currentMemCount
|
||||||
}
|
}
|
||||||
|
@ -3972,12 +3975,14 @@ acceptContactRequestAsync user cReq@UserContactRequest {agentInvitationId = Agen
|
||||||
acceptGroupJoinRequestAsync :: User -> GroupInfo -> UserContactRequest -> GroupMemberRole -> Maybe IncognitoProfile -> CM GroupMember
|
acceptGroupJoinRequestAsync :: User -> GroupInfo -> UserContactRequest -> GroupMemberRole -> Maybe IncognitoProfile -> CM GroupMember
|
||||||
acceptGroupJoinRequestAsync
|
acceptGroupJoinRequestAsync
|
||||||
user
|
user
|
||||||
gInfo@GroupInfo {groupProfile, membership}
|
gInfo@GroupInfo {groupProfile, membership, businessChat}
|
||||||
ucr@UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange}
|
ucr@UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange}
|
||||||
gLinkMemRole
|
gLinkMemRole
|
||||||
incognitoProfile = do
|
incognitoProfile = do
|
||||||
gVar <- asks random
|
gVar <- asks random
|
||||||
(groupMemberId, memberId) <- withStore $ \db -> createAcceptedMember db gVar user gInfo ucr gLinkMemRole
|
(groupMemberId, memberId) <- withStore $ \db -> do
|
||||||
|
liftIO $ deleteContactRequestRec db user ucr
|
||||||
|
createAcceptedMember db gVar user gInfo ucr gLinkMemRole
|
||||||
currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo
|
currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo
|
||||||
let Profile {displayName} = profileToSendOnAccept user incognitoProfile True
|
let Profile {displayName} = profileToSendOnAccept user incognitoProfile True
|
||||||
GroupMember {memberRole = userRole, memberId = userMemberId} = membership
|
GroupMember {memberRole = userRole, memberId = userMemberId} = membership
|
||||||
|
@ -3988,6 +3993,7 @@ acceptGroupJoinRequestAsync
|
||||||
fromMemberName = displayName,
|
fromMemberName = displayName,
|
||||||
invitedMember = MemberIdRole memberId gLinkMemRole,
|
invitedMember = MemberIdRole memberId gLinkMemRole,
|
||||||
groupProfile,
|
groupProfile,
|
||||||
|
businessChat,
|
||||||
groupSize = Just currentMemCount
|
groupSize = Just currentMemCount
|
||||||
}
|
}
|
||||||
subMode <- chatReadVar subscriptionMode
|
subMode <- chatReadVar subscriptionMode
|
||||||
|
@ -3998,6 +4004,43 @@ acceptGroupJoinRequestAsync
|
||||||
liftIO $ createAcceptedMemberConnection db user connIds chatV ucr groupMemberId subMode
|
liftIO $ createAcceptedMemberConnection db user connIds chatV ucr groupMemberId subMode
|
||||||
getGroupMemberById db vr user groupMemberId
|
getGroupMemberById db vr user groupMemberId
|
||||||
|
|
||||||
|
acceptBusinessJoinRequestAsync :: User -> UserContactRequest -> CM GroupInfo
|
||||||
|
acceptBusinessJoinRequestAsync
|
||||||
|
user
|
||||||
|
ucr@UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange} = do
|
||||||
|
vr <- chatVersionRange
|
||||||
|
gVar <- asks random
|
||||||
|
let userProfile@Profile {displayName, preferences} = profileToSendOnAccept user Nothing True
|
||||||
|
groupPreferences = maybe defaultBusinessGroupPrefs businessGroupPrefs preferences
|
||||||
|
(gInfo, clientMember) <- withStore $ \db -> do
|
||||||
|
liftIO $ deleteContactRequestRec db user ucr
|
||||||
|
createBusinessRequestGroup db vr gVar user ucr groupPreferences
|
||||||
|
let GroupInfo {membership} = gInfo
|
||||||
|
GroupMember {memberRole = userRole, memberId = userMemberId} = membership
|
||||||
|
GroupMember {groupMemberId, memberId} = clientMember
|
||||||
|
msg =
|
||||||
|
XGrpLinkInv $
|
||||||
|
GroupLinkInvitation
|
||||||
|
{ fromMember = MemberIdRole userMemberId userRole,
|
||||||
|
fromMemberName = displayName,
|
||||||
|
invitedMember = MemberIdRole memberId GRMember,
|
||||||
|
groupProfile = businessGroupProfile userProfile groupPreferences,
|
||||||
|
-- This refers to the "title member" that defines the group name and profile.
|
||||||
|
-- This coincides with fromMember to be current user when accepting the connecting user,
|
||||||
|
-- but it will be different when inviting somebody else.
|
||||||
|
businessChat = Just $ BusinessChatInfo userMemberId BCBusiness,
|
||||||
|
groupSize = Just 1
|
||||||
|
}
|
||||||
|
subMode <- chatReadVar subscriptionMode
|
||||||
|
let chatV = vr `peerConnChatVersion` cReqChatVRange
|
||||||
|
connIds <- agentAcceptContactAsync user True invId msg subMode PQSupportOff chatV
|
||||||
|
withStore' $ \db -> createAcceptedMemberConnection db user connIds chatV ucr groupMemberId subMode
|
||||||
|
pure gInfo
|
||||||
|
where
|
||||||
|
businessGroupProfile :: Profile -> GroupPreferences -> GroupProfile
|
||||||
|
businessGroupProfile Profile {displayName, fullName, image} groupPreferences =
|
||||||
|
GroupProfile {displayName, fullName, description = Nothing, image, groupPreferences = Just groupPreferences}
|
||||||
|
|
||||||
profileToSendOnAccept :: User -> Maybe IncognitoProfile -> Bool -> Profile
|
profileToSendOnAccept :: User -> Maybe IncognitoProfile -> Bool -> Profile
|
||||||
profileToSendOnAccept user ip = userProfileToSend user (getIncognitoProfile <$> ip) Nothing
|
profileToSendOnAccept user ip = userProfileToSend user (getIncognitoProfile <$> ip) Nothing
|
||||||
where
|
where
|
||||||
|
@ -4683,15 +4726,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||||
CONF confId pqSupport _ connInfo -> do
|
CONF confId pqSupport _ connInfo -> do
|
||||||
conn' <- processCONFpqSupport conn pqSupport
|
conn' <- processCONFpqSupport conn pqSupport
|
||||||
-- [incognito] send saved profile
|
-- [incognito] send saved profile
|
||||||
|
(conn'', inGroup) <- saveConnInfo conn' connInfo
|
||||||
incognitoProfile <- forM customUserProfileId $ \profileId -> withStore (\db -> getProfileById db userId profileId)
|
incognitoProfile <- forM customUserProfileId $ \profileId -> withStore (\db -> getProfileById db userId profileId)
|
||||||
let profileToSend = userProfileToSend user (fromLocalProfile <$> incognitoProfile) Nothing False
|
let profileToSend = userProfileToSend user (fromLocalProfile <$> incognitoProfile) Nothing inGroup
|
||||||
conn'' <- saveConnInfo conn' connInfo
|
|
||||||
-- [async agent commands] no continuation needed, but command should be asynchronous for stability
|
-- [async agent commands] no continuation needed, but command should be asynchronous for stability
|
||||||
allowAgentConnectionAsync user conn'' confId $ XInfo profileToSend
|
allowAgentConnectionAsync user conn'' confId $ XInfo profileToSend
|
||||||
INFO pqSupport connInfo -> do
|
INFO pqSupport connInfo -> do
|
||||||
processINFOpqSupport conn pqSupport
|
processINFOpqSupport conn pqSupport
|
||||||
_conn' <- saveConnInfo conn connInfo
|
void $ saveConnInfo conn connInfo
|
||||||
pure ()
|
|
||||||
MSG meta _msgFlags _msgBody ->
|
MSG meta _msgFlags _msgBody ->
|
||||||
-- We are not saving message (saveDirectRcvMSG) as contact hasn't been created yet,
|
-- We are not saving message (saveDirectRcvMSG) as contact hasn't been created yet,
|
||||||
-- chat item is also not created here
|
-- chat item is also not created here
|
||||||
|
@ -4806,6 +4848,16 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||||
let p = userProfileToSend user (fromLocalProfile <$> incognitoProfile) (Just ct') False
|
let p = userProfileToSend user (fromLocalProfile <$> incognitoProfile) (Just ct') False
|
||||||
allowAgentConnectionAsync user conn'' confId $ XInfo p
|
allowAgentConnectionAsync user conn'' confId $ XInfo p
|
||||||
void $ withStore' $ \db -> resetMemberContactFields db ct'
|
void $ withStore' $ \db -> resetMemberContactFields db ct'
|
||||||
|
XGrpLinkInv glInv -> do
|
||||||
|
-- XGrpLinkInv here means we are connecting via business contact card, so we replace contact with group
|
||||||
|
(gInfo, host) <- withStore $ \db -> do
|
||||||
|
liftIO $ deleteContactCardKeepConn db connId ct
|
||||||
|
createGroupInvitedViaLink db vr user conn'' glInv
|
||||||
|
-- [incognito] send saved profile
|
||||||
|
incognitoProfile <- forM customUserProfileId $ \pId -> withStore (\db -> getProfileById db userId pId)
|
||||||
|
let profileToSend = userProfileToSend user (fromLocalProfile <$> incognitoProfile) Nothing True
|
||||||
|
allowAgentConnectionAsync user conn'' confId $ XInfo profileToSend
|
||||||
|
toView $ CRBusinessLinkConnecting user gInfo host ct
|
||||||
_ -> messageError "CONF for existing contact must have x.grp.mem.info or x.info"
|
_ -> messageError "CONF for existing contact must have x.grp.mem.info or x.info"
|
||||||
INFO pqSupport connInfo -> do
|
INFO pqSupport connInfo -> do
|
||||||
processINFOpqSupport conn pqSupport
|
processINFOpqSupport conn pqSupport
|
||||||
|
@ -4936,7 +4988,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||||
_ -> pure ()
|
_ -> pure ()
|
||||||
|
|
||||||
processGroupMessage :: AEvent e -> ConnectionEntity -> Connection -> GroupInfo -> GroupMember -> CM ()
|
processGroupMessage :: AEvent e -> ConnectionEntity -> Connection -> GroupInfo -> GroupMember -> CM ()
|
||||||
processGroupMessage agentMsg connEntity conn@Connection {connId, connectionCode} gInfo@GroupInfo {groupId, groupProfile, membership, chatSettings} m = case agentMsg of
|
processGroupMessage agentMsg connEntity conn@Connection {connId, connChatVersion, connectionCode} gInfo@GroupInfo {groupId, groupProfile, membership, chatSettings} m = case agentMsg of
|
||||||
INV (ACR _ cReq) ->
|
INV (ACR _ cReq) ->
|
||||||
withCompletedCommand conn agentMsg $ \CommandData {cmdFunction} ->
|
withCompletedCommand conn agentMsg $ \CommandData {cmdFunction} ->
|
||||||
case cReq of
|
case cReq of
|
||||||
|
@ -4977,6 +5029,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||||
invitedMember = MemberIdRole memberId memRole,
|
invitedMember = MemberIdRole memberId memRole,
|
||||||
connRequest = cReq,
|
connRequest = cReq,
|
||||||
groupProfile,
|
groupProfile,
|
||||||
|
businessChat = Nothing,
|
||||||
groupLinkId = groupLinkId,
|
groupLinkId = groupLinkId,
|
||||||
groupSize = Just currentMemCount
|
groupSize = Just currentMemCount
|
||||||
}
|
}
|
||||||
|
@ -5049,6 +5102,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||||
void . sendGroupMessage user gInfo members . XGrpMemNew $ memberInfo m
|
void . sendGroupMessage user gInfo members . XGrpMemNew $ memberInfo m
|
||||||
sendIntroductions members
|
sendIntroductions members
|
||||||
when (groupFeatureAllowed SGFHistory gInfo) sendHistory
|
when (groupFeatureAllowed SGFHistory gInfo) sendHistory
|
||||||
|
when (connChatVersion < batchSend2Version) $ sendGroupAutoReply members
|
||||||
where
|
where
|
||||||
sendXGrpLinkMem = do
|
sendXGrpLinkMem = do
|
||||||
let profileMode = ExistingIncognito <$> incognitoMembershipProfile gInfo
|
let profileMode = ExistingIncognito <$> incognitoMembershipProfile gInfo
|
||||||
|
@ -5311,9 +5365,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||||
OK ->
|
OK ->
|
||||||
-- [async agent commands] continuation on receiving OK
|
-- [async agent commands] continuation on receiving OK
|
||||||
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure ()
|
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure ()
|
||||||
JOINED _ ->
|
JOINED sqSecured ->
|
||||||
-- [async agent commands] continuation on receiving JOINED
|
-- [async agent commands] continuation on receiving JOINED
|
||||||
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure ()
|
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData ->
|
||||||
|
when sqSecured $ do
|
||||||
|
members <- withStore' $ \db -> getGroupMembers db vr user gInfo
|
||||||
|
when (connChatVersion >= batchSend2Version) $ sendGroupAutoReply members
|
||||||
QCONT -> do
|
QCONT -> do
|
||||||
continued <- continueSending connEntity conn
|
continued <- continueSending connEntity conn
|
||||||
when continued $ sendPendingGroupMessages user m conn
|
when continued $ sendPendingGroupMessages user m conn
|
||||||
|
@ -5341,6 +5398,23 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||||
updateGroupItemsErrorStatus db msgId groupMemberId newStatus = do
|
updateGroupItemsErrorStatus db msgId groupMemberId newStatus = do
|
||||||
itemIds <- getChatItemIdsByAgentMsgId db connId msgId
|
itemIds <- getChatItemIdsByAgentMsgId db connId msgId
|
||||||
forM_ itemIds $ \itemId -> updateGroupMemSndStatus' db itemId groupMemberId newStatus
|
forM_ itemIds $ \itemId -> updateGroupMemSndStatus' db itemId groupMemberId newStatus
|
||||||
|
sendGroupAutoReply members = autoReplyMC >>= mapM_ send
|
||||||
|
where
|
||||||
|
autoReplyMC = do
|
||||||
|
let GroupInfo {businessChat} = gInfo
|
||||||
|
GroupMember {memberId = joiningMemberId} = m
|
||||||
|
case businessChat of
|
||||||
|
Just BusinessChatInfo {memberId, chatType = BCCustomer}
|
||||||
|
| joiningMemberId == memberId -> useReply <$> withStore (`getUserAddress` user)
|
||||||
|
where
|
||||||
|
useReply UserContactLink {autoAccept} = case autoAccept of
|
||||||
|
Just AutoAccept {businessAddress, autoReply} | businessAddress -> autoReply
|
||||||
|
_ -> Nothing
|
||||||
|
_ -> pure Nothing
|
||||||
|
send mc = do
|
||||||
|
msg <- sendGroupMessage' user gInfo members (XMsgNew $ MCSimple (extMsgContent mc Nothing))
|
||||||
|
ci <- saveSndChatItem user (CDGroupSnd gInfo) msg (CISndMsgContent mc)
|
||||||
|
toView $ CRNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci]
|
||||||
|
|
||||||
agentMsgDecryptError :: AgentCryptoError -> (MsgDecryptError, Word32)
|
agentMsgDecryptError :: AgentCryptoError -> (MsgDecryptError, Word32)
|
||||||
agentMsgDecryptError = \case
|
agentMsgDecryptError = \case
|
||||||
|
@ -5525,26 +5599,37 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||||
CORContact contact -> toView $ CRContactRequestAlreadyAccepted user contact
|
CORContact contact -> toView $ CRContactRequestAlreadyAccepted user contact
|
||||||
CORRequest cReq -> do
|
CORRequest cReq -> do
|
||||||
ucl <- withStore $ \db -> getUserContactLinkById db userId userContactLinkId
|
ucl <- withStore $ \db -> getUserContactLinkById db userId userContactLinkId
|
||||||
let (UserContactLink {autoAccept}, groupId_, gLinkMemRole) = ucl
|
let (UserContactLink {connReqContact, autoAccept}, groupId_, gLinkMemRole) = ucl
|
||||||
|
isSimplexTeam = sameConnReqContact connReqContact adminContactReq
|
||||||
|
v = maxVersion chatVRange
|
||||||
case autoAccept of
|
case autoAccept of
|
||||||
Just AutoAccept {acceptIncognito} -> case groupId_ of
|
Just AutoAccept {acceptIncognito, businessAddress}
|
||||||
Nothing -> do
|
| businessAddress ->
|
||||||
-- [incognito] generate profile to send, create connection with incognito profile
|
if v < groupFastLinkJoinVersion || (isSimplexTeam && v < businessChatsVersion)
|
||||||
incognitoProfile <- if acceptIncognito then Just . NewIncognito <$> liftIO generateRandomProfile else pure Nothing
|
then do
|
||||||
ct <- acceptContactRequestAsync user cReq incognitoProfile True reqPQSup
|
ct <- acceptContactRequestAsync user cReq Nothing True reqPQSup
|
||||||
toView $ CRAcceptingContactRequest user ct
|
toView $ CRAcceptingContactRequest user ct
|
||||||
Just groupId -> do
|
else do
|
||||||
gInfo <- withStore $ \db -> getGroupInfo db vr user groupId
|
gInfo <- acceptBusinessJoinRequestAsync user cReq
|
||||||
let profileMode = ExistingIncognito <$> incognitoMembershipProfile gInfo
|
toView $ CRAcceptingBusinessRequest user gInfo
|
||||||
if maxVersion chatVRange >= groupFastLinkJoinVersion
|
| otherwise -> case groupId_ of
|
||||||
then do
|
Nothing -> do
|
||||||
mem <- acceptGroupJoinRequestAsync user gInfo cReq gLinkMemRole profileMode
|
-- [incognito] generate profile to send, create connection with incognito profile
|
||||||
createInternalChatItem user (CDGroupRcv gInfo mem) (CIRcvGroupEvent RGEInvitedViaGroupLink) Nothing
|
incognitoProfile <- if acceptIncognito then Just . NewIncognito <$> liftIO generateRandomProfile else pure Nothing
|
||||||
toView $ CRAcceptingGroupJoinRequestMember user gInfo mem
|
ct <- acceptContactRequestAsync user cReq incognitoProfile True reqPQSup
|
||||||
else do
|
toView $ CRAcceptingContactRequest user ct
|
||||||
-- TODO v5.7 remove old API (or v6.0?)
|
Just groupId -> do
|
||||||
ct <- acceptContactRequestAsync user cReq profileMode False PQSupportOff
|
gInfo <- withStore $ \db -> getGroupInfo db vr user groupId
|
||||||
toView $ CRAcceptingGroupJoinRequest user gInfo ct
|
let profileMode = ExistingIncognito <$> incognitoMembershipProfile gInfo
|
||||||
|
if v >= groupFastLinkJoinVersion
|
||||||
|
then do
|
||||||
|
mem <- acceptGroupJoinRequestAsync user gInfo cReq gLinkMemRole profileMode
|
||||||
|
createInternalChatItem user (CDGroupRcv gInfo mem) (CIRcvGroupEvent RGEInvitedViaGroupLink) Nothing
|
||||||
|
toView $ CRAcceptingGroupJoinRequestMember user gInfo mem
|
||||||
|
else do
|
||||||
|
-- TODO v5.7 remove old API (or v6.0?)
|
||||||
|
ct <- acceptContactRequestAsync user cReq profileMode False PQSupportOff
|
||||||
|
toView $ CRAcceptingGroupJoinRequest user gInfo ct
|
||||||
_ -> toView $ CRReceivedContactRequest user cReq
|
_ -> toView $ CRReceivedContactRequest user cReq
|
||||||
|
|
||||||
memberCanSend :: GroupMember -> CM () -> CM ()
|
memberCanSend :: GroupMember -> CM () -> CM ()
|
||||||
|
@ -6353,9 +6438,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||||
xInfoMember gInfo m p' brokerTs = void $ processMemberProfileUpdate gInfo m p' True (Just brokerTs)
|
xInfoMember gInfo m p' brokerTs = void $ processMemberProfileUpdate gInfo m p' True (Just brokerTs)
|
||||||
|
|
||||||
xGrpLinkMem :: GroupInfo -> GroupMember -> Connection -> Profile -> CM ()
|
xGrpLinkMem :: GroupInfo -> GroupMember -> Connection -> Profile -> CM ()
|
||||||
xGrpLinkMem gInfo@GroupInfo {membership} m@GroupMember {groupMemberId, memberCategory} Connection {viaGroupLink} p' = do
|
xGrpLinkMem gInfo@GroupInfo {membership, businessChat} m@GroupMember {groupMemberId, memberCategory} Connection {viaGroupLink} p' = do
|
||||||
xGrpLinkMemReceived <- withStore $ \db -> getXGrpLinkMemReceived db groupMemberId
|
xGrpLinkMemReceived <- withStore $ \db -> getXGrpLinkMemReceived db groupMemberId
|
||||||
if viaGroupLink && isNothing (memberContactId m) && memberCategory == GCHostMember && not xGrpLinkMemReceived
|
if (viaGroupLink || isJust businessChat) && isNothing (memberContactId m) && memberCategory == GCHostMember && not xGrpLinkMemReceived
|
||||||
then do
|
then do
|
||||||
m' <- processMemberProfileUpdate gInfo m p' False Nothing
|
m' <- processMemberProfileUpdate gInfo m p' False Nothing
|
||||||
withStore' $ \db -> setXGrpLinkMemReceived db groupMemberId True
|
withStore' $ \db -> setXGrpLinkMemReceived db groupMemberId True
|
||||||
|
@ -6652,7 +6737,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||||
toView $ CRContactAndMemberAssociated user c2 g m1 c2'
|
toView $ CRContactAndMemberAssociated user c2 g m1 c2'
|
||||||
pure c2'
|
pure c2'
|
||||||
|
|
||||||
saveConnInfo :: Connection -> ConnInfo -> CM Connection
|
saveConnInfo :: Connection -> ConnInfo -> CM (Connection, Bool)
|
||||||
saveConnInfo activeConn connInfo = do
|
saveConnInfo activeConn connInfo = do
|
||||||
ChatMessage {chatVRange, chatMsgEvent} <- parseChatMessage activeConn connInfo
|
ChatMessage {chatVRange, chatMsgEvent} <- parseChatMessage activeConn connInfo
|
||||||
conn' <- updatePeerChatVRange activeConn chatVRange
|
conn' <- updatePeerChatVRange activeConn chatVRange
|
||||||
|
@ -6661,13 +6746,13 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||||
let contactUsed = connDirect activeConn
|
let contactUsed = connDirect activeConn
|
||||||
ct <- withStore $ \db -> createDirectContact db user conn' p contactUsed
|
ct <- withStore $ \db -> createDirectContact db user conn' p contactUsed
|
||||||
toView $ CRContactConnecting user ct
|
toView $ CRContactConnecting user ct
|
||||||
pure conn'
|
pure (conn', False)
|
||||||
XGrpLinkInv glInv -> do
|
XGrpLinkInv glInv -> do
|
||||||
(gInfo, host) <- withStore $ \db -> createGroupInvitedViaLink db vr user conn' glInv
|
(gInfo, host) <- withStore $ \db -> createGroupInvitedViaLink db vr user conn' glInv
|
||||||
toView $ CRGroupLinkConnecting user gInfo host
|
toView $ CRGroupLinkConnecting user gInfo host
|
||||||
pure conn'
|
pure (conn', True)
|
||||||
-- TODO show/log error, other events in SMP confirmation
|
-- TODO show/log error, other events in SMP confirmation
|
||||||
_ -> pure conn'
|
_ -> pure (conn', False)
|
||||||
|
|
||||||
xGrpMemNew :: GroupInfo -> GroupMember -> MemberInfo -> RcvMessage -> UTCTime -> CM ()
|
xGrpMemNew :: GroupInfo -> GroupMember -> MemberInfo -> RcvMessage -> UTCTime -> CM ()
|
||||||
xGrpMemNew gInfo m memInfo@(MemberInfo memId memRole _ _) msg brokerTs = do
|
xGrpMemNew gInfo m memInfo@(MemberInfo memId memRole _ _) msg brokerTs = do
|
||||||
|
@ -8683,11 +8768,11 @@ chatCommandP =
|
||||||
dbKeyP = nonEmptyKey <$?> strP
|
dbKeyP = nonEmptyKey <$?> strP
|
||||||
nonEmptyKey k@(DBEncryptionKey s) = if BA.null s then Left "empty key" else Right k
|
nonEmptyKey k@(DBEncryptionKey s) = if BA.null s then Left "empty key" else Right k
|
||||||
dbEncryptionConfig currentKey newKey = DBEncryptionConfig {currentKey, newKey, keepKey = Just False}
|
dbEncryptionConfig currentKey newKey = DBEncryptionConfig {currentKey, newKey, keepKey = Just False}
|
||||||
autoAcceptP =
|
autoAcceptP = ifM onOffP (Just <$> (businessAA <|> addressAA)) (pure Nothing)
|
||||||
ifM
|
where
|
||||||
onOffP
|
addressAA = AutoAccept False <$> (" incognito=" *> onOffP <|> pure False) <*> autoReply
|
||||||
(Just <$> (AutoAccept <$> (" incognito=" *> onOffP <|> pure False) <*> optional (A.space *> msgContentP)))
|
businessAA = AutoAccept True <$> (" business" *> pure False) <*> autoReply
|
||||||
(pure Nothing)
|
autoReply = optional (A.space *> msgContentP)
|
||||||
rcCtrlAddressP = RCCtrlAddress <$> ("addr=" *> strP) <*> (" iface=" *> (jsonP <|> text1P))
|
rcCtrlAddressP = RCCtrlAddress <$> ("addr=" *> strP) <*> (" iface=" *> (jsonP <|> text1P))
|
||||||
text1P = safeDecodeUtf8 <$> A.takeTill (== ' ')
|
text1P = safeDecodeUtf8 <$> A.takeTill (== ' ')
|
||||||
char_ = optional . A.char
|
char_ = optional . A.char
|
||||||
|
|
|
@ -56,7 +56,7 @@ initializeBotAddress' logAddress cc = do
|
||||||
where
|
where
|
||||||
showBotAddress uri = do
|
showBotAddress uri = do
|
||||||
when logAddress $ putStrLn $ "Bot's contact address is: " <> B.unpack (strEncode uri)
|
when logAddress $ putStrLn $ "Bot's contact address is: " <> B.unpack (strEncode uri)
|
||||||
void $ sendChatCmd cc $ AddressAutoAccept $ Just AutoAccept {acceptIncognito = False, autoReply = Nothing}
|
void $ sendChatCmd cc $ AddressAutoAccept $ Just AutoAccept {businessAddress = False, acceptIncognito = False, autoReply = Nothing}
|
||||||
|
|
||||||
sendMessage :: ChatController -> Contact -> Text -> IO ()
|
sendMessage :: ChatController -> Contact -> Text -> IO ()
|
||||||
sendMessage cc ct = sendComposedMessage cc ct Nothing . MCText
|
sendMessage cc ct = sendComposedMessage cc ct Nothing . MCText
|
||||||
|
|
|
@ -639,6 +639,7 @@ data ChatResponse
|
||||||
| CRContactRequestRejected {user :: User, contactRequest :: UserContactRequest}
|
| CRContactRequestRejected {user :: User, contactRequest :: UserContactRequest}
|
||||||
| CRUserAcceptedGroupSent {user :: User, groupInfo :: GroupInfo, hostContact :: Maybe Contact}
|
| CRUserAcceptedGroupSent {user :: User, groupInfo :: GroupInfo, hostContact :: Maybe Contact}
|
||||||
| CRGroupLinkConnecting {user :: User, groupInfo :: GroupInfo, hostMember :: GroupMember}
|
| CRGroupLinkConnecting {user :: User, groupInfo :: GroupInfo, hostMember :: GroupMember}
|
||||||
|
| CRBusinessLinkConnecting {user :: User, groupInfo :: GroupInfo, hostMember :: GroupMember, fromContact :: Contact}
|
||||||
| CRUserDeletedMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember}
|
| CRUserDeletedMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember}
|
||||||
| CRGroupsList {user :: User, groups :: [(GroupInfo, GroupSummary)]}
|
| CRGroupsList {user :: User, groups :: [(GroupInfo, GroupSummary)]}
|
||||||
| CRSentGroupInvitation {user :: User, groupInfo :: GroupInfo, contact :: Contact, member :: GroupMember}
|
| CRSentGroupInvitation {user :: User, groupInfo :: GroupInfo, contact :: Contact, member :: GroupMember}
|
||||||
|
@ -665,6 +666,7 @@ data ChatResponse
|
||||||
| CRUserContactLinkDeleted {user :: User}
|
| CRUserContactLinkDeleted {user :: User}
|
||||||
| CRReceivedContactRequest {user :: User, contactRequest :: UserContactRequest}
|
| CRReceivedContactRequest {user :: User, contactRequest :: UserContactRequest}
|
||||||
| CRAcceptingContactRequest {user :: User, contact :: Contact}
|
| CRAcceptingContactRequest {user :: User, contact :: Contact}
|
||||||
|
| CRAcceptingBusinessRequest {user :: User, groupInfo :: GroupInfo}
|
||||||
| CRContactAlreadyExists {user :: User, contact :: Contact}
|
| CRContactAlreadyExists {user :: User, contact :: Contact}
|
||||||
| CRContactRequestAlreadyAccepted {user :: User, contact :: Contact}
|
| CRContactRequestAlreadyAccepted {user :: User, contact :: Contact}
|
||||||
| CRLeftMemberUser {user :: User, groupInfo :: GroupInfo}
|
| CRLeftMemberUser {user :: User, groupInfo :: GroupInfo}
|
||||||
|
|
22
src/Simplex/Chat/Migrations/M20241128_business_chats.hs
Normal file
22
src/Simplex/Chat/Migrations/M20241128_business_chats.hs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
|
|
||||||
|
module Simplex.Chat.Migrations.M20241128_business_chats where
|
||||||
|
|
||||||
|
import Database.SQLite.Simple (Query)
|
||||||
|
import Database.SQLite.Simple.QQ (sql)
|
||||||
|
|
||||||
|
m20241128_business_chats :: Query
|
||||||
|
m20241128_business_chats =
|
||||||
|
[sql|
|
||||||
|
ALTER TABLE user_contact_links ADD business_address INTEGER DEFAULT 0;
|
||||||
|
ALTER TABLE groups ADD COLUMN business_member_id BLOB NULL;
|
||||||
|
ALTER TABLE groups ADD COLUMN business_chat TEXT NULL;
|
||||||
|
|]
|
||||||
|
|
||||||
|
down_m20241128_business_chats :: Query
|
||||||
|
down_m20241128_business_chats =
|
||||||
|
[sql|
|
||||||
|
ALTER TABLE user_contact_links DROP COLUMN business_address;
|
||||||
|
ALTER TABLE groups DROP COLUMN business_member_id;
|
||||||
|
ALTER TABLE groups DROP COLUMN business_chat;
|
||||||
|
|]
|
|
@ -127,7 +127,9 @@ CREATE TABLE groups(
|
||||||
via_group_link_uri_hash BLOB,
|
via_group_link_uri_hash BLOB,
|
||||||
user_member_profile_sent_at TEXT,
|
user_member_profile_sent_at TEXT,
|
||||||
custom_data BLOB,
|
custom_data BLOB,
|
||||||
ui_themes TEXT, -- received
|
ui_themes TEXT,
|
||||||
|
business_member_id BLOB NULL,
|
||||||
|
business_chat TEXT NULL, -- received
|
||||||
FOREIGN KEY(user_id, local_display_name)
|
FOREIGN KEY(user_id, local_display_name)
|
||||||
REFERENCES display_names(user_id, local_display_name)
|
REFERENCES display_names(user_id, local_display_name)
|
||||||
ON DELETE CASCADE
|
ON DELETE CASCADE
|
||||||
|
@ -309,6 +311,7 @@ CREATE TABLE user_contact_links(
|
||||||
auto_accept_incognito INTEGER DEFAULT 0 CHECK(auto_accept_incognito NOT NULL),
|
auto_accept_incognito INTEGER DEFAULT 0 CHECK(auto_accept_incognito NOT NULL),
|
||||||
group_link_id BLOB,
|
group_link_id BLOB,
|
||||||
group_link_member_role TEXT NULL,
|
group_link_member_role TEXT NULL,
|
||||||
|
business_address INTEGER DEFAULT 0,
|
||||||
UNIQUE(user_id, local_display_name)
|
UNIQUE(user_id, local_display_name)
|
||||||
);
|
);
|
||||||
CREATE TABLE contact_requests(
|
CREATE TABLE contact_requests(
|
||||||
|
|
|
@ -66,12 +66,13 @@ import Simplex.Messaging.Version hiding (version)
|
||||||
-- 7 - update member profiles (1/15/2024)
|
-- 7 - update member profiles (1/15/2024)
|
||||||
-- 8 - compress messages and PQ e2e encryption (2024-03-08)
|
-- 8 - compress messages and PQ e2e encryption (2024-03-08)
|
||||||
-- 9 - batch sending in direct connections (2024-07-24)
|
-- 9 - batch sending in direct connections (2024-07-24)
|
||||||
|
-- 10 - business chats (2024-11-29)
|
||||||
|
|
||||||
-- This should not be used directly in code, instead use `maxVersion chatVRange` from ChatConfig.
|
-- This should not be used directly in code, instead use `maxVersion chatVRange` from ChatConfig.
|
||||||
-- This indirection is needed for backward/forward compatibility testing.
|
-- This indirection is needed for backward/forward compatibility testing.
|
||||||
-- Testing with real app versions is still needed, as tests use the current code with different version ranges, not the old code.
|
-- Testing with real app versions is still needed, as tests use the current code with different version ranges, not the old code.
|
||||||
currentChatVersion :: VersionChat
|
currentChatVersion :: VersionChat
|
||||||
currentChatVersion = VersionChat 9
|
currentChatVersion = VersionChat 10
|
||||||
|
|
||||||
-- This should not be used directly in code, instead use `chatVRange` from ChatConfig (see comment above)
|
-- This should not be used directly in code, instead use `chatVRange` from ChatConfig (see comment above)
|
||||||
supportedChatVRange :: VersionRangeChat
|
supportedChatVRange :: VersionRangeChat
|
||||||
|
@ -110,6 +111,10 @@ pqEncryptionCompressionVersion = VersionChat 8
|
||||||
batchSend2Version :: VersionChat
|
batchSend2Version :: VersionChat
|
||||||
batchSend2Version = VersionChat 9
|
batchSend2Version = VersionChat 9
|
||||||
|
|
||||||
|
-- supports differentiating business chats when joining contact addresses
|
||||||
|
businessChatsVersion :: VersionChat
|
||||||
|
businessChatsVersion = VersionChat 10
|
||||||
|
|
||||||
agentToChatVersion :: VersionSMPA -> VersionChat
|
agentToChatVersion :: VersionSMPA -> VersionChat
|
||||||
agentToChatVersion v
|
agentToChatVersion v
|
||||||
| v < pqdrSMPAgentVersion = initialChatVersion
|
| v < pqdrSMPAgentVersion = initialChatVersion
|
||||||
|
|
|
@ -123,7 +123,7 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do
|
||||||
-- GroupInfo
|
-- GroupInfo
|
||||||
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
|
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
|
||||||
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
|
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
|
||||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.ui_themes, g.custom_data,
|
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data,
|
||||||
-- GroupInfo {membership}
|
-- GroupInfo {membership}
|
||||||
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
|
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
|
||||||
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
|
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
|
||||||
|
|
|
@ -30,6 +30,7 @@ module Simplex.Chat.Store.Groups
|
||||||
getGroupAndMember,
|
getGroupAndMember,
|
||||||
createNewGroup,
|
createNewGroup,
|
||||||
createGroupInvitation,
|
createGroupInvitation,
|
||||||
|
deleteContactCardKeepConn,
|
||||||
createGroupInvitedViaLink,
|
createGroupInvitedViaLink,
|
||||||
setViaGroupLinkHash,
|
setViaGroupLinkHash,
|
||||||
setGroupInvitationChatItemId,
|
setGroupInvitationChatItemId,
|
||||||
|
@ -62,6 +63,7 @@ module Simplex.Chat.Store.Groups
|
||||||
createNewContactMemberAsync,
|
createNewContactMemberAsync,
|
||||||
createAcceptedMember,
|
createAcceptedMember,
|
||||||
createAcceptedMemberConnection,
|
createAcceptedMemberConnection,
|
||||||
|
createBusinessRequestGroup,
|
||||||
getContactViaMember,
|
getContactViaMember,
|
||||||
setNewContactMemberConnRequest,
|
setNewContactMemberConnRequest,
|
||||||
getMemberInvitation,
|
getMemberInvitation,
|
||||||
|
@ -153,19 +155,20 @@ import Simplex.Messaging.Util (eitherToMaybe, ($>>=), (<$$>))
|
||||||
import Simplex.Messaging.Version
|
import Simplex.Messaging.Version
|
||||||
import UnliftIO.STM
|
import UnliftIO.STM
|
||||||
|
|
||||||
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe Bool, Bool, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime, Maybe UIThemeEntityOverrides, Maybe CustomData) :. GroupMemberRow
|
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe Bool, Bool, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime, Maybe MemberId, Maybe BusinessChatType, Maybe UIThemeEntityOverrides, Maybe CustomData) :. GroupMemberRow
|
||||||
|
|
||||||
type GroupMemberRow = ((Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, Bool, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences))
|
type GroupMemberRow = ((Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, Bool, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences))
|
||||||
|
|
||||||
type MaybeGroupMemberRow = ((Maybe Int64, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe Bool, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe ConnReqContact, Maybe LocalAlias, Maybe Preferences))
|
type MaybeGroupMemberRow = ((Maybe Int64, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe Bool, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe ConnReqContact, Maybe LocalAlias, Maybe Preferences))
|
||||||
|
|
||||||
toGroupInfo :: VersionRangeChat -> Int64 -> GroupInfoRow -> GroupInfo
|
toGroupInfo :: VersionRangeChat -> Int64 -> GroupInfoRow -> GroupInfo
|
||||||
toGroupInfo vr userContactId ((groupId, localDisplayName, displayName, fullName, description, image, hostConnCustomUserProfileId, enableNtfs_, sendRcpts, favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt, uiThemes, customData) :. userMemberRow) =
|
toGroupInfo vr userContactId ((groupId, localDisplayName, displayName, fullName, description, image, hostConnCustomUserProfileId, enableNtfs_, sendRcpts, favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt, businessMemberId, businessChatType, uiThemes, customData) :. userMemberRow) =
|
||||||
let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr}
|
let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr}
|
||||||
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts, favorite}
|
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts, favorite}
|
||||||
fullGroupPreferences = mergeGroupPreferences groupPreferences
|
fullGroupPreferences = mergeGroupPreferences groupPreferences
|
||||||
groupProfile = GroupProfile {displayName, fullName, description, image, groupPreferences}
|
groupProfile = GroupProfile {displayName, fullName, description, image, groupPreferences}
|
||||||
in GroupInfo {groupId, localDisplayName, groupProfile, fullGroupPreferences, membership, hostConnCustomUserProfileId, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, uiThemes, customData}
|
businessChat = BusinessChatInfo <$> businessMemberId <*> businessChatType
|
||||||
|
in GroupInfo {groupId, localDisplayName, groupProfile, businessChat, fullGroupPreferences, membership, hostConnCustomUserProfileId, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, uiThemes, customData}
|
||||||
|
|
||||||
toGroupMember :: Int64 -> GroupMemberRow -> GroupMember
|
toGroupMember :: Int64 -> GroupMemberRow -> GroupMember
|
||||||
toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages, memberRestriction_) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, preferences)) =
|
toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages, memberRestriction_) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, preferences)) =
|
||||||
|
@ -276,7 +279,7 @@ getGroupAndMember db User {userId, userContactId} groupMemberId vr =
|
||||||
-- GroupInfo
|
-- GroupInfo
|
||||||
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
|
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
|
||||||
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
|
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
|
||||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.ui_themes, g.custom_data,
|
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data,
|
||||||
-- GroupInfo {membership}
|
-- GroupInfo {membership}
|
||||||
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
|
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
|
||||||
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
|
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
|
||||||
|
@ -342,6 +345,7 @@ createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = Exc
|
||||||
{ groupId,
|
{ groupId,
|
||||||
localDisplayName = ldn,
|
localDisplayName = ldn,
|
||||||
groupProfile,
|
groupProfile,
|
||||||
|
businessChat = Nothing,
|
||||||
fullGroupPreferences,
|
fullGroupPreferences,
|
||||||
membership,
|
membership,
|
||||||
hostConnCustomUserProfileId = Nothing,
|
hostConnCustomUserProfileId = Nothing,
|
||||||
|
@ -357,7 +361,7 @@ createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = Exc
|
||||||
-- | creates a new group record for the group the current user was invited to, or returns an existing one
|
-- | creates a new group record for the group the current user was invited to, or returns an existing one
|
||||||
createGroupInvitation :: DB.Connection -> VersionRangeChat -> User -> Contact -> GroupInvitation -> Maybe ProfileId -> ExceptT StoreError IO (GroupInfo, GroupMemberId)
|
createGroupInvitation :: DB.Connection -> VersionRangeChat -> User -> Contact -> GroupInvitation -> Maybe ProfileId -> ExceptT StoreError IO (GroupInfo, GroupMemberId)
|
||||||
createGroupInvitation _ _ _ Contact {localDisplayName, activeConn = Nothing} _ _ = throwError $ SEContactNotReady localDisplayName
|
createGroupInvitation _ _ _ Contact {localDisplayName, activeConn = Nothing} _ _ = throwError $ SEContactNotReady localDisplayName
|
||||||
createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activeConn = Just Connection {customUserProfileId, peerChatVRange}} GroupInvitation {fromMember, invitedMember, connRequest, groupProfile} incognitoProfileId = do
|
createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activeConn = Just Connection {customUserProfileId, peerChatVRange}} GroupInvitation {fromMember, invitedMember, connRequest, groupProfile, businessChat} incognitoProfileId = do
|
||||||
liftIO getInvitationGroupId_ >>= \case
|
liftIO getInvitationGroupId_ >>= \case
|
||||||
Nothing -> createGroupInvitation_
|
Nothing -> createGroupInvitation_
|
||||||
Just gId -> do
|
Just gId -> do
|
||||||
|
@ -395,10 +399,10 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ
|
||||||
[sql|
|
[sql|
|
||||||
INSERT INTO groups
|
INSERT INTO groups
|
||||||
(group_profile_id, local_display_name, inv_queue_info, host_conn_custom_user_profile_id, user_id, enable_ntfs,
|
(group_profile_id, local_display_name, inv_queue_info, host_conn_custom_user_profile_id, user_id, enable_ntfs,
|
||||||
created_at, updated_at, chat_ts, user_member_profile_sent_at)
|
created_at, updated_at, chat_ts, user_member_profile_sent_at, business_member_id, business_chat)
|
||||||
VALUES (?,?,?,?,?,?,?,?,?,?)
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
|
||||||
|]
|
|]
|
||||||
(profileId, localDisplayName, connRequest, customUserProfileId, userId, True, currentTs, currentTs, currentTs, currentTs)
|
((profileId, localDisplayName, connRequest, customUserProfileId, userId, True, currentTs, currentTs, currentTs, currentTs) :. businessChatTuple businessChat)
|
||||||
insertedRowId db
|
insertedRowId db
|
||||||
let hostVRange = adjustedMemberVRange vr peerChatVRange
|
let hostVRange = adjustedMemberVRange vr peerChatVRange
|
||||||
GroupMember {groupMemberId} <- createContactMemberInv_ db user groupId Nothing contact fromMember GCHostMember GSMemInvited IBUnknown Nothing currentTs hostVRange
|
GroupMember {groupMemberId} <- createContactMemberInv_ db user groupId Nothing contact fromMember GCHostMember GSMemInvited IBUnknown Nothing currentTs hostVRange
|
||||||
|
@ -409,6 +413,7 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ
|
||||||
{ groupId,
|
{ groupId,
|
||||||
localDisplayName,
|
localDisplayName,
|
||||||
groupProfile,
|
groupProfile,
|
||||||
|
businessChat = Nothing,
|
||||||
fullGroupPreferences,
|
fullGroupPreferences,
|
||||||
membership,
|
membership,
|
||||||
hostConnCustomUserProfileId = customUserProfileId,
|
hostConnCustomUserProfileId = customUserProfileId,
|
||||||
|
@ -423,6 +428,11 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ
|
||||||
groupMemberId
|
groupMemberId
|
||||||
)
|
)
|
||||||
|
|
||||||
|
businessChatTuple :: Maybe BusinessChatInfo -> (Maybe MemberId, Maybe BusinessChatType)
|
||||||
|
businessChatTuple = \case
|
||||||
|
Just BusinessChatInfo {memberId, chatType} -> (Just memberId, Just chatType)
|
||||||
|
Nothing -> (Nothing, Nothing)
|
||||||
|
|
||||||
adjustedMemberVRange :: VersionRangeChat -> VersionRangeChat -> VersionRangeChat
|
adjustedMemberVRange :: VersionRangeChat -> VersionRangeChat -> VersionRangeChat
|
||||||
adjustedMemberVRange chatVR vr@(VersionRange minV maxV) =
|
adjustedMemberVRange chatVR vr@(VersionRange minV maxV) =
|
||||||
let maxV' = min maxV (maxVersion chatVR)
|
let maxV' = min maxV (maxVersion chatVR)
|
||||||
|
@ -497,13 +507,19 @@ createContactMemberInv_ db User {userId, userContactId} groupId invitedByGroupMe
|
||||||
)
|
)
|
||||||
pure $ Right incognitoLdn
|
pure $ Right incognitoLdn
|
||||||
|
|
||||||
|
deleteContactCardKeepConn :: DB.Connection -> Int64 -> Contact -> IO ()
|
||||||
|
deleteContactCardKeepConn db connId Contact {contactId, profile = LocalProfile {profileId}} = do
|
||||||
|
DB.execute db "UPDATE connections SET contact_id = NULL WHERE connection_id = ?" (Only connId)
|
||||||
|
DB.execute db "DELETE FROM contacts WHERE contact_id = ?" (Only contactId)
|
||||||
|
DB.execute db "DELETE FROM contact_profiles WHERE contact_profile_id = ?" (Only profileId)
|
||||||
|
|
||||||
createGroupInvitedViaLink :: DB.Connection -> VersionRangeChat -> User -> Connection -> GroupLinkInvitation -> ExceptT StoreError IO (GroupInfo, GroupMember)
|
createGroupInvitedViaLink :: DB.Connection -> VersionRangeChat -> User -> Connection -> GroupLinkInvitation -> ExceptT StoreError IO (GroupInfo, GroupMember)
|
||||||
createGroupInvitedViaLink
|
createGroupInvitedViaLink
|
||||||
db
|
db
|
||||||
vr
|
vr
|
||||||
user@User {userId, userContactId}
|
user@User {userId, userContactId}
|
||||||
Connection {connId, customUserProfileId}
|
Connection {connId, customUserProfileId}
|
||||||
GroupLinkInvitation {fromMember, fromMemberName, invitedMember, groupProfile} = do
|
GroupLinkInvitation {fromMember, fromMemberName, invitedMember, groupProfile, businessChat} = do
|
||||||
currentTs <- liftIO getCurrentTime
|
currentTs <- liftIO getCurrentTime
|
||||||
groupId <- insertGroup_ currentTs
|
groupId <- insertGroup_ currentTs
|
||||||
hostMemberId <- insertHost_ currentTs groupId
|
hostMemberId <- insertHost_ currentTs groupId
|
||||||
|
@ -527,10 +543,10 @@ createGroupInvitedViaLink
|
||||||
[sql|
|
[sql|
|
||||||
INSERT INTO groups
|
INSERT INTO groups
|
||||||
(group_profile_id, local_display_name, host_conn_custom_user_profile_id, user_id, enable_ntfs,
|
(group_profile_id, local_display_name, host_conn_custom_user_profile_id, user_id, enable_ntfs,
|
||||||
created_at, updated_at, chat_ts, user_member_profile_sent_at)
|
created_at, updated_at, chat_ts, user_member_profile_sent_at, business_member_id, business_chat)
|
||||||
VALUES (?,?,?,?,?,?,?,?,?)
|
VALUES (?,?,?,?,?,?,?,?,?,?,?)
|
||||||
|]
|
|]
|
||||||
(profileId, localDisplayName, customUserProfileId, userId, True, currentTs, currentTs, currentTs, currentTs)
|
((profileId, localDisplayName, customUserProfileId, userId, True, currentTs, currentTs, currentTs, currentTs) :. businessChatTuple businessChat)
|
||||||
insertedRowId db
|
insertedRowId db
|
||||||
insertHost_ currentTs groupId = do
|
insertHost_ currentTs groupId = do
|
||||||
let fromMemberProfile = profileFromName fromMemberName
|
let fromMemberProfile = profileFromName fromMemberName
|
||||||
|
@ -637,7 +653,7 @@ getUserGroupDetails db vr User {userId, userContactId} _contactId_ search_ =
|
||||||
SELECT
|
SELECT
|
||||||
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
|
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
|
||||||
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
|
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
|
||||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.ui_themes, g.custom_data,
|
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data,
|
||||||
mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction,
|
mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction,
|
||||||
mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences
|
mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences
|
||||||
FROM groups g
|
FROM groups g
|
||||||
|
@ -879,9 +895,7 @@ createAcceptedMember
|
||||||
User {userId, userContactId}
|
User {userId, userContactId}
|
||||||
GroupInfo {groupId, membership}
|
GroupInfo {groupId, membership}
|
||||||
UserContactRequest {cReqChatVRange, localDisplayName, profileId}
|
UserContactRequest {cReqChatVRange, localDisplayName, profileId}
|
||||||
memberRole = do
|
memberRole =
|
||||||
liftIO $
|
|
||||||
DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND local_display_name = ?" (userId, localDisplayName)
|
|
||||||
createWithRandomId gVar $ \memId -> do
|
createWithRandomId gVar $ \memId -> do
|
||||||
createdAt <- liftIO getCurrentTime
|
createdAt <- liftIO getCurrentTime
|
||||||
insertMember_ (MemberId memId) createdAt
|
insertMember_ (MemberId memId) createdAt
|
||||||
|
@ -917,6 +931,46 @@ createAcceptedMemberConnection
|
||||||
Connection {connId} <- createConnection_ db userId ConnMember (Just groupMemberId) agentConnId ConnNew chatV cReqChatVRange Nothing (Just userContactLinkId) Nothing 0 createdAt subMode PQSupportOff
|
Connection {connId} <- createConnection_ db userId ConnMember (Just groupMemberId) agentConnId ConnNew chatV cReqChatVRange Nothing (Just userContactLinkId) Nothing 0 createdAt subMode PQSupportOff
|
||||||
setCommandConnId db user cmdId connId
|
setCommandConnId db user cmdId connId
|
||||||
|
|
||||||
|
createBusinessRequestGroup :: DB.Connection -> VersionRangeChat -> TVar ChaChaDRG -> User -> UserContactRequest -> GroupPreferences -> ExceptT StoreError IO (GroupInfo, GroupMember)
|
||||||
|
createBusinessRequestGroup
|
||||||
|
db
|
||||||
|
vr
|
||||||
|
gVar
|
||||||
|
user@User {userId}
|
||||||
|
ucr@UserContactRequest {profile}
|
||||||
|
groupPreferences = do
|
||||||
|
currentTs <- liftIO getCurrentTime
|
||||||
|
groupInfo <- insertGroup_ currentTs
|
||||||
|
(groupMemberId, memberId) <- createAcceptedMember db gVar user groupInfo ucr GRMember
|
||||||
|
liftIO $ setBusinessMemberId groupInfo memberId
|
||||||
|
acceptedMember <- getGroupMemberById db vr user groupMemberId
|
||||||
|
pure (groupInfo, acceptedMember)
|
||||||
|
where
|
||||||
|
insertGroup_ currentTs = ExceptT $ do
|
||||||
|
let Profile {displayName, fullName, image} = profile
|
||||||
|
withLocalDisplayName db userId displayName $ \localDisplayName -> runExceptT $ do
|
||||||
|
groupId <- liftIO $ do
|
||||||
|
DB.execute
|
||||||
|
db
|
||||||
|
"INSERT INTO group_profiles (display_name, full_name, image, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?)"
|
||||||
|
(displayName, fullName, image, userId, groupPreferences, currentTs, currentTs)
|
||||||
|
profileId <- insertedRowId db
|
||||||
|
DB.execute
|
||||||
|
db
|
||||||
|
[sql|
|
||||||
|
INSERT INTO groups
|
||||||
|
(group_profile_id, local_display_name, user_id, enable_ntfs,
|
||||||
|
created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat)
|
||||||
|
VALUES (?,?,?,?,?,?,?,?,?)
|
||||||
|
|]
|
||||||
|
(profileId, localDisplayName, userId, True, currentTs, currentTs, currentTs, currentTs, BCCustomer)
|
||||||
|
insertedRowId db
|
||||||
|
memberId <- liftIO $ encodedRandomBytes gVar 12
|
||||||
|
void $ createContactMemberInv_ db user groupId Nothing user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser Nothing currentTs vr
|
||||||
|
getGroupInfo db vr user groupId
|
||||||
|
setBusinessMemberId GroupInfo {groupId} businessMemberId = do
|
||||||
|
DB.execute db "UPDATE groups SET business_member_id = ? WHERE group_id = ?" (businessMemberId, groupId)
|
||||||
|
|
||||||
getContactViaMember :: DB.Connection -> VersionRangeChat -> User -> GroupMember -> ExceptT StoreError IO Contact
|
getContactViaMember :: DB.Connection -> VersionRangeChat -> User -> GroupMember -> ExceptT StoreError IO Contact
|
||||||
getContactViaMember db vr user@User {userId} GroupMember {groupMemberId} = do
|
getContactViaMember db vr user@User {userId} GroupMember {groupMemberId} = do
|
||||||
contactId <-
|
contactId <-
|
||||||
|
@ -1315,7 +1369,7 @@ getViaGroupMember db vr User {userId, userContactId} Contact {contactId} =
|
||||||
-- GroupInfo
|
-- GroupInfo
|
||||||
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
|
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
|
||||||
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
|
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
|
||||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.ui_themes, g.custom_data,
|
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data,
|
||||||
-- GroupInfo {membership}
|
-- GroupInfo {membership}
|
||||||
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
|
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
|
||||||
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
|
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
|
||||||
|
@ -1411,7 +1465,7 @@ getGroupInfo db vr User {userId, userContactId} groupId =
|
||||||
-- GroupInfo
|
-- GroupInfo
|
||||||
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
|
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
|
||||||
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
|
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
|
||||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.ui_themes, g.custom_data,
|
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data,
|
||||||
-- GroupMember - membership
|
-- GroupMember - membership
|
||||||
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
|
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
|
||||||
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
|
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
|
||||||
|
|
|
@ -117,6 +117,7 @@ import Simplex.Chat.Migrations.M20241010_contact_requests_contact_id
|
||||||
import Simplex.Chat.Migrations.M20241023_chat_item_autoincrement_id
|
import Simplex.Chat.Migrations.M20241023_chat_item_autoincrement_id
|
||||||
import Simplex.Chat.Migrations.M20241027_server_operators
|
import Simplex.Chat.Migrations.M20241027_server_operators
|
||||||
import Simplex.Chat.Migrations.M20241125_indexes
|
import Simplex.Chat.Migrations.M20241125_indexes
|
||||||
|
import Simplex.Chat.Migrations.M20241128_business_chats
|
||||||
import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..))
|
import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..))
|
||||||
|
|
||||||
schemaMigrations :: [(String, Query, Maybe Query)]
|
schemaMigrations :: [(String, Query, Maybe Query)]
|
||||||
|
@ -233,7 +234,8 @@ schemaMigrations =
|
||||||
("20241010_contact_requests_contact_id", m20241010_contact_requests_contact_id, Just down_m20241010_contact_requests_contact_id),
|
("20241010_contact_requests_contact_id", m20241010_contact_requests_contact_id, Just down_m20241010_contact_requests_contact_id),
|
||||||
("20241023_chat_item_autoincrement_id", m20241023_chat_item_autoincrement_id, Just down_m20241023_chat_item_autoincrement_id),
|
("20241023_chat_item_autoincrement_id", m20241023_chat_item_autoincrement_id, Just down_m20241023_chat_item_autoincrement_id),
|
||||||
("20241027_server_operators", m20241027_server_operators, Just down_m20241027_server_operators),
|
("20241027_server_operators", m20241027_server_operators, Just down_m20241027_server_operators),
|
||||||
("20241125_indexes", m20241125_indexes, Just down_m20241125_indexes)
|
("20241125_indexes", m20241125_indexes, Just down_m20241125_indexes),
|
||||||
|
("20241128_business_chats", m20241128_business_chats, Just down_m20241128_business_chats)
|
||||||
]
|
]
|
||||||
|
|
||||||
-- | The list of migrations in ascending order by date
|
-- | The list of migrations in ascending order by date
|
||||||
|
|
|
@ -445,7 +445,8 @@ data UserContactLink = UserContactLink
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
data AutoAccept = AutoAccept
|
data AutoAccept = AutoAccept
|
||||||
{ acceptIncognito :: IncognitoEnabled,
|
{ businessAddress :: Bool, -- possibly, it can be wrapped together with acceptIncognito, or AutoAccept made sum type
|
||||||
|
acceptIncognito :: IncognitoEnabled,
|
||||||
autoReply :: Maybe MsgContent
|
autoReply :: Maybe MsgContent
|
||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
@ -454,10 +455,10 @@ $(J.deriveJSON defaultJSON ''AutoAccept)
|
||||||
|
|
||||||
$(J.deriveJSON defaultJSON ''UserContactLink)
|
$(J.deriveJSON defaultJSON ''UserContactLink)
|
||||||
|
|
||||||
toUserContactLink :: (ConnReqContact, Bool, IncognitoEnabled, Maybe MsgContent) -> UserContactLink
|
toUserContactLink :: (ConnReqContact, Bool, Bool, IncognitoEnabled, Maybe MsgContent) -> UserContactLink
|
||||||
toUserContactLink (connReq, autoAccept, acceptIncognito, autoReply) =
|
toUserContactLink (connReq, autoAccept, businessAddress, acceptIncognito, autoReply) =
|
||||||
UserContactLink connReq $
|
UserContactLink connReq $
|
||||||
if autoAccept then Just AutoAccept {acceptIncognito, autoReply} else Nothing
|
if autoAccept then Just AutoAccept {businessAddress, acceptIncognito, autoReply} else Nothing
|
||||||
|
|
||||||
getUserAddress :: DB.Connection -> User -> ExceptT StoreError IO UserContactLink
|
getUserAddress :: DB.Connection -> User -> ExceptT StoreError IO UserContactLink
|
||||||
getUserAddress db User {userId} =
|
getUserAddress db User {userId} =
|
||||||
|
@ -465,7 +466,7 @@ getUserAddress db User {userId} =
|
||||||
DB.query
|
DB.query
|
||||||
db
|
db
|
||||||
[sql|
|
[sql|
|
||||||
SELECT conn_req_contact, auto_accept, auto_accept_incognito, auto_reply_msg_content
|
SELECT conn_req_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content
|
||||||
FROM user_contact_links
|
FROM user_contact_links
|
||||||
WHERE user_id = ? AND local_display_name = '' AND group_id IS NULL
|
WHERE user_id = ? AND local_display_name = '' AND group_id IS NULL
|
||||||
|]
|
|]
|
||||||
|
@ -477,7 +478,7 @@ getUserContactLinkById db userId userContactLinkId =
|
||||||
DB.query
|
DB.query
|
||||||
db
|
db
|
||||||
[sql|
|
[sql|
|
||||||
SELECT conn_req_contact, auto_accept, auto_accept_incognito, auto_reply_msg_content, group_id, group_link_member_role
|
SELECT conn_req_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content, group_id, group_link_member_role
|
||||||
FROM user_contact_links
|
FROM user_contact_links
|
||||||
WHERE user_id = ?
|
WHERE user_id = ?
|
||||||
AND user_contact_link_id = ?
|
AND user_contact_link_id = ?
|
||||||
|
@ -490,7 +491,7 @@ getUserContactLinkByConnReq db User {userId} (cReqSchema1, cReqSchema2) =
|
||||||
DB.query
|
DB.query
|
||||||
db
|
db
|
||||||
[sql|
|
[sql|
|
||||||
SELECT conn_req_contact, auto_accept, auto_accept_incognito, auto_reply_msg_content
|
SELECT conn_req_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content
|
||||||
FROM user_contact_links
|
FROM user_contact_links
|
||||||
WHERE user_id = ? AND conn_req_contact IN (?,?)
|
WHERE user_id = ? AND conn_req_contact IN (?,?)
|
||||||
|]
|
|]
|
||||||
|
@ -522,13 +523,13 @@ updateUserAddressAutoAccept db user@User {userId} autoAccept = do
|
||||||
db
|
db
|
||||||
[sql|
|
[sql|
|
||||||
UPDATE user_contact_links
|
UPDATE user_contact_links
|
||||||
SET auto_accept = ?, auto_accept_incognito = ?, auto_reply_msg_content = ?
|
SET auto_accept = ?, business_address = ?, auto_accept_incognito = ?, auto_reply_msg_content = ?
|
||||||
WHERE user_id = ? AND local_display_name = '' AND group_id IS NULL
|
WHERE user_id = ? AND local_display_name = '' AND group_id IS NULL
|
||||||
|]
|
|]
|
||||||
(ucl :. Only userId)
|
(ucl :. Only userId)
|
||||||
ucl = case autoAccept of
|
ucl = case autoAccept of
|
||||||
Just AutoAccept {acceptIncognito, autoReply} -> (True, acceptIncognito, autoReply)
|
Just AutoAccept {businessAddress, acceptIncognito, autoReply} -> (True, businessAddress, acceptIncognito, autoReply)
|
||||||
_ -> (False, False, Nothing)
|
_ -> (False, False, False, Nothing)
|
||||||
|
|
||||||
getProtocolServers :: forall p. ProtocolTypeI p => DB.Connection -> SProtocolType p -> User -> IO [UserServer p]
|
getProtocolServers :: forall p. ProtocolTypeI p => DB.Connection -> SProtocolType p -> User -> IO [UserServer p]
|
||||||
getProtocolServers db p User {userId} =
|
getProtocolServers db p User {userId} =
|
||||||
|
@ -589,7 +590,7 @@ getServerOperators db = do
|
||||||
let conditionsAction = usageConditionsAction ops currentConditions now
|
let conditionsAction = usageConditionsAction ops currentConditions now
|
||||||
pure ServerOperatorConditions {serverOperators = ops, currentConditions, conditionsAction}
|
pure ServerOperatorConditions {serverOperators = ops, currentConditions, conditionsAction}
|
||||||
|
|
||||||
getUserServers :: DB.Connection -> User -> ExceptT StoreError IO ([Maybe ServerOperator], [UserServer 'PSMP], [UserServer 'PXFTP])
|
getUserServers :: DB.Connection -> User -> ExceptT StoreError IO ([Maybe ServerOperator], [UserServer 'PSMP], [UserServer 'PXFTP])
|
||||||
getUserServers db user =
|
getUserServers db user =
|
||||||
(,,)
|
(,,)
|
||||||
<$> (map Just . serverOperators <$> getServerOperators db)
|
<$> (map Just . serverOperators <$> getServerOperators db)
|
||||||
|
@ -620,7 +621,8 @@ getUpdateServerOperators db presetOps newUser = do
|
||||||
mapM_ insertConditions condsToAdd
|
mapM_ insertConditions condsToAdd
|
||||||
latestAcceptedConds_ <- getLatestAcceptedConditions db
|
latestAcceptedConds_ <- getLatestAcceptedConditions db
|
||||||
ops <- updatedServerOperators presetOps <$> getServerOperators_ db
|
ops <- updatedServerOperators presetOps <$> getServerOperators_ db
|
||||||
forM ops $ traverse $ mapM $ \(ASO _ op) -> -- traverse for tuple, mapM for Maybe
|
forM ops $ traverse $ mapM $ \(ASO _ op) ->
|
||||||
|
-- traverse for tuple, mapM for Maybe
|
||||||
case operatorId op of
|
case operatorId op of
|
||||||
DBNewEntity -> do
|
DBNewEntity -> do
|
||||||
op' <- insertOperator op
|
op' <- insertOperator op
|
||||||
|
@ -765,8 +767,9 @@ acceptConditions db condId opIds acceptedAt = do
|
||||||
liftIO $ forM_ operators $ \op -> acceptConditions_ db op conditionsCommit ts
|
liftIO $ forM_ operators $ \op -> acceptConditions_ db op conditionsCommit ts
|
||||||
where
|
where
|
||||||
getServerOperator_ opId =
|
getServerOperator_ opId =
|
||||||
ExceptT $ firstRow toServerOperator (SEOperatorNotFound opId) $
|
ExceptT $
|
||||||
DB.query db (serverOperatorQuery <> " WHERE server_operator_id = ?") (Only opId)
|
firstRow toServerOperator (SEOperatorNotFound opId) $
|
||||||
|
DB.query db (serverOperatorQuery <> " WHERE server_operator_id = ?") (Only opId)
|
||||||
|
|
||||||
acceptConditions_ :: DB.Connection -> ServerOperator -> Text -> Maybe UTCTime -> IO ()
|
acceptConditions_ :: DB.Connection -> ServerOperator -> Text -> Maybe UTCTime -> IO ()
|
||||||
acceptConditions_ db ServerOperator {operatorId, operatorTag} conditionsCommit acceptedAt =
|
acceptConditions_ db ServerOperator {operatorId, operatorTag} conditionsCommit acceptedAt =
|
||||||
|
|
|
@ -52,7 +52,7 @@ import Simplex.Messaging.Agent.Protocol (ACorrId, AEventTag (..), AEvtTag (..),
|
||||||
import Simplex.Messaging.Crypto.File (CryptoFileArgs (..))
|
import Simplex.Messaging.Crypto.File (CryptoFileArgs (..))
|
||||||
import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport, pattern PQEncOff)
|
import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport, pattern PQEncOff)
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, sumTypeJSON, taggedObjectJSON)
|
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, sumTypeJSON)
|
||||||
import Simplex.Messaging.Util (safeDecodeUtf8, (<$?>))
|
import Simplex.Messaging.Util (safeDecodeUtf8, (<$?>))
|
||||||
import Simplex.Messaging.Version
|
import Simplex.Messaging.Version
|
||||||
import Simplex.Messaging.Version.Internal
|
import Simplex.Messaging.Version.Internal
|
||||||
|
@ -371,6 +371,7 @@ data GroupInfo = GroupInfo
|
||||||
{ groupId :: GroupId,
|
{ groupId :: GroupId,
|
||||||
localDisplayName :: GroupName,
|
localDisplayName :: GroupName,
|
||||||
groupProfile :: GroupProfile,
|
groupProfile :: GroupProfile,
|
||||||
|
businessChat :: Maybe BusinessChatInfo,
|
||||||
fullGroupPreferences :: FullGroupPreferences,
|
fullGroupPreferences :: FullGroupPreferences,
|
||||||
membership :: GroupMember,
|
membership :: GroupMember,
|
||||||
hostConnCustomUserProfileId :: Maybe ProfileId,
|
hostConnCustomUserProfileId :: Maybe ProfileId,
|
||||||
|
@ -384,6 +385,24 @@ data GroupInfo = GroupInfo
|
||||||
}
|
}
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
data BusinessChatType
|
||||||
|
= BCBusiness -- used on the customer side
|
||||||
|
| BCCustomer -- used on the business side
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
instance TextEncoding BusinessChatType where
|
||||||
|
textEncode = \case
|
||||||
|
BCBusiness -> "business"
|
||||||
|
BCCustomer -> "customer"
|
||||||
|
textDecode = \case
|
||||||
|
"business" -> Just BCBusiness
|
||||||
|
"customer" -> Just BCCustomer
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
|
instance FromField BusinessChatType where fromField = fromTextField_ textDecode
|
||||||
|
|
||||||
|
instance ToField BusinessChatType where toField = toField . textEncode
|
||||||
|
|
||||||
groupName' :: GroupInfo -> GroupName
|
groupName' :: GroupInfo -> GroupName
|
||||||
groupName' GroupInfo {localDisplayName = g} = g
|
groupName' GroupInfo {localDisplayName = g} = g
|
||||||
|
|
||||||
|
@ -598,6 +617,7 @@ data GroupInvitation = GroupInvitation
|
||||||
invitedMember :: MemberIdRole,
|
invitedMember :: MemberIdRole,
|
||||||
connRequest :: ConnReqInvitation,
|
connRequest :: ConnReqInvitation,
|
||||||
groupProfile :: GroupProfile,
|
groupProfile :: GroupProfile,
|
||||||
|
businessChat :: Maybe BusinessChatInfo,
|
||||||
groupLinkId :: Maybe GroupLinkId,
|
groupLinkId :: Maybe GroupLinkId,
|
||||||
groupSize :: Maybe Int
|
groupSize :: Maybe Int
|
||||||
}
|
}
|
||||||
|
@ -608,6 +628,7 @@ data GroupLinkInvitation = GroupLinkInvitation
|
||||||
fromMemberName :: ContactName,
|
fromMemberName :: ContactName,
|
||||||
invitedMember :: MemberIdRole,
|
invitedMember :: MemberIdRole,
|
||||||
groupProfile :: GroupProfile,
|
groupProfile :: GroupProfile,
|
||||||
|
businessChat :: Maybe BusinessChatInfo,
|
||||||
groupSize :: Maybe Int
|
groupSize :: Maybe Int
|
||||||
}
|
}
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
@ -632,6 +653,12 @@ data MemberInfo = MemberInfo
|
||||||
}
|
}
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
data BusinessChatInfo = BusinessChatInfo
|
||||||
|
{ memberId :: MemberId,
|
||||||
|
chatType :: BusinessChatType
|
||||||
|
}
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
memberInfo :: GroupMember -> MemberInfo
|
memberInfo :: GroupMember -> MemberInfo
|
||||||
memberInfo GroupMember {memberId, memberRole, memberProfile, activeConn} =
|
memberInfo GroupMember {memberId, memberRole, memberProfile, activeConn} =
|
||||||
MemberInfo
|
MemberInfo
|
||||||
|
@ -1696,6 +1723,10 @@ $(JQ.deriveJSON (enumJSON $ dropPrefix "MF") ''MsgFilter)
|
||||||
|
|
||||||
$(JQ.deriveJSON defaultJSON ''ChatSettings)
|
$(JQ.deriveJSON defaultJSON ''ChatSettings)
|
||||||
|
|
||||||
|
$(JQ.deriveJSON (enumJSON $ dropPrefix "BC") ''BusinessChatType)
|
||||||
|
|
||||||
|
$(JQ.deriveJSON defaultJSON ''BusinessChatInfo)
|
||||||
|
|
||||||
$(JQ.deriveJSON defaultJSON ''GroupInfo)
|
$(JQ.deriveJSON defaultJSON ''GroupInfo)
|
||||||
|
|
||||||
$(JQ.deriveJSON defaultJSON ''Group)
|
$(JQ.deriveJSON defaultJSON ''Group)
|
||||||
|
@ -1706,18 +1737,18 @@ instance FromField MsgFilter where fromField = fromIntField_ msgFilterIntP
|
||||||
|
|
||||||
instance ToField MsgFilter where toField = toField . msgFilterInt
|
instance ToField MsgFilter where toField = toField . msgFilterInt
|
||||||
|
|
||||||
$(JQ.deriveJSON (taggedObjectJSON $ dropPrefix "CRData") ''CReqClientData)
|
$(JQ.deriveJSON defaultJSON ''CReqClientData)
|
||||||
|
|
||||||
$(JQ.deriveJSON defaultJSON ''MemberIdRole)
|
$(JQ.deriveJSON defaultJSON ''MemberIdRole)
|
||||||
|
|
||||||
|
$(JQ.deriveJSON defaultJSON ''MemberInfo)
|
||||||
|
|
||||||
$(JQ.deriveJSON defaultJSON ''GroupInvitation)
|
$(JQ.deriveJSON defaultJSON ''GroupInvitation)
|
||||||
|
|
||||||
$(JQ.deriveJSON defaultJSON ''GroupLinkInvitation)
|
$(JQ.deriveJSON defaultJSON ''GroupLinkInvitation)
|
||||||
|
|
||||||
$(JQ.deriveJSON defaultJSON ''IntroInvitation)
|
$(JQ.deriveJSON defaultJSON ''IntroInvitation)
|
||||||
|
|
||||||
$(JQ.deriveJSON defaultJSON ''MemberInfo)
|
|
||||||
|
|
||||||
$(JQ.deriveJSON defaultJSON ''MemberRestrictions)
|
$(JQ.deriveJSON defaultJSON ''MemberRestrictions)
|
||||||
|
|
||||||
$(JQ.deriveJSON defaultJSON ''GroupMemberRef)
|
$(JQ.deriveJSON defaultJSON ''GroupMemberRef)
|
||||||
|
|
|
@ -390,6 +390,33 @@ defaultGroupPrefs =
|
||||||
emptyGroupPrefs :: GroupPreferences
|
emptyGroupPrefs :: GroupPreferences
|
||||||
emptyGroupPrefs = GroupPreferences Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
|
emptyGroupPrefs = GroupPreferences Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
|
||||||
|
|
||||||
|
businessGroupPrefs :: Preferences -> GroupPreferences
|
||||||
|
businessGroupPrefs Preferences {timedMessages, fullDelete, reactions, voice} =
|
||||||
|
defaultBusinessGroupPrefs
|
||||||
|
{ timedMessages = Just TimedMessagesGroupPreference {enable = maybe FEOff enableFeature timedMessages, ttl = maybe Nothing prefParam timedMessages},
|
||||||
|
fullDelete = Just FullDeleteGroupPreference {enable = maybe FEOff enableFeature fullDelete},
|
||||||
|
reactions = Just ReactionsGroupPreference {enable = maybe FEOn enableFeature reactions},
|
||||||
|
voice = Just VoiceGroupPreference {enable = maybe FEOff enableFeature voice, role = Nothing}
|
||||||
|
}
|
||||||
|
where
|
||||||
|
enableFeature :: FeatureI f => FeaturePreference f -> GroupFeatureEnabled
|
||||||
|
enableFeature p = case getField @"allow" p of
|
||||||
|
FANo -> FEOff
|
||||||
|
_ -> FEOn
|
||||||
|
|
||||||
|
defaultBusinessGroupPrefs :: GroupPreferences
|
||||||
|
defaultBusinessGroupPrefs =
|
||||||
|
GroupPreferences
|
||||||
|
{ timedMessages = Just $ TimedMessagesGroupPreference FEOff Nothing,
|
||||||
|
directMessages = Just $ DirectMessagesGroupPreference FEOff Nothing,
|
||||||
|
fullDelete = Just $ FullDeleteGroupPreference FEOff,
|
||||||
|
reactions = Just $ ReactionsGroupPreference FEOn,
|
||||||
|
voice = Just $ VoiceGroupPreference FEOff Nothing,
|
||||||
|
files = Just $ FilesGroupPreference FEOn Nothing,
|
||||||
|
simplexLinks = Just $ SimplexLinksGroupPreference FEOn Nothing,
|
||||||
|
history = Just $ HistoryGroupPreference FEOn
|
||||||
|
}
|
||||||
|
|
||||||
data TimedMessagesPreference = TimedMessagesPreference
|
data TimedMessagesPreference = TimedMessagesPreference
|
||||||
{ allow :: FeatureAllowed,
|
{ allow :: FeatureAllowed,
|
||||||
ttl :: Maybe Int
|
ttl :: Maybe Int
|
||||||
|
|
|
@ -204,12 +204,14 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
||||||
CRContactDeletedByContact u c -> ttyUser u [ttyFullContact c <> " deleted contact with you"]
|
CRContactDeletedByContact u c -> ttyUser u [ttyFullContact c <> " deleted contact with you"]
|
||||||
CRChatCleared u chatInfo -> ttyUser u $ viewChatCleared chatInfo
|
CRChatCleared u chatInfo -> ttyUser u $ viewChatCleared chatInfo
|
||||||
CRAcceptingContactRequest u c -> ttyUser u $ viewAcceptingContactRequest c
|
CRAcceptingContactRequest u c -> ttyUser u $ viewAcceptingContactRequest c
|
||||||
|
CRAcceptingBusinessRequest u g -> ttyUser u $ viewAcceptingBusinessRequest g
|
||||||
CRContactAlreadyExists u c -> ttyUser u [ttyFullContact c <> ": contact already exists"]
|
CRContactAlreadyExists u c -> ttyUser u [ttyFullContact c <> ": contact already exists"]
|
||||||
CRContactRequestAlreadyAccepted u c -> ttyUser u [ttyFullContact c <> ": sent you a duplicate contact request, but you are already connected, no action needed"]
|
CRContactRequestAlreadyAccepted u c -> ttyUser u [ttyFullContact c <> ": sent you a duplicate contact request, but you are already connected, no action needed"]
|
||||||
CRUserContactLinkCreated u cReq -> ttyUser u $ connReqContact_ "Your new chat address is created!" cReq
|
CRUserContactLinkCreated u cReq -> ttyUser u $ connReqContact_ "Your new chat address is created!" cReq
|
||||||
CRUserContactLinkDeleted u -> ttyUser u viewUserContactLinkDeleted
|
CRUserContactLinkDeleted u -> ttyUser u viewUserContactLinkDeleted
|
||||||
CRUserAcceptedGroupSent u _g _ -> ttyUser u [] -- [ttyGroup' g <> ": joining the group..."]
|
CRUserAcceptedGroupSent u _g _ -> ttyUser u [] -- [ttyGroup' g <> ": joining the group..."]
|
||||||
CRGroupLinkConnecting u g _ -> ttyUser u [ttyGroup' g <> ": joining the group..."]
|
CRGroupLinkConnecting u g _ -> ttyUser u [ttyGroup' g <> ": joining the group..."]
|
||||||
|
CRBusinessLinkConnecting u g _ _ -> ttyUser u [ttyGroup' g <> ": joining the group..."]
|
||||||
CRUserDeletedMember u g m -> ttyUser u [ttyGroup' g <> ": you removed " <> ttyMember m <> " from the group"]
|
CRUserDeletedMember u g m -> ttyUser u [ttyGroup' g <> ": you removed " <> ttyMember m <> " from the group"]
|
||||||
CRLeftMemberUser u g -> ttyUser u $ [ttyGroup' g <> ": you left the group"] <> groupPreserved g
|
CRLeftMemberUser u g -> ttyUser u $ [ttyGroup' g <> ": you left the group"] <> groupPreserved g
|
||||||
CRUnknownMemberCreated u g fwdM um -> ttyUser u [ttyGroup' g <> ": " <> ttyMember fwdM <> " forwarded a message from an unknown member, creating unknown member record " <> ttyMember um]
|
CRUnknownMemberCreated u g fwdM um -> ttyUser u [ttyGroup' g <> ": " <> ttyMember fwdM <> " forwarded a message from an unknown member, creating unknown member record " <> ttyMember um]
|
||||||
|
@ -979,9 +981,14 @@ simplexChatContact (CRContactUri crData) = CRContactUri crData {crScheme = simpl
|
||||||
|
|
||||||
autoAcceptStatus_ :: Maybe AutoAccept -> [StyledString]
|
autoAcceptStatus_ :: Maybe AutoAccept -> [StyledString]
|
||||||
autoAcceptStatus_ = \case
|
autoAcceptStatus_ = \case
|
||||||
Just AutoAccept {acceptIncognito, autoReply} ->
|
Just AutoAccept {businessAddress, acceptIncognito, autoReply} ->
|
||||||
("auto_accept on" <> if acceptIncognito then ", incognito" else "")
|
("auto_accept on" <> aaInfo)
|
||||||
: maybe [] ((["auto reply:"] <>) . ttyMsgContent) autoReply
|
: maybe [] ((["auto reply:"] <>) . ttyMsgContent) autoReply
|
||||||
|
where
|
||||||
|
aaInfo
|
||||||
|
| businessAddress = ", business"
|
||||||
|
| acceptIncognito = ", incognito"
|
||||||
|
| otherwise = ""
|
||||||
_ -> ["auto_accept off"]
|
_ -> ["auto_accept off"]
|
||||||
|
|
||||||
groupLink_ :: StyledString -> GroupInfo -> ConnReqContact -> GroupMemberRole -> [StyledString]
|
groupLink_ :: StyledString -> GroupInfo -> ConnReqContact -> GroupMemberRole -> [StyledString]
|
||||||
|
@ -1017,6 +1024,9 @@ viewAcceptingContactRequest ct
|
||||||
| contactReady ct = [ttyFullContact ct <> ": accepting contact request, you can send messages to contact"]
|
| contactReady ct = [ttyFullContact ct <> ": accepting contact request, you can send messages to contact"]
|
||||||
| otherwise = [ttyFullContact ct <> ": accepting contact request..."]
|
| otherwise = [ttyFullContact ct <> ": accepting contact request..."]
|
||||||
|
|
||||||
|
viewAcceptingBusinessRequest :: GroupInfo -> [StyledString]
|
||||||
|
viewAcceptingBusinessRequest g = [ttyFullGroup g <> ": accepting business address request..."]
|
||||||
|
|
||||||
viewReceivedContactRequest :: ContactName -> Profile -> [StyledString]
|
viewReceivedContactRequest :: ContactName -> Profile -> [StyledString]
|
||||||
viewReceivedContactRequest c Profile {fullName} =
|
viewReceivedContactRequest c Profile {fullName} =
|
||||||
[ ttyFullName c fullName <> " wants to connect to you!",
|
[ ttyFullName c fullName <> " wants to connect to you!",
|
||||||
|
|
|
@ -47,6 +47,8 @@ chatProfileTests = do
|
||||||
it "delete connection requests when contact link deleted" testDeleteConnectionRequests
|
it "delete connection requests when contact link deleted" testDeleteConnectionRequests
|
||||||
it "auto-reply message" testAutoReplyMessage
|
it "auto-reply message" testAutoReplyMessage
|
||||||
it "auto-reply message in incognito" testAutoReplyMessageInIncognito
|
it "auto-reply message in incognito" testAutoReplyMessageInIncognito
|
||||||
|
describe "business address" $ do
|
||||||
|
it "create and connect via business address" testBusinessAddress
|
||||||
describe "contact address connection plan" $ do
|
describe "contact address connection plan" $ do
|
||||||
it "contact address ok to connect; known contact" testPlanAddressOkKnown
|
it "contact address ok to connect; known contact" testPlanAddressOkKnown
|
||||||
it "own contact address" testPlanAddressOwn
|
it "own contact address" testPlanAddressOwn
|
||||||
|
@ -677,6 +679,49 @@ testAutoReplyMessageInIncognito = testChat2 aliceProfile bobProfile $
|
||||||
alice <## "use /i bob to print out this incognito profile again"
|
alice <## "use /i bob to print out this incognito profile again"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
testBusinessAddress :: HasCallStack => FilePath -> IO ()
|
||||||
|
testBusinessAddress = testChat3 businessProfile aliceProfile {fullName = "Alice @ Biz"} bobProfile $
|
||||||
|
\biz alice bob -> do
|
||||||
|
biz ##> "/ad"
|
||||||
|
cLink <- getContactLink biz True
|
||||||
|
biz ##> "/auto_accept on business"
|
||||||
|
biz <## "auto_accept on, business"
|
||||||
|
bob ##> ("/c " <> cLink)
|
||||||
|
bob <## "connection request sent!"
|
||||||
|
biz <## "#bob_1 (Bob): accepting business address request..."
|
||||||
|
biz <## "#bob_1: bob joined the group"
|
||||||
|
bob <## "#biz: joining the group..."
|
||||||
|
bob <## "#biz: you joined the group"
|
||||||
|
biz #> "#bob_1 hi"
|
||||||
|
bob <# "#biz biz_1> hi"
|
||||||
|
bob #> "#biz hello"
|
||||||
|
biz <# "#bob_1 bob> hello"
|
||||||
|
connectUsers biz alice
|
||||||
|
biz <##> alice
|
||||||
|
biz ##> "/a #bob_1 alice"
|
||||||
|
biz <## "invitation to join the group #bob_1 sent to alice"
|
||||||
|
alice <## "#bob (Bob): biz invites you to join the group as member"
|
||||||
|
alice <## "use /j bob to accept"
|
||||||
|
alice ##> "/j bob"
|
||||||
|
concurrentlyN_
|
||||||
|
[ do
|
||||||
|
alice <## "#bob: you joined the group"
|
||||||
|
alice <### [WithTime "#bob biz> hi [>>]", WithTime "#bob bob_1> hello [>>]"]
|
||||||
|
alice <## "#bob: member bob_1 (Bob) is connected",
|
||||||
|
biz <## "#bob_1: alice joined the group",
|
||||||
|
do
|
||||||
|
bob <## "#biz: biz_1 added alice (Alice @ Biz) to the group (connecting...)"
|
||||||
|
bob <## "#biz: new member alice is connected"
|
||||||
|
]
|
||||||
|
alice #> "#bob hey"
|
||||||
|
concurrently_
|
||||||
|
(bob <# "#biz alice> hey")
|
||||||
|
(biz <# "#bob_1 alice> hey")
|
||||||
|
bob #> "#biz hey there"
|
||||||
|
concurrently_
|
||||||
|
(alice <# "#bob bob_1> hey there")
|
||||||
|
(biz <# "#bob_1 bob> hey there")
|
||||||
|
|
||||||
testPlanAddressOkKnown :: HasCallStack => FilePath -> IO ()
|
testPlanAddressOkKnown :: HasCallStack => FilePath -> IO ()
|
||||||
testPlanAddressOkKnown =
|
testPlanAddressOkKnown =
|
||||||
testChat2 aliceProfile bobProfile $
|
testChat2 aliceProfile bobProfile $
|
||||||
|
@ -2380,7 +2425,7 @@ testSetUITheme =
|
||||||
a <## "you've shared main profile with this contact"
|
a <## "you've shared main profile with this contact"
|
||||||
a <## "connection not verified, use /code command to see security code"
|
a <## "connection not verified, use /code command to see security code"
|
||||||
a <## "quantum resistant end-to-end encryption"
|
a <## "quantum resistant end-to-end encryption"
|
||||||
a <## "peer chat protocol version range: (Version 1, Version 9)"
|
a <## "peer chat protocol version range: (Version 1, Version 10)"
|
||||||
groupInfo a = do
|
groupInfo a = do
|
||||||
a <## "group ID: 1"
|
a <## "group ID: 1"
|
||||||
a <## "current members: 1"
|
a <## "current members: 1"
|
||||||
|
|
|
@ -64,6 +64,9 @@ cathProfile = Profile {displayName = "cath", fullName = "Catherine", image = Not
|
||||||
danProfile :: Profile
|
danProfile :: Profile
|
||||||
danProfile = Profile {displayName = "dan", fullName = "Daniel", image = Nothing, contactLink = Nothing, preferences = defaultPrefs}
|
danProfile = Profile {displayName = "dan", fullName = "Daniel", image = Nothing, contactLink = Nothing, preferences = defaultPrefs}
|
||||||
|
|
||||||
|
businessProfile :: Profile
|
||||||
|
businessProfile = Profile {displayName = "biz", fullName = "Biz Inc", image = Nothing, contactLink = Nothing, preferences = defaultPrefs}
|
||||||
|
|
||||||
it :: HasCallStack => String -> (FilePath -> Expectation) -> SpecWith (Arg (FilePath -> Expectation))
|
it :: HasCallStack => String -> (FilePath -> Expectation) -> SpecWith (Arg (FilePath -> Expectation))
|
||||||
it name test =
|
it name test =
|
||||||
Hspec.it name $ \tmp -> timeout t (test tmp) >>= maybe (error "test timed out") pure
|
Hspec.it name $ \tmp -> timeout t (test tmp) >>= maybe (error "test timed out") pure
|
||||||
|
|
|
@ -133,7 +133,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
|
||||||
"{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}"
|
"{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}"
|
||||||
##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing)))
|
##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing)))
|
||||||
it "x.msg.new chat message with chat version range" $
|
it "x.msg.new chat message with chat version range" $
|
||||||
"{\"v\":\"1-9\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}"
|
"{\"v\":\"1-10\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}"
|
||||||
##==## ChatMessage supportedChatVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing)))
|
##==## ChatMessage supportedChatVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing)))
|
||||||
it "x.msg.new quote" $
|
it "x.msg.new quote" $
|
||||||
"{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello to you too\",\"type\":\"text\"},\"quote\":{\"content\":{\"text\":\"hello there!\",\"type\":\"text\"},\"msgRef\":{\"msgId\":\"BQYHCA==\",\"sent\":true,\"sentAt\":\"1970-01-01T00:00:01.000000001Z\"}}}}"
|
"{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello to you too\",\"type\":\"text\"},\"quote\":{\"content\":{\"text\":\"hello there!\",\"type\":\"text\"},\"msgRef\":{\"msgId\":\"BQYHCA==\",\"sent\":true,\"sentAt\":\"1970-01-01T00:00:01.000000001Z\"}}}}"
|
||||||
|
@ -232,10 +232,10 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
|
||||||
==# XContact testProfile Nothing
|
==# XContact testProfile Nothing
|
||||||
it "x.grp.inv" $
|
it "x.grp.inv" $
|
||||||
"{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}}}}"
|
"{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}}}}"
|
||||||
#==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, groupLinkId = Nothing, groupSize = Nothing}
|
#==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, businessChat = Nothing, groupLinkId = Nothing, groupSize = Nothing}
|
||||||
it "x.grp.inv with group link id" $
|
it "x.grp.inv with group link id" $
|
||||||
"{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}, \"groupLinkId\":\"AQIDBA==\"}}}"
|
"{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}, \"groupLinkId\":\"AQIDBA==\"}}}"
|
||||||
#==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, groupLinkId = Just $ GroupLinkId "\1\2\3\4", groupSize = Nothing}
|
#==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, businessChat = Nothing, groupLinkId = Just $ GroupLinkId "\1\2\3\4", groupSize = Nothing}
|
||||||
it "x.grp.acpt without incognito profile" $
|
it "x.grp.acpt without incognito profile" $
|
||||||
"{\"v\":\"1\",\"event\":\"x.grp.acpt\",\"params\":{\"memberId\":\"AQIDBA==\"}}"
|
"{\"v\":\"1\",\"event\":\"x.grp.acpt\",\"params\":{\"memberId\":\"AQIDBA==\"}}"
|
||||||
#==# XGrpAcpt (MemberId "\1\2\3\4")
|
#==# XGrpAcpt (MemberId "\1\2\3\4")
|
||||||
|
@ -243,13 +243,13 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
|
||||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
"{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||||
#==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile}
|
#==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile}
|
||||||
it "x.grp.mem.new with member chat version range" $
|
it "x.grp.mem.new with member chat version range" $
|
||||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-9\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
"{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-10\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||||
#==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile}
|
#==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile}
|
||||||
it "x.grp.mem.intro" $
|
it "x.grp.mem.intro" $
|
||||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
"{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||||
#==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} Nothing
|
#==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} Nothing
|
||||||
it "x.grp.mem.intro with member chat version range" $
|
it "x.grp.mem.intro with member chat version range" $
|
||||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-9\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
"{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-10\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||||
#==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile} Nothing
|
#==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile} Nothing
|
||||||
it "x.grp.mem.intro with member restrictions" $
|
it "x.grp.mem.intro with member restrictions" $
|
||||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberRestrictions\":{\"restriction\":\"blocked\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
"{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberRestrictions\":{\"restriction\":\"blocked\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||||
|
@ -264,7 +264,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
|
||||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
"{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||||
#==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = Just testConnReq}
|
#==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = Just testConnReq}
|
||||||
it "x.grp.mem.fwd with member chat version range and w/t directConnReq" $
|
it "x.grp.mem.fwd with member chat version range and w/t directConnReq" $
|
||||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-9\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
"{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-10\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||||
#==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = Nothing}
|
#==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = Nothing}
|
||||||
it "x.grp.mem.info" $
|
it "x.grp.mem.info" $
|
||||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.info\",\"params\":{\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}"
|
"{\"v\":\"1\",\"event\":\"x.grp.mem.info\",\"params\":{\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue