diff --git a/apps/ios/Shared/Model/AppAPITypes.swift b/apps/ios/Shared/Model/AppAPITypes.swift index 855f3380cc..44a03aa037 100644 --- a/apps/ios/Shared/Model/AppAPITypes.swift +++ b/apps/ios/Shared/Model/AppAPITypes.swift @@ -1111,6 +1111,7 @@ enum ChatEvent: Decodable, ChatAPIResult { case connectedToGroupMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember, memberContact: Contact?) case groupUpdated(user: UserRef, toGroup: GroupInfo) case groupLinkDataUpdated(user: UserRef, groupInfo: GroupInfo, groupLink: GroupLink, groupRelays: [GroupRelay], relaysChanged: Bool) + case groupRelayUpdated(user: UserRef, groupInfo: GroupInfo, member: GroupMember, groupRelay: GroupRelay) case newMemberContactReceivedInv(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) // receiving file events case rcvFileAccepted(user: UserRef, chatItem: AChatItem) @@ -1188,6 +1189,7 @@ enum ChatEvent: Decodable, ChatAPIResult { case .connectedToGroupMember: "connectedToGroupMember" case .groupUpdated: "groupUpdated" case .groupLinkDataUpdated: "groupLinkDataUpdated" + case .groupRelayUpdated: "groupRelayUpdated" case .newMemberContactReceivedInv: "newMemberContactReceivedInv" case .rcvFileAccepted: "rcvFileAccepted" case .rcvFileAcceptedSndCancelled: "rcvFileAcceptedSndCancelled" @@ -1269,6 +1271,7 @@ enum ChatEvent: Decodable, ChatAPIResult { case let .connectedToGroupMember(u, groupInfo, member, memberContact): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\nmemberContact: \(String(describing: memberContact))") case let .groupUpdated(u, toGroup): return withUser(u, String(describing: toGroup)) case let .groupLinkDataUpdated(u, groupInfo, groupLink, groupRelays, relaysChanged): return withUser(u, "groupInfo: \(groupInfo)\ngroupLink: \(groupLink)\ngroupRelays: \(groupRelays)\nrelaysChanged: \(relaysChanged)") + case let .groupRelayUpdated(u, groupInfo, member, groupRelay): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\ngroupRelay: \(groupRelay)") case let .newMemberContactReceivedInv(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") case let .rcvFileAccepted(u, chatItem): return withUser(u, String(describing: chatItem)) case .rcvFileAcceptedSndCancelled: return noDetails diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 5e85417326..9c23ac6307 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -343,6 +343,13 @@ class ChannelRelaysModel: ObservableObject { self.groupRelays = groupRelays } + func updateRelay(_ groupInfo: GroupInfo, _ relay: GroupRelay) { + if groupId == groupInfo.groupId, + let i = groupRelays.firstIndex(where: { $0.groupRelayId == relay.groupRelayId }) { + groupRelays[i] = relay + } + } + func reset() { groupId = nil groupRelays = [] diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 1119f28bac..9e4f50b0af 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -2594,6 +2594,13 @@ func processReceivedMsg(_ res: ChatEvent) async { } } } + case let .groupRelayUpdated(user, groupInfo, member, groupRelay): + if active(user) { + await MainActor.run { + _ = m.upsertGroupMember(groupInfo, member) + ChannelRelaysModel.shared.updateRelay(groupInfo, groupRelay) + } + } case let .memberRole(user, groupInfo, byMember: _, member: member, fromRole: _, toRole: _): if active(user) { await MainActor.run { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift b/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift index 13b6c0e682..56ee370402 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift @@ -105,7 +105,6 @@ struct GroupLinkView: View { Label("Share link", systemImage: "square.and.arrow.up") } - // TODO [relays] review: channel link deletion is only possible together with deleting the channel if !creatingGroup && !isChannel { Button(role: .destructive) { alert = .deleteLink } label: { Label("Delete link", systemImage: "trash") diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index 442f547b9c..af7054db01 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -172,7 +172,6 @@ struct GroupMemberInfoView: View { let label: LocalizedStringKey = groupInfo.useRelays ? "Channel" : groupInfo.businessChat == nil ? "Group" : "Chat" infoRow(label, groupInfo.displayName) - // TODO [relays] review: role changing is not supported for channels currently if !groupInfo.useRelays, let roles = member.canChangeRoleTo(groupInfo: groupInfo) { Picker("Change role", selection: $newRole) { ForEach(roles) { role in @@ -637,7 +636,7 @@ struct GroupMemberInfoView: View { } } // TODO [relays] removing relay should also remove its link from group link data; - // removing last relay should be prohibited or show warning + // TODO - removing last relay should be prohibited or show warning if canRemove && mem.memberRole != .relay { if mem.memberStatus == .memRemoved || mem.memberStatus == .memLeft { deleteMemberMessagesButton(mem) diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ChatRelayView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ChatRelayView.swift index 14c6b61a34..df9ea10adf 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ChatRelayView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ChatRelayView.swift @@ -50,7 +50,6 @@ func validRelayAddress(_ address: String) -> Bool { } } -// TODO [relays] TBC matching relay to operator by domain (relay address can be hosted on operator server) func addChatRelay( _ relay: UserChatRelay, _ userServers: Binding<[UserOperatorServers]>, diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 0f3230e737..dcd8673de8 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -182,8 +182,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-1LMKtNtYaECIyeIru99CUt-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-1LMKtNtYaECIyeIru99CUt-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-1LMKtNtYaECIyeIru99CUt.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-1LMKtNtYaECIyeIru99CUt.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -553,8 +553,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-1LMKtNtYaECIyeIru99CUt-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.9-1LMKtNtYaECIyeIru99CUt-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-1LMKtNtYaECIyeIru99CUt.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.9-1LMKtNtYaECIyeIru99CUt.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -716,8 +716,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-1LMKtNtYaECIyeIru99CUt-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-1LMKtNtYaECIyeIru99CUt.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -803,8 +803,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-1LMKtNtYaECIyeIru99CUt-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-1LMKtNtYaECIyeIru99CUt.a */, ); path = Libraries; sourceTree = ""; diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 20358f75de..817337ddca 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -88,6 +88,13 @@ object ChannelRelaysModel { this.groupRelays.addAll(groupRelays) } + fun updateRelay(groupInfo: GroupInfo, relay: GroupRelay) { + if (groupId.value == groupInfo.groupId) { + val i = groupRelays.indexOfFirst { it.groupRelayId == relay.groupRelayId } + if (i >= 0) groupRelays[i] = relay + } + } + fun reset() { groupId.value = null groupRelays.clear() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 723e7bdc25..779209a00f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -2993,6 +2993,13 @@ object ChatController { } } } + is CR.GroupRelayUpdated -> + if (active(r.user)) { + withContext(Dispatchers.Main) { + chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member) + ChannelRelaysModel.updateRelay(r.groupInfo, r.groupRelay) + } + } is CR.NewMemberContactReceivedInv -> if (active(r.user)) { withContext(Dispatchers.Main) { @@ -6301,6 +6308,7 @@ sealed class CR { @Serializable @SerialName("connectedToGroupMember") class ConnectedToGroupMember(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val memberContact: Contact? = null): CR() @Serializable @SerialName("groupUpdated") class GroupUpdated(val user: UserRef, val toGroup: GroupInfo): CR() @Serializable @SerialName("groupLinkDataUpdated") class GroupLinkDataUpdated(val user: UserRef, val groupInfo: GroupInfo, val groupLink: GroupLink, val groupRelays: List, val relaysChanged: Boolean): CR() + @Serializable @SerialName("groupRelayUpdated") class GroupRelayUpdated(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val groupRelay: GroupRelay): CR() @Serializable @SerialName("groupLinkCreated") class GroupLinkCreated(val user: UserRef, val groupInfo: GroupInfo, val groupLink: GroupLink): CR() @Serializable @SerialName("groupLink") class CRGroupLink(val user: UserRef, val groupInfo: GroupInfo, val groupLink: GroupLink): CR() @Serializable @SerialName("groupLinkDeleted") class GroupLinkDeleted(val user: UserRef, val groupInfo: GroupInfo): CR() @@ -6486,6 +6494,7 @@ sealed class CR { is ConnectedToGroupMember -> "connectedToGroupMember" is GroupUpdated -> "groupUpdated" is GroupLinkDataUpdated -> "groupLinkDataUpdated" + is GroupRelayUpdated -> "groupRelayUpdated" is GroupLinkCreated -> "groupLinkCreated" is CRGroupLink -> "groupLink" is GroupLinkDeleted -> "groupLinkDeleted" @@ -6664,6 +6673,7 @@ sealed class CR { is ConnectedToGroupMember -> withUser(user, "groupInfo: $groupInfo\nmember: $member\nmemberContact: $memberContact") is GroupUpdated -> withUser(user, json.encodeToString(toGroup)) is GroupLinkDataUpdated -> withUser(user, "groupInfo: $groupInfo\ngroupLink: $groupLink\ngroupRelays: $groupRelays\nrelaysChanged: $relaysChanged") + is GroupRelayUpdated -> withUser(user, "groupInfo: $groupInfo\nmember: $member\ngroupRelay: $groupRelay") is GroupLinkCreated -> withUser(user, "groupInfo: $groupInfo\ngroupLink: $groupLink") is CRGroupLink -> withUser(user, "groupInfo: $groupInfo\ngroupLink: $groupLink") is GroupLinkDeleted -> withUser(user, json.encodeToString(groupInfo)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ChatRelayView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ChatRelayView.kt index 4611f62991..646ca816e7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ChatRelayView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ChatRelayView.kt @@ -62,7 +62,6 @@ fun validRelayAddress(address: String): Boolean { (parsedMd.first().format as Format.SimplexLink).linkType == SimplexLinkType.relay } -// TODO [relays] TBC matching relay to operator by domain (relay address can be hosted on operator server) fun addChatRelay( relay: UserChatRelay, userServers: MutableState>, diff --git a/bots/api/EVENTS.md b/bots/api/EVENTS.md index bc99f68c4b..947c60586a 100644 --- a/bots/api/EVENTS.md +++ b/bots/api/EVENTS.md @@ -39,6 +39,7 @@ This file is generated automatically. - [MemberBlockedForAll](#memberblockedforall) - [GroupMemberUpdated](#groupmemberupdated) - [GroupLinkDataUpdated](#grouplinkdataupdated) + - [GroupRelayUpdated](#grouprelayupdated) [File events](#file-events) - Main events @@ -468,6 +469,20 @@ Group link data updated. --- +### GroupRelayUpdated + +Group relay member updated. + +**Record type**: +- type: "groupRelayUpdated" +- user: [User](./TYPES.md#user) +- groupInfo: [GroupInfo](./TYPES.md#groupinfo) +- member: [GroupMember](./TYPES.md#groupmember) +- groupRelay: [GroupRelay](./TYPES.md#grouprelay) + +--- + + ## File events Bots that send or receive files may process these events to track delivery status and to process completion. diff --git a/bots/src/API/Docs/Events.hs b/bots/src/API/Docs/Events.hs index 0d1998aa28..c8446e9e67 100644 --- a/bots/src/API/Docs/Events.hs +++ b/bots/src/API/Docs/Events.hs @@ -98,7 +98,8 @@ chatEventsDocsData = ("CEvtMemberAcceptedByOther", "Another group owner, admin or moderator accepted member to the group after review (\"knocking\")."), ("CEvtMemberBlockedForAll", "Another member blocked for all members."), ("CEvtGroupMemberUpdated", "Another group member profile updated."), - ("CEvtGroupLinkDataUpdated", "Group link data updated.") + ("CEvtGroupLinkDataUpdated", "Group link data updated."), + ("CEvtGroupRelayUpdated", "Group relay member updated.") ] ), ( "File events", diff --git a/cabal.project b/cabal.project index 5b75f49edb..1223e93ea6 100644 --- a/cabal.project +++ b/cabal.project @@ -21,7 +21,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 782cacfb3cc57883465eecc0b9b30662daf2b81f + tag: fc1272d6144f13fa7e92b3dd6202e35c52cd75e2 source-repository-package type: git diff --git a/packages/simplex-chat-client/types/typescript/src/events.ts b/packages/simplex-chat-client/types/typescript/src/events.ts index b0b9b31fb4..cc19305913 100644 --- a/packages/simplex-chat-client/types/typescript/src/events.ts +++ b/packages/simplex-chat-client/types/typescript/src/events.ts @@ -30,6 +30,7 @@ export type ChatEvent = | CEvt.MemberBlockedForAll | CEvt.GroupMemberUpdated | CEvt.GroupLinkDataUpdated + | CEvt.GroupRelayUpdated | CEvt.RcvFileDescrReady | CEvt.RcvFileComplete | CEvt.SndFileCompleteXFTP @@ -82,6 +83,7 @@ export namespace CEvt { | "memberBlockedForAll" | "groupMemberUpdated" | "groupLinkDataUpdated" + | "groupRelayUpdated" | "rcvFileDescrReady" | "rcvFileComplete" | "sndFileCompleteXFTP" @@ -314,6 +316,14 @@ export namespace CEvt { relaysChanged: boolean } + export interface GroupRelayUpdated extends Interface { + type: "groupRelayUpdated" + user: T.User + groupInfo: T.GroupInfo + member: T.GroupMember + groupRelay: T.GroupRelay + } + export interface RcvFileDescrReady extends Interface { type: "rcvFileDescrReady" user: T.User diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 27a906fb52..978185a416 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."782cacfb3cc57883465eecc0b9b30662daf2b81f" = "0ck5hcj2yn540l11bbhn0ghgk49mfyqy0c4xqkbw1kk0fd9hhxs6"; + "https://github.com/simplex-chat/simplexmq.git"."fc1272d6144f13fa7e92b3dd6202e35c52cd75e2" = "072bgw44fsq2r9vbrphr2xkydcksfvxna7m6ln5rfc60dbkajisl"; "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/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index f1205e3dfb..d82ffb255f 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -858,6 +858,7 @@ data ChatEvent | CEvtReceivedGroupInvitation {user :: User, groupInfo :: GroupInfo, contact :: Contact, fromMemberRole :: GroupMemberRole, memberRole :: GroupMemberRole} | CEvtUserJoinedGroup {user :: User, groupInfo :: GroupInfo, hostMember :: GroupMember} | CEvtGroupLinkDataUpdated {user :: User, groupInfo :: GroupInfo, groupLink :: GroupLink, groupRelays :: [GroupRelay], relaysChanged :: Bool} + | CEvtGroupRelayUpdated {user :: User, groupInfo :: GroupInfo, member :: GroupMember, groupRelay :: GroupRelay} | CEvtJoinedGroupMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember} -- there is the same command response | CEvtJoinedGroupMemberConnecting {user :: User, groupInfo :: GroupInfo, hostMember :: GroupMember, member :: GroupMember} | CEvtMemberAcceptedByOther {user :: User, groupInfo :: GroupInfo, acceptingMember :: GroupMember, member :: GroupMember} diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index c9d85b9f45..0143844c6c 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -1841,7 +1841,7 @@ processChatCommand vr nm = \case let userData = contactShortLinkData (userProfileDirect user incognitoProfile Nothing True) Nothing userLinkData = UserInvLinkData userData -- TODO [certs rcv] - (connId, (ccLink, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId user) True False SCMInvitation (Just userLinkData) Nothing IKPQOn subMode + (connId, (_, ccLink, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId user) Nothing True False SCMInvitation (Just userLinkData) Nothing IKPQOn subMode ccLink' <- shortenCreatedLink ccLink -- TODO PQ pass minVersion from the current range conn <- withFastStore' $ \db -> createDirectConnection db user connId ccLink' Nothing ConnNew incognitoProfile subMode initialChatVersion PQSupportOn @@ -1883,7 +1883,7 @@ processChatCommand vr nm = \case | short = Just $ UserInvLinkData $ contactShortLinkData (userProfileDirect newUser Nothing Nothing True) Nothing | otherwise = Nothing -- TODO [certs rcv] - (agConnId, (ccLink, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId newUser) True False SCMInvitation userLinkData_ Nothing IKPQOn subMode + (agConnId, (_, ccLink, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId newUser) Nothing True False SCMInvitation userLinkData_ Nothing IKPQOn subMode ccLink' <- shortenCreatedLink ccLink conn' <- withFastStore' $ \db -> do deleteConnectionRecord db user connId @@ -2067,18 +2067,19 @@ processChatCommand vr nm = \case _ -> False connectToRelay gInfo' relayLink = do gVar <- asks random - -- TODO [relays] member: set relay profile before/during connection - -- TODO - on fetching relay link data? (-> relay should add profile to relay link) - -- TODO - or update upon connection, as in regular prepared groups - -- TODO (current logic mimics insertHost_ from createPreparedGroup) -- Save relayLink to re-use relay member record on retry (check by relayLink) relayMember <- withFastStore $ \db -> getCreateRelayForMember db vr gVar user gInfo' relayLink r <- tryAllErrors $ do - (fd, _cData) <- getShortLinkConnReq nm user relayLink + (fd@FixedLinkData {rootKey = relayKey, linkEntityId}, cData) <- getShortLinkConnReq nm user relayLink + relayLinkData_ <- liftIO $ decodeLinkUserData cData + case (relayLinkData_, linkEntityId) of + (Just RelayShortLinkData {relayProfile = p}, Just entityId) -> + withFastStore $ \db -> updateRelayMemberData db user relayMember (MemberId entityId) (MemberKey relayKey) p + _ -> throwChatError $ CEException "relay link: no relay link data or entity id" let cReq = linkConnReq fd relayLinkToConnect = CCLink cReq (Just relayLink) void $ connectViaContact user (Just $ PCEGroup gInfo' relayMember) incognito relayLinkToConnect Nothing Nothing - -- Re-read member to get updated activeConn + -- Re-read member to get updated activeConn and updated data (from updateRelayMemberData) relayMember' <- withFastStore $ \db -> getGroupMember db vr user groupId (groupMemberId' relayMember) pure (relayLink, relayMember', r) retryRelayConnectionAsync gInfo' relayLink relayMember@GroupMember {activeConn} = do @@ -2086,7 +2087,7 @@ processChatCommand vr nm = \case deleteAgentConnectionAsync $ aConnId conn withStore' $ \db -> deleteConnectionRecord db user (dbConnId conn) subMode <- chatReadVar subscriptionMode - newConnIds <- getAgentConnShortLinkAsync user relayLink + newConnIds <- getAgentConnShortLinkAsync user CFGetRelayDataJoin Nothing relayLink withStore' $ \db -> createRelayMemberConnectionAsync db user gInfo' relayMember relayLink newConnIds subMode GroupInfo {preparedGroup = Just PreparedGroup {connLinkToConnect, welcomeSharedMsgId, requestSharedMsgId}} -> do hostMember <- withFastStore $ \db -> getHostMember db vr user groupId @@ -2161,13 +2162,11 @@ processChatCommand vr nm = \case Left e -> throwError $ ChatErrorStore e Right _ -> throwError $ ChatErrorStore SEDuplicateContactLink subMode <- chatReadVar subscriptionMode - -- TODO [relays] relay: address creation - -- TODO - add relay key, identity to link data - -- TODO - validate short link is created (returned by agent) + -- TODO [relays] relay: add relay profile, identity, key to link data? let userData = contactShortLinkData (userProfileDirect user Nothing Nothing True) Nothing userLinkData = UserContactLinkData UserContactData {direct = True, owners = [], relays = [], userData} -- TODO [certs rcv] - (connId, (ccLink, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId user) True True SCMContact (Just userLinkData) Nothing IKPQOn subMode + (connId, (_, ccLink, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId user) Nothing True True SCMContact (Just userLinkData) Nothing IKPQOn subMode ccLink' <- shortenCreatedLink ccLink let ccLink'' = if isTrue userChatRelay then createdRelayLink ccLink' else ccLink' withFastStore $ \db -> createUserContactLink db user connId ccLink'' subMode @@ -2433,7 +2432,7 @@ processChatCommand vr nm = \case gVar <- asks random subMode <- chatReadVar subscriptionMode -- TODO [certs rcv] - (agentConnId, (CCLink cReq _, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId user) True False SCMInvitation Nothing Nothing IKPQOff subMode + (agentConnId, (_, CCLink cReq _, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId user) Nothing True False SCMInvitation Nothing Nothing IKPQOff subMode member <- withFastStore $ \db -> createNewContactMember db gVar user gInfo contact memRole agentConnId cReq subMode sendInvitation member cReq pure $ CRSentGroupInvitation user gInfo contact member @@ -2857,7 +2856,7 @@ processChatCommand vr nm = \case userLinkData = UserContactLinkData UserContactData {direct = True, owners = [], relays = [], userData} crClientData = encodeJSON $ CRDataGroup groupLinkId -- TODO [certs rcv] - (connId, (ccLink, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId user) True True SCMContact (Just userLinkData) (Just crClientData) IKPQOff subMode + (connId, (_, ccLink, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId user) Nothing True True SCMContact (Just userLinkData) (Just crClientData) IKPQOff subMode ccLink' <- createdGroupLink <$> shortenCreatedLink ccLink gVar <- asks random gLink <- withFastStore $ \db -> createGroupLink db gVar user gInfo connId ccLink' groupLinkId mRole subMode @@ -2898,7 +2897,7 @@ processChatCommand vr nm = \case subMode <- chatReadVar subscriptionMode -- TODO PQ should negotitate contact connection with PQSupportOn? -- TODO [certs rcv] - (connId, (CCLink cReq _, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId user) True False SCMInvitation Nothing Nothing IKPQOff subMode + (connId, (_, CCLink cReq _, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId user) Nothing True False SCMInvitation Nothing Nothing IKPQOff subMode -- [incognito] reuse membership incognito profile ct <- withFastStore' $ \db -> createMemberContact db user connId cReq g m mConn subMode void $ createChatItem user (CDDirectSnd ct) False CIChatBanner Nothing (Just epochStart) diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index de8511bce9..ff43e70a09 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -1032,7 +1032,7 @@ acceptBusinessJoinRequestAsync -- TODO [short links] get updated business chat group and member? (currently not used) pure (gInfo, clientMember) -acceptRelayJoinRequestAsync :: User -> Int64 -> GroupInfo -> GroupMember -> InvitationId -> VersionRangeChat -> ShortLinkContact -> MemberKey -> CM (GroupInfo, GroupMember) +acceptRelayJoinRequestAsync :: User -> Int64 -> GroupInfo -> GroupMember -> InvitationId -> VersionRangeChat -> ShortLinkContact -> CM (GroupInfo, GroupMember) acceptRelayJoinRequestAsync user uclId @@ -1040,9 +1040,8 @@ acceptRelayJoinRequestAsync _ownerMember@GroupMember {groupMemberId} cReqInvId cReqChatVRange - relayLink - memberKey = do - let msg = XGrpRelayAcpt relayLink memberKey + relayLink = do + let msg = XGrpRelayAcpt relayLink subMode <- chatReadVar subscriptionMode vr <- chatVersionRange let chatV = vr `peerConnChatVersion` cReqChatVRange @@ -1323,7 +1322,7 @@ updatePublicGroupData user gInfo pure gInfo' | otherwise = pure gInfo --- TODO [relays] owner: set owners on updating link data +-- TODO [relays] owner: set owners on updating link data (multi-owner) groupLinkData :: GroupInfo -> GroupLink -> [GroupRelay] -> (UserConnLinkData 'CMContact, CRClientData) groupLinkData gInfo@GroupInfo {groupProfile, groupSummary = GroupSummary {publicMemberCount}} GroupLink {groupLinkId} groupRelays = let direct = not $ useRelays' gInfo @@ -2448,11 +2447,11 @@ setAgentConnShortLinkAsync user conn@Connection {connId} userLinkData crClientDa cmdId <- withStore' $ \db -> createCommand db user (Just connId) CFSetShortLink withAgent $ \a -> setConnShortLinkAsync a (aCorrId cmdId) (aConnId conn) userLinkData crClientData_ -getAgentConnShortLinkAsync :: User -> ShortLinkContact -> CM (CommandId, ConnId) -getAgentConnShortLinkAsync user shortLink = do +getAgentConnShortLinkAsync :: User -> CommandFunction -> Maybe Connection -> ShortLinkContact -> CM (CommandId, ConnId) +getAgentConnShortLinkAsync user cmdFunc conn_ shortLink = do shortLink' <- restoreShortLink' shortLink - cmdId <- withStore' $ \db -> createCommand db user Nothing CFGetShortLink - connId <- withAgent $ \a -> getConnShortLinkAsync a (aUserId user) (aCorrId cmdId) shortLink' + cmdId <- withStore' $ \db -> createCommand db user (dbConnId <$> conn_) cmdFunc + connId <- withAgent $ \a -> getConnShortLinkAsync a (aUserId user) (aCorrId cmdId) (aConnId <$> conn_) shortLink' pure (cmdId, connId) agentXFTPDeleteRcvFile :: RcvFileId -> FileTransferId -> CM () diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index 650b554857..fce2fb0137 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -735,13 +735,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- [async agent commands] no continuation needed, but command should be asynchronous for stability allowAgentConnectionAsync user conn' confId XOk | otherwise -> messageError "x.grp.acpt: memberId is different from expected" - XGrpRelayAcpt relayLink memberKey + XGrpRelayAcpt relayLink | memberRole' membership == GROwner && isRelay m -> do - withStore $ \db -> do - relay <- getGroupRelayByGMId db (groupMemberId' m) - liftIO $ updateGroupMemberStatus db userId m GSMemAccepted - void $ liftIO $ setRelayLinkAccepted db relay relayLink memberKey - allowAgentConnectionAsync user conn' confId XOk + withStore' $ \db -> setRelayLinkConfId db m confId relayLink + void $ getAgentConnShortLinkAsync user CFGetRelayDataAccept (Just conn') relayLink | otherwise -> messageError "x.grp.relay.acpt: only owner can add relay" _ -> messageError "CONF from invited member must have x.grp.acpt" GCHostMember -> @@ -866,11 +863,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = when (groupFeatureAllowed SGFHistory gInfo'' && not memberIsCustomer) $ sendHistory user gInfo'' m' where sendXGrpLinkMem gInfo'' = do - let GroupInfo {membership = membership'} = gInfo'' - incognitoProfile = ExistingIncognito <$> incognitoMembershipProfile gInfo'' + let incognitoProfile = ExistingIncognito <$> incognitoMembershipProfile gInfo'' profileToSend = userProfileInGroup user gInfo (fromIncognitoProfile <$> incognitoProfile) - memberKey = MemberKey <$> memberPubKey membership' - void $ sendDirectMemberMessage conn (XGrpLinkMem profileToSend memberKey) groupId + void $ sendDirectMemberMessage conn (XGrpLinkMem profileToSend) groupId _ -> do unless (memberPending m) $ withStore' $ \db -> updateGroupMemberStatus db userId m GSMemConnected notifyMemberConnected gInfo m Nothing @@ -968,7 +963,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = XFileCancel sharedMsgId -> xFileCancelGroup gInfo' (Just m'') sharedMsgId XFileAcptInv sharedMsgId fileConnReq_ fName -> Nothing <$ xFileAcptInvGroup gInfo' m'' sharedMsgId fileConnReq_ fName XInfo p -> fmap ctx <$> xInfoMember gInfo' m'' p msg brokerTs - XGrpLinkMem p memberKey -> Nothing <$ xGrpLinkMem gInfo' m'' conn' p memberKey + XGrpLinkMem p -> Nothing <$ xGrpLinkMem gInfo' m'' conn' p XGrpLinkAcpt acceptance role memberId -> Nothing <$ xGrpLinkAcpt gInfo' m'' acceptance role memberId msg brokerTs XGrpMemNew memInfo msgScope -> fmap ctx <$> xGrpMemNew gInfo' m'' memInfo msgScope msg brokerTs XGrpMemIntro memInfo memRestrictions_ -> Nothing <$ xGrpMemIntro gInfo' m'' memInfo memRestrictions_ @@ -1088,30 +1083,53 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = forM_ mc_ $ \mc -> do connReq_ <- withStore' $ \db -> getBusinessContactRequest db user groupId sendGroupAutoReply mc connReq_ - LDATA FixedLinkData {linkConnReq = cReq} _cData -> - -- [async agent commands] CFGetConnShortLink continuation - join relay connection with resolved link + LDATA FixedLinkData {linkConnReq = cReq, rootKey = relayKey, linkEntityId} cData -> withCompletedCommand conn agentMsg $ \CommandData {cmdFunction} -> case cmdFunction of - CFGetShortLink -> case cReq of - CRContactUri crData@ConnReqUriData {crClientData} -> do - let pqSup = PQSupportOff - lift (withAgent' $ \a -> connRequestPQSupport a pqSup cReq) >>= \case - Nothing -> throwChatError CEInvalidConnReq - Just (agentV, _) -> do - let chatV = agentToChatVersion agentV - groupLinkId = crClientData >>= decodeJSON >>= \(CRDataGroup gli) -> Just gli - cReqHash = contactCReqHash $ CRContactUri crData {crScheme = SSSimplex} - -- Update connection with data derived from cReq, now available after getConnShortLinkAsync - withStore' $ \db -> updateConnLinkData db user conn cReq cReqHash groupLinkId chatV pqSup - let GroupMember {memberId = membershipMemId} = membership - incognitoProfile = fromLocalProfile <$> incognitoMembershipProfile gInfo - profileToSend = userProfileInGroup user gInfo incognitoProfile - memberPubKey <- case groupKeys gInfo of - Just GroupKeys {memberPrivKey} -> pure $ C.publicKey memberPrivKey - Nothing -> throwChatError $ CEInternalError "no group keys for channel membership" - dm <- encodeConnInfo $ XMember profileToSend membershipMemId (MemberKey memberPubKey) - subMode <- chatReadVar subscriptionMode - void $ joinAgentConnectionAsync user (Just conn) True cReq dm subMode + CFGetRelayDataJoin -> do + -- Update relay member with key, memberId and profile from link + relayLinkData_ <- liftIO $ decodeLinkUserData cData + case (relayLinkData_, linkEntityId) of + (Just RelayShortLinkData {relayProfile = p}, Just entityId) -> + withStore $ \db -> updateRelayMemberData db user m (MemberId entityId) (MemberKey relayKey) p + _ -> throwChatError $ CEException "relay link: no relay link data or entity id" + case cReq of + CRContactUri crData@ConnReqUriData {crClientData} -> do + let pqSup = PQSupportOff + lift (withAgent' $ \a -> connRequestPQSupport a pqSup cReq) >>= \case + Nothing -> throwChatError CEInvalidConnReq + Just (agentV, _) -> do + let chatV = agentToChatVersion agentV + groupLinkId = crClientData >>= decodeJSON >>= \(CRDataGroup gli) -> Just gli + cReqHash = contactCReqHash $ CRContactUri crData {crScheme = SSSimplex} + -- Update connection with data derived from cReq, now available after getConnShortLinkAsync + withStore' $ \db -> updateConnLinkData db user conn cReq cReqHash groupLinkId chatV pqSup + let GroupMember {memberId = membershipMemId} = membership + incognitoProfile = fromLocalProfile <$> incognitoMembershipProfile gInfo + profileToSend = userProfileInGroup user gInfo incognitoProfile + memberPubKey <- case groupKeys gInfo of + Just GroupKeys {memberPrivKey} -> pure $ C.publicKey memberPrivKey + Nothing -> throwChatError $ CEInternalError "no group keys for channel membership" + dm <- encodeConnInfo $ XMember profileToSend membershipMemId (MemberKey memberPubKey) + subMode <- chatReadVar subscriptionMode + void $ joinAgentConnectionAsync user (Just conn) True cReq dm subMode + CFGetRelayDataAccept -> do + let GroupMember {memberId = MemberId expectedMemberId} = m + if linkEntityId == Just expectedMemberId + then do + relayProfile <- liftIO (decodeLinkUserData cData) >>= \case + Just RelayShortLinkData {relayProfile = p} -> pure p + Nothing -> throwChatError $ CEException "relay link: no relay link data" + (confId, m', relay) <- withStore $ \db -> do + confId <- getRelayConfId db m + liftIO $ updateGroupMemberStatus db userId m GSMemAccepted + (m', relay) <- setRelayLinkAccepted db vr user m (MemberKey relayKey) relayProfile + pure (confId, m', relay) + allowAgentConnectionAsync user conn confId XOk + toView $ CEvtGroupRelayUpdated user gInfo m' relay + else + -- TODO [relays] owner: TBC failed RelayStatus? + messageError "relay link: relay member ID mismatch" _ -> throwChatError $ CECommandError "unexpected cmdFunction" QCONT -> do continued <- continueSending connEntity conn @@ -2402,13 +2420,13 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = void $ processMemberProfileUpdate gInfo m p' (Just (msg, brokerTs)) pure $ memberEventDeliveryScope m - xGrpLinkMem :: GroupInfo -> GroupMember -> Connection -> Profile -> Maybe MemberKey -> CM () - xGrpLinkMem gInfo@GroupInfo {membership, businessChat} m@GroupMember {groupMemberId, memberCategory} Connection {viaGroupLink} p' memberKey_ = do + xGrpLinkMem :: GroupInfo -> GroupMember -> Connection -> Profile -> CM () + xGrpLinkMem gInfo@GroupInfo {membership, businessChat} m@GroupMember {groupMemberId, memberCategory} Connection {viaGroupLink} p' = do xGrpLinkMemReceived <- withStore $ \db -> getXGrpLinkMemReceived db groupMemberId if (viaGroupLink || isJust businessChat) && isNothing (memberContactId m) && memberCategory == GCHostMember && not xGrpLinkMemReceived then do m' <- processMemberProfileUpdate gInfo m p' Nothing - withStore' $ \db -> setXGrpLinkMemReceived db groupMemberId True memberKey_ + withStore' $ \db -> setXGrpLinkMemReceived db groupMemberId True let connectedIncognito = memberIncognito membership probeMatchingMemberContact m' connectedIncognito else messageError "x.grp.link.mem error: invalid group link host profile update" @@ -3596,56 +3614,52 @@ runRelayRequestWorker a Worker {doWork} = do -- Check if relay link already exists (recovery case) case groupLink_ of Right GroupLink {connLinkContact = CCLink _ sLnk_} -> - case (sLnk_, memberPubKey $ membership gInfo) of - (Just sLnk, Just k) -> acceptOwnerConnection rrd gInfo sLnk (MemberKey k) - (Nothing, _) -> throwChatError $ CEException "processRelayRequest: relay link doesn't have short link" - (_, Nothing) -> throwChatError $ CEException "processRelayRequest: no member key" + case sLnk_ of + Just sLnk -> acceptOwnerConnection rrd gInfo sLnk + Nothing -> throwChatError $ CEException "processRelayRequest: relay link doesn't have short link" Left _ -> do - (gInfo', sLnk, memberKey) <- getLinkDataCreateRelayLink rrd gInfo - acceptOwnerConnection rrd gInfo' sLnk memberKey + (gInfo', sLnk) <- getLinkDataCreateRelayLink rrd gInfo + acceptOwnerConnection rrd gInfo' sLnk where - getLinkDataCreateRelayLink :: RelayRequestData -> GroupInfo -> CM (GroupInfo, ShortLinkContact, MemberKey) + getLinkDataCreateRelayLink :: RelayRequestData -> GroupInfo -> CM (GroupInfo, ShortLinkContact) getLinkDataCreateRelayLink RelayRequestData {reqGroupLink} gInfo = do (FixedLinkData {linkEntityId, rootKey}, cData@(ContactLinkData _ UserContactData {owners})) <- getShortLinkConnReq NRMBackground user reqGroupLink liftIO (decodeLinkUserData cData) >>= \case Nothing -> throwChatError $ CEException "getLinkDataCreateRelayLink: no group link data" Just GroupShortLinkData {groupProfile = gp} -> do validateGroupProfile gp - gVar <- asks random - (_, memberPrivKey) <- liftIO $ atomically $ C.generateKeyPair gVar + ((_, memberPrivKey), sLnk) <- createRelayLink gInfo gInfo' <- withStore $ \db -> do void $ updateGroupProfile db user gInfo gp updateRelayGroupKeys db user gInfo linkEntityId rootKey memberPrivKey owners getGroupInfo db vr user groupId - sLnk <- createRelayLink gInfo' - pure (gInfo', sLnk, MemberKey $ C.publicKey memberPrivKey) + pure (gInfo', sLnk) where validateGroupProfile :: GroupProfile -> CM () validateGroupProfile _groupProfile = do -- TODO [relays] relay: validate group profile, verify owner's signature pure () - createRelayLink :: GroupInfo -> CM ShortLinkContact - createRelayLink gi@GroupInfo {groupProfile} = do - -- TODO [relays] relay: set relay link data - -- TODO - link data: relay key for group, relay identity (profile, certificate, relay identity key) - -- TODO - starting role should be communicated in protocol from owner to relays + createRelayLink :: GroupInfo -> CM (C.KeyPairEd25519, ShortLinkContact) + createRelayLink gi = do + let GroupInfo {membership = relayMem} = gi + GroupMember {memberId = relayMemId, memberProfile = relayLP} = relayMem + MemberId relayMemberIdBS = relayMemId + userData = encodeShortLinkData $ RelayShortLinkData {relayProfile = fromLocalProfile relayLP} + userLinkData = UserContactLinkData UserContactData {direct = True, owners = [], relays = [], userData} groupLinkId <- GroupLinkId <$> drgRandomBytes 16 subMode <- chatReadVar subscriptionMode + -- TODO [relays] starting role should be communicated in protocol from owner to relays subRole <- asks $ channelSubscriberRole . config - let userData = encodeShortLinkData $ GroupShortLinkData {groupProfile, publicGroupData = Nothing} - userLinkData = UserContactLinkData UserContactData {direct = True, owners = [], relays = [], userData} - crClientData = encodeJSON $ CRDataGroup groupLinkId - (connId, (ccLink, _serviceId)) <- withAgent $ \a' -> createConnection a' NRMBackground (aUserId user) True True SCMContact (Just userLinkData) (Just crClientData) CR.IKPQOff subMode + let crClientData = encodeJSON $ CRDataGroup groupLinkId + (connId, (Just sigKeys, ccLink, _serviceId)) <- withAgent $ \a' -> createConnection a' NRMBackground (aUserId user) (Just relayMemberIdBS) True True SCMContact (Just userLinkData) (Just crClientData) CR.IKPQOff subMode ccLink' <- createdGroupLink <$> shortenCreatedLink ccLink sLnk <- case toShortLinkContact ccLink' of Just sl -> pure sl Nothing -> throwChatError $ CEException "failed to create relay link: no short link" gVar <- asks random void $ withFastStore $ \db -> createGroupLink db gVar user gi connId ccLink' groupLinkId subRole subMode - pure sLnk - acceptOwnerConnection :: RelayRequestData -> GroupInfo -> ShortLinkContact -> MemberKey -> CM () - acceptOwnerConnection RelayRequestData {relayInvId, reqChatVRange} gi relayLink memberKey = do + pure (sigKeys, sLnk) + acceptOwnerConnection :: RelayRequestData -> GroupInfo -> ShortLinkContact -> CM () + acceptOwnerConnection RelayRequestData {relayInvId, reqChatVRange} gi relayLink = do ownerMember <- withStore $ \db -> getHostMember db vr user groupId - void $ acceptRelayJoinRequestAsync user uclId gi ownerMember relayInvId reqChatVRange relayLink memberKey - -- TODO [relays] relay: group invite accepted event, chat item (?) - pure () + void $ acceptRelayJoinRequestAsync user uclId gi ownerMember relayInvId reqChatVRange relayLink diff --git a/src/Simplex/Chat/Protocol.hs b/src/Simplex/Chat/Protocol.hs index 2e921425b9..d012bbb0a5 100644 --- a/src/Simplex/Chat/Protocol.hs +++ b/src/Simplex/Chat/Protocol.hs @@ -432,10 +432,10 @@ data ChatMsgEvent (e :: MsgEncoding) where XGrpAcpt :: MemberId -> ChatMsgEvent 'Json XGrpLinkInv :: GroupLinkInvitation -> ChatMsgEvent 'Json XGrpLinkReject :: GroupLinkRejection -> ChatMsgEvent 'Json - XGrpLinkMem :: Profile -> Maybe MemberKey -> ChatMsgEvent 'Json + XGrpLinkMem :: Profile -> ChatMsgEvent 'Json XGrpLinkAcpt :: GroupAcceptance -> GroupMemberRole -> MemberId -> ChatMsgEvent 'Json XGrpRelayInv :: GroupRelayInvitation -> ChatMsgEvent 'Json - XGrpRelayAcpt :: ShortLinkContact -> MemberKey -> ChatMsgEvent 'Json + XGrpRelayAcpt :: ShortLinkContact -> ChatMsgEvent 'Json XGrpMemNew :: MemberInfo -> Maybe MsgScope -> ChatMsgEvent 'Json XGrpMemIntro :: MemberInfo -> Maybe MemberRestrictions -> ChatMsgEvent 'Json XGrpMemInv :: MemberId -> IntroInvitation -> ChatMsgEvent 'Json @@ -1126,10 +1126,10 @@ toCMEventTag msg = case msg of XGrpAcpt _ -> XGrpAcpt_ XGrpLinkInv _ -> XGrpLinkInv_ XGrpLinkReject _ -> XGrpLinkReject_ - XGrpLinkMem _ _ -> XGrpLinkMem_ + XGrpLinkMem _ -> XGrpLinkMem_ XGrpLinkAcpt {} -> XGrpLinkAcpt_ XGrpRelayInv _ -> XGrpRelayInv_ - XGrpRelayAcpt _ _ -> XGrpRelayAcpt_ + XGrpRelayAcpt _ -> XGrpRelayAcpt_ XGrpMemNew {} -> XGrpMemNew_ XGrpMemIntro _ _ -> XGrpMemIntro_ XGrpMemInv _ _ -> XGrpMemInv_ @@ -1278,10 +1278,10 @@ appJsonToCM AppMessageJson {v, msgId, event, params} = do XGrpAcpt_ -> XGrpAcpt <$> p "memberId" XGrpLinkInv_ -> XGrpLinkInv <$> p "groupLinkInvitation" XGrpLinkReject_ -> XGrpLinkReject <$> p "groupLinkRejection" - XGrpLinkMem_ -> XGrpLinkMem <$> p "profile" <*> opt "memberKey" + XGrpLinkMem_ -> XGrpLinkMem <$> p "profile" XGrpLinkAcpt_ -> XGrpLinkAcpt <$> p "acceptance" <*> p "role" <*> p "memberId" XGrpRelayInv_ -> XGrpRelayInv <$> p "groupRelayInvitation" - XGrpRelayAcpt_ -> XGrpRelayAcpt <$> p "relayLink" <*> p "memberKey" + XGrpRelayAcpt_ -> XGrpRelayAcpt <$> p "relayLink" XGrpMemNew_ -> XGrpMemNew <$> p "memberInfo" <*> opt "scope" XGrpMemIntro_ -> XGrpMemIntro <$> p "memberInfo" <*> opt "memberRestrictions" XGrpMemInv_ -> XGrpMemInv <$> p "memberId" <*> p "memberIntro" @@ -1345,10 +1345,10 @@ chatToAppMessage chatMsg@ChatMessage {chatVRange, msgId, chatMsgEvent} = case en XGrpAcpt memId -> o ["memberId" .= memId] XGrpLinkInv groupLinkInv -> o ["groupLinkInvitation" .= groupLinkInv] XGrpLinkReject groupLinkRjct -> o ["groupLinkRejection" .= groupLinkRjct] - XGrpLinkMem profile memberKey -> o $ ("memberKey" .=? memberKey) ["profile" .= profile] + XGrpLinkMem profile -> o ["profile" .= profile] XGrpLinkAcpt acceptance role memberId -> o ["acceptance" .= acceptance, "role" .= role, "memberId" .= memberId] XGrpRelayInv groupRelayInv -> o ["groupRelayInvitation" .= groupRelayInv] - XGrpRelayAcpt relayLink memberKey -> o ["relayLink" .= relayLink, "memberKey" .= memberKey] + XGrpRelayAcpt relayLink -> o ["relayLink" .= relayLink] XGrpMemNew memInfo scope -> o $ ("scope" .=? scope) ["memberInfo" .= memInfo] XGrpMemIntro memInfo memRestrictions -> o $ ("memberRestrictions" .=? memRestrictions) ["memberInfo" .= memInfo] XGrpMemInv memId memIntro -> o ["memberId" .= memId, "memberIntro" .= memIntro] @@ -1436,3 +1436,10 @@ $(JQ.deriveJSON defaultJSON ''PublicGroupData) $(JQ.deriveJSON defaultJSON ''GroupShortLinkData) +data RelayShortLinkData = RelayShortLinkData + { relayProfile :: Profile + } + deriving (Show) + +$(JQ.deriveJSON defaultJSON ''RelayShortLinkData) + diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 8698b1718c..12f726c524 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -88,6 +88,9 @@ module Simplex.Chat.Store.Groups updateRelayStatus, updateRelayStatusFromTo, setRelayLinkAccepted, + setRelayLinkConfId, + getRelayConfId, + updateRelayMemberData, setGroupInProgressDone, createRelayRequestGroup, updateRelayOwnStatusFromTo, @@ -197,7 +200,7 @@ import Simplex.Chat.Types.MemberRelations (IntroductionDirection (..), MemberRel import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared import Simplex.Chat.Types.UITheme -import Simplex.Messaging.Agent.Protocol (ConnId, CreatedConnLink (..), InvitationId, OwnerAuth (..), UserId) +import Simplex.Messaging.Agent.Protocol (ConfirmationId, ConnId, CreatedConnLink (..), InvitationId, OwnerAuth (..), UserId) import Simplex.Messaging.Agent.Store.AgentStore (firstRow, fromOnlyBI, maybeFirstRow) import Simplex.Messaging.Agent.Store.DB (Binary (..), BoolInt (..)) import Simplex.Messaging.Agent.Store.Entity (DBEntityId) @@ -1417,26 +1420,74 @@ updateRelayStatus_ db relayId relayStatus = do currentTs <- getCurrentTime DB.execute db "UPDATE group_relays SET relay_status = ?, updated_at = ? WHERE group_relay_id = ?" (relayStatus, currentTs, relayId) -setRelayLinkAccepted :: DB.Connection -> GroupRelay -> ShortLinkContact -> MemberKey -> IO GroupRelay -setRelayLinkAccepted db relay@GroupRelay {groupRelayId, groupMemberId} relayLink (MemberKey k) = do +setRelayLinkAccepted :: DB.Connection -> VersionRangeChat -> User -> GroupMember -> MemberKey -> Profile -> ExceptT StoreError IO (GroupMember, GroupRelay) +setRelayLinkAccepted db vr user m (MemberKey relayKey) profile = do + let gmId = groupMemberId' m + currentTs <- liftIO getCurrentTime + liftIO $ DB.execute + db + [sql| + UPDATE group_relays + SET relay_status = ?, updated_at = ? + WHERE group_member_id = ? + |] + (RSAccepted, currentTs, gmId) + liftIO $ DB.execute + db + [sql| + UPDATE group_members + SET member_pub_key = ?, updated_at = ? + WHERE group_member_id = ? + |] + (relayKey, currentTs, gmId) + void $ updateMemberProfile db user m profile + (,) <$> getGroupMemberById db vr user gmId <*> getGroupRelayByGMId db gmId + +setRelayLinkConfId :: DB.Connection -> GroupMember -> ConfirmationId -> ShortLinkContact -> IO () +setRelayLinkConfId db m confId relayLink = do currentTs <- getCurrentTime DB.execute db [sql| UPDATE group_relays - SET relay_link = ?, relay_status = ?, updated_at = ? - WHERE group_relay_id = ? + SET conf_id = ?, relay_link = ?, updated_at = ? + WHERE group_member_id = ? |] - (relayLink, RSAccepted, currentTs, groupRelayId) + (confId, relayLink, currentTs, groupMemberId' m) DB.execute db [sql| UPDATE group_members - SET relay_link = ?, member_pub_key = ?, updated_at = ? + SET relay_link = ?, updated_at = ? WHERE group_member_id = ? |] - (relayLink, k, currentTs, groupMemberId) - pure relay {relayStatus = RSAccepted, relayLink = Just relayLink} + (relayLink, currentTs, groupMemberId' m) + +getRelayConfId :: DB.Connection -> GroupMember -> ExceptT StoreError IO ConfirmationId +getRelayConfId db m = + ExceptT . firstRow fromOnly (SEGroupRelayNotFoundByMemberId $ groupMemberId' m) $ + DB.query + db + [sql| + SELECT conf_id + FROM group_relays + WHERE group_member_id = ? AND conf_id IS NOT NULL + |] + (Only (groupMemberId' m)) + +updateRelayMemberData :: DB.Connection -> User -> GroupMember -> MemberId -> MemberKey -> Profile -> ExceptT StoreError IO () +updateRelayMemberData db user m memberId (MemberKey relayKey) profile = do + currentTs <- liftIO getCurrentTime + liftIO $ + DB.execute + db + [sql| + UPDATE group_members + SET member_id = ?, member_pub_key = ?, updated_at = ? + WHERE group_member_id = ? + |] + (memberId, relayKey, currentTs, groupMemberId' m) + void $ updateMemberProfile db user m profile setGroupInProgressDone :: DB.Connection -> GroupInfo -> IO () setGroupInProgressDone db GroupInfo {groupId} = do @@ -2851,14 +2902,13 @@ getXGrpLinkMemReceived db mId = ExceptT . firstRow fromOnlyBI (SEGroupMemberNotFound mId) $ DB.query db "SELECT xgrplinkmem_received FROM group_members WHERE group_member_id = ?" (Only mId) -setXGrpLinkMemReceived :: DB.Connection -> GroupMemberId -> Bool -> Maybe MemberKey -> IO () -setXGrpLinkMemReceived db mId xGrpLinkMemReceived memberKey_ = do +setXGrpLinkMemReceived :: DB.Connection -> GroupMemberId -> Bool -> IO () +setXGrpLinkMemReceived db mId xGrpLinkMemReceived = do currentTs <- getCurrentTime - let k = (\(MemberKey k') -> k') <$> memberKey_ DB.execute db - "UPDATE group_members SET xgrplinkmem_received = ?, member_pub_key = ?, updated_at = ? WHERE group_member_id = ?" - (BI xGrpLinkMemReceived, k, currentTs, mId) + "UPDATE group_members SET xgrplinkmem_received = ?, updated_at = ? WHERE group_member_id = ?" + (BI xGrpLinkMemReceived, currentTs, mId) createNewUnknownGroupMember :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> MemberId -> Text -> GroupMemberRole -> ExceptT StoreError IO GroupMember createNewUnknownGroupMember db vr user@User {userId, userContactId} GroupInfo {groupId} memberId memberName unknownMemberRole = do diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/M20260222_chat_relays.hs b/src/Simplex/Chat/Store/Postgres/Migrations/M20260222_chat_relays.hs index a504e7f1d8..1d39060d07 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/M20260222_chat_relays.hs +++ b/src/Simplex/Chat/Store/Postgres/Migrations/M20260222_chat_relays.hs @@ -54,6 +54,7 @@ CREATE TABLE group_relays( chat_relay_id BIGINT NOT NULL REFERENCES chat_relays ON DELETE CASCADE, relay_status TEXT NOT NULL, relay_link BYTEA, + conf_id BYTEA, created_at TEXT NOT NULL DEFAULT (now()), updated_at TEXT NOT NULL DEFAULT (now()) ); diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20250813_delivery_tasks.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20250813_delivery_tasks.hs index 35f2006cef..dce9574fe3 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/M20250813_delivery_tasks.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20250813_delivery_tasks.hs @@ -5,10 +5,6 @@ module Simplex.Chat.Store.SQLite.Migrations.M20250813_delivery_tasks where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) --- TODO [relays] add later in new migration for MemberProfileUpdate delivery jobs: --- TODO - ALTER TABLE group_members ADD COLUMN last_profile_delivery_ts TEXT; --- TODO - ALTER TABLE group_members ADD COLUMN join_ts TEXT; - -- How columns correspond to types: -- both tables: diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20260222_chat_relays.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20260222_chat_relays.hs index 766921858f..250cf25bd6 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/M20260222_chat_relays.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20260222_chat_relays.hs @@ -67,6 +67,7 @@ CREATE TABLE group_relays( chat_relay_id INTEGER NOT NULL REFERENCES chat_relays ON DELETE CASCADE, relay_status TEXT NOT NULL, relay_link BLOB, + conf_id BLOB, created_at TEXT NOT NULL DEFAULT(datetime('now')), updated_at TEXT NOT NULL DEFAULT(datetime('now')) ) STRICT; diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index 99c1b1899b..ec90caca8a 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -3472,6 +3472,14 @@ Query: Plan: SEARCH commands USING INTEGER PRIMARY KEY (rowid=?) +Query: + SELECT conf_id + FROM group_relays + WHERE group_member_id = ? AND conf_id IS NOT NULL + +Plan: +SEARCH group_relays USING INDEX idx_group_relays_group_member_id (group_member_id=?) + Query: SELECT connection_id, agent_conn_id, conn_level, via_contact, via_user_contact_link, via_group_link, group_link_id, xcontact_id, custom_user_profile_id, conn_status, conn_type, contact_conn_initiated, local_alias, contact_id, group_member_id, user_contact_link_id, @@ -3911,6 +3919,14 @@ Query: Plan: SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?) +Query: + UPDATE group_members + SET member_id = ?, member_pub_key = ?, updated_at = ? + WHERE group_member_id = ? + +Plan: +SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?) + Query: UPDATE group_members SET member_relations_vector = ? @@ -4879,6 +4895,14 @@ Query: Plan: SEARCH contacts USING INTEGER PRIMARY KEY (rowid=?) +Query: + UPDATE group_members + SET member_pub_key = ?, updated_at = ? + WHERE group_member_id = ? + +Plan: +SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?) + Query: UPDATE group_members SET member_relations_vector = ?, updated_at = ? @@ -4921,7 +4945,7 @@ SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?) Query: UPDATE group_members - SET relay_link = ?, member_pub_key = ?, updated_at = ? + SET relay_link = ?, updated_at = ? WHERE group_member_id = ? Plan: @@ -4964,11 +4988,19 @@ SEARCH groups USING INTEGER PRIMARY KEY (rowid=?) Query: UPDATE group_relays - SET relay_link = ?, relay_status = ?, updated_at = ? - WHERE group_relay_id = ? + SET conf_id = ?, relay_link = ?, updated_at = ? + WHERE group_member_id = ? Plan: -SEARCH group_relays USING INTEGER PRIMARY KEY (rowid=?) +SEARCH group_relays USING INDEX idx_group_relays_group_member_id (group_member_id=?) + +Query: + UPDATE group_relays + SET relay_status = ?, updated_at = ? + WHERE group_member_id = ? + +Plan: +SEARCH group_relays USING INDEX idx_group_relays_group_member_id (group_member_id=?) Query: UPDATE group_snd_item_statuses @@ -6959,7 +6991,7 @@ Query: UPDATE group_members SET support_chat_ts = ? WHERE group_member_id = ? Plan: SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?) -Query: UPDATE group_members SET xgrplinkmem_received = ?, member_pub_key = ?, updated_at = ? WHERE group_member_id = ? +Query: UPDATE group_members SET xgrplinkmem_received = ?, updated_at = ? WHERE group_member_id = ? Plan: SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?) diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql index 9782ce056d..4ab9a442f0 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql @@ -765,6 +765,7 @@ CREATE TABLE group_relays( chat_relay_id INTEGER NOT NULL REFERENCES chat_relays ON DELETE CASCADE, relay_status TEXT NOT NULL, relay_link BLOB, + conf_id BLOB, created_at TEXT NOT NULL DEFAULT(datetime('now')), updated_at TEXT NOT NULL DEFAULT(datetime('now')) ) STRICT; diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 32d1c726bf..05aaf5744f 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -121,7 +121,6 @@ instance ToField AgentUserId where toField (AgentUserId uId) = toField uId aUserId :: User -> UserId aUserId User {agentUserId = AgentUserId uId} = uId --- TODO [relays] filter out chat relay users where necessary (e.g. loading list of users for UI) data User = User { userId :: UserId, agentUserId :: AgentUserId, @@ -1856,7 +1855,8 @@ data CommandFunction | CFAckMessage -- not used | CFDeleteConn -- not used | CFSetShortLink - | CFGetShortLink + | CFGetRelayDataJoin + | CFGetRelayDataAccept deriving (Eq, Show) instance FromField CommandFunction where fromField = fromTextField_ textDecode @@ -1875,7 +1875,8 @@ instance TextEncoding CommandFunction where "ack_message" -> Just CFAckMessage "delete_conn" -> Just CFDeleteConn "set_short_link" -> Just CFSetShortLink - "get_short_link" -> Just CFGetShortLink + "get_relay_data_join" -> Just CFGetRelayDataJoin + "get_relay_data_accept" -> Just CFGetRelayDataAccept _ -> Nothing textEncode = \case CFCreateConnGrpMemInv -> "create_conn" @@ -1888,7 +1889,8 @@ instance TextEncoding CommandFunction where CFAckMessage -> "ack_message" CFDeleteConn -> "delete_conn" CFSetShortLink -> "set_short_link" - CFGetShortLink -> "get_short_link" + CFGetRelayDataJoin -> "get_relay_data_join" + CFGetRelayDataAccept -> "get_relay_data_accept" commandExpectedResponse :: CommandFunction -> AEvtTag commandExpectedResponse = \case @@ -1902,7 +1904,8 @@ commandExpectedResponse = \case CFAckMessage -> t OK_ CFDeleteConn -> t OK_ CFSetShortLink -> t LINK_ - CFGetShortLink -> t LDATA_ + CFGetRelayDataJoin -> t LDATA_ + CFGetRelayDataAccept -> t LDATA_ where t = AEvtTag SAEConn diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 22d136c59e..3657ce5d05 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -474,6 +474,7 @@ chatEventToView hu ChatConfig {logLevel, showReactions, showReceipts, testView} CEvtGroupLinkDataUpdated u g groupLink relays relaysChanged | relaysChanged -> ttyUser u $ viewGroupLinkRelaysUpdated g groupLink relays | otherwise -> [] + CEvtGroupRelayUpdated {} -> [] CEvtJoinedGroupMember u g m -> ttyUser u $ viewJoinedGroupMember g m CEvtHostConnected p h -> [plain $ "connected to " <> viewHostEvent p h] CEvtHostDisconnected p h -> [plain $ "disconnected from " <> viewHostEvent p h] @@ -2062,7 +2063,7 @@ viewConnectionPlan ChatConfig {logLevel, testView} _connLink = \case GLPOk groupSLinkInfo_ groupSLinkData -> let direct = maybe True (\(GroupShortLinkInfo {direct = d}) -> d) groupSLinkInfo_ in [grpLink $ if direct then "ok to connect directly" else "ok to connect via relays"] - <> [viewJSON groupSLinkData] -- | testView] -- TODO [relays] disable link data output in cli (uncomment testView) + <> [viewJSON groupSLinkData | testView] GLPOwnLink g -> [grpLink "own link for group " <> ttyGroup' g] GLPConnectingConfirmReconnect -> [grpLink "connecting, allowed to reconnect"] GLPConnectingProhibit Nothing -> [grpLink "connecting"] diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index 8039576255..ca2581eb01 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -210,7 +210,7 @@ testCfg = shortLinkPresetServers = ["smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=@localhost:7001"], testView = True, tbqSize = 16, - channelSubscriberRole = GRMember, + channelSubscriberRole = GRMember, -- starting role is GRMember to test members sending messages confirmMigrations = MCYesUp }