mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-09-01 20:08:57 +00:00
core, ios: track "short link data set" state, interact with address accordingly (#5978)
This commit is contained in:
@@ -837,8 +837,8 @@ enum ChatResponse1: Decodable, ChatAPIResult {
|
||||
case let .groupAliasUpdated(u, toGroup): return withUser(u, String(describing: toGroup))
|
||||
case let .connectionAliasUpdated(u, toConnection): return withUser(u, String(describing: toConnection))
|
||||
case let .contactPrefsUpdated(u, fromContact, toContact): return withUser(u, "fromContact: \(String(describing: fromContact))\ntoContact: \(String(describing: toContact))")
|
||||
case let .userContactLink(u, contactLink): return withUser(u, contactLink.responseDetails)
|
||||
case let .userContactLinkUpdated(u, contactLink): return withUser(u, contactLink.responseDetails)
|
||||
case let .userContactLink(u, contactLink): return withUser(u, String(describing: contactLink))
|
||||
case let .userContactLinkUpdated(u, contactLink): return withUser(u, String(describing: contactLink))
|
||||
case let .userContactLinkCreated(u, connLink): return withUser(u, String(describing: connLink))
|
||||
case .userContactLinkDeleted: return noDetails
|
||||
case let .acceptingContactRequest(u, contact): return withUser(u, String(describing: contact))
|
||||
@@ -888,8 +888,8 @@ enum ChatResponse2: Decodable, ChatAPIResult {
|
||||
case membersRoleUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], toRole: GroupMemberRole)
|
||||
case membersBlockedForAllUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], blocked: Bool)
|
||||
case groupUpdated(user: UserRef, toGroup: GroupInfo)
|
||||
case groupLinkCreated(user: UserRef, groupInfo: GroupInfo, connLinkContact: CreatedConnLink, memberRole: GroupMemberRole)
|
||||
case groupLink(user: UserRef, groupInfo: GroupInfo, connLinkContact: CreatedConnLink, memberRole: GroupMemberRole)
|
||||
case groupLinkCreated(user: UserRef, groupInfo: GroupInfo, groupLink: GroupLink)
|
||||
case groupLink(user: UserRef, groupInfo: GroupInfo, groupLink: GroupLink)
|
||||
case groupLinkDeleted(user: UserRef, groupInfo: GroupInfo)
|
||||
case newMemberContact(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember)
|
||||
case newMemberContactSentInv(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember)
|
||||
@@ -984,8 +984,8 @@ enum ChatResponse2: Decodable, ChatAPIResult {
|
||||
case let .membersRoleUser(u, groupInfo, members, toRole): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\ntoRole: \(toRole)")
|
||||
case let .membersBlockedForAllUser(u, groupInfo, members, blocked): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(members)\nblocked: \(blocked)")
|
||||
case let .groupUpdated(u, toGroup): return withUser(u, String(describing: toGroup))
|
||||
case let .groupLinkCreated(u, groupInfo, connLinkContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnLinkContact: \(connLinkContact)\nmemberRole: \(memberRole)")
|
||||
case let .groupLink(u, groupInfo, connLinkContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnLinkContact: \(connLinkContact)\nmemberRole: \(memberRole)")
|
||||
case let .groupLinkCreated(u, groupInfo, groupLink): return withUser(u, "groupInfo: \(groupInfo)\ngroupLink: \(groupLink)")
|
||||
case let .groupLink(u, groupInfo, groupLink): return withUser(u, "groupInfo: \(groupInfo)\ngroupLink: \(groupLink)")
|
||||
case let .groupLinkDeleted(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
||||
case let .newMemberContact(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)")
|
||||
case let .newMemberContactSentInv(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)")
|
||||
@@ -1395,11 +1395,8 @@ struct UserMsgReceiptSettings: Codable {
|
||||
|
||||
struct UserContactLink: Decodable, Hashable {
|
||||
var connLinkContact: CreatedConnLink
|
||||
var shortLinkDataSet: Bool
|
||||
var autoAccept: AutoAccept?
|
||||
|
||||
var responseDetails: String {
|
||||
"connLinkContact: \(connLinkContact)\nautoAccept: \(AutoAccept.cmdString(autoAccept))"
|
||||
}
|
||||
}
|
||||
|
||||
struct AutoAccept: Codable, Hashable {
|
||||
@@ -1420,6 +1417,14 @@ struct AutoAccept: Codable, Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
struct GroupLink: Decodable, Hashable {
|
||||
var userContactLinkId: Int64
|
||||
var connLinkContact: CreatedConnLink
|
||||
var shortLinkDataSet: Bool
|
||||
var groupLinkId: String
|
||||
var acceptMemberRole: GroupMemberRole
|
||||
}
|
||||
|
||||
struct DeviceToken: Decodable {
|
||||
var pushProvider: PushProvider
|
||||
var token: String
|
||||
|
||||
@@ -404,6 +404,10 @@ final class ChatModel: ObservableObject {
|
||||
remoteCtrlSession?.active ?? false
|
||||
}
|
||||
|
||||
var addressShortLinkDataSet: Bool {
|
||||
userAddress?.shortLinkDataSet ?? true
|
||||
}
|
||||
|
||||
func getUser(_ userId: Int64) -> User? {
|
||||
currentUser?.userId == userId
|
||||
? currentUser
|
||||
@@ -1216,9 +1220,7 @@ struct ShowingInvitation {
|
||||
var connChatUsed: Bool
|
||||
}
|
||||
|
||||
// TODO [short links] incognito if !userAddress.shortLinkDataSet; or remove if no access to chat model here
|
||||
struct NTFContactRequest {
|
||||
var incognito: Bool
|
||||
var chatId: String
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import UIKit
|
||||
import SimpleXChat
|
||||
|
||||
let ntfActionAcceptContact = "NTF_ACT_ACCEPT_CONTACT"
|
||||
let ntfActionAcceptContactIncognito = "NTF_ACT_ACCEPT_CONTACT_INCOGNITO"
|
||||
let ntfActionAcceptCall = "NTF_ACT_ACCEPT_CALL"
|
||||
let ntfActionRejectCall = "NTF_ACT_REJECT_CALL"
|
||||
|
||||
@@ -59,13 +58,12 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
|
||||
logger.debug("NtfManager.processNotificationResponse changeActiveUser")
|
||||
changeActiveUser(userId, viewPwd: nil)
|
||||
}
|
||||
if content.categoryIdentifier == ntfCategoryContactRequest && (action == ntfActionAcceptContact || action == ntfActionAcceptContactIncognito),
|
||||
if content.categoryIdentifier == ntfCategoryContactRequest && action == ntfActionAcceptContact,
|
||||
let chatId = content.userInfo["chatId"] as? String {
|
||||
let incognito = action == ntfActionAcceptContactIncognito
|
||||
if case let .contactRequest(contactRequest) = chatModel.getChat(chatId)?.chatInfo {
|
||||
Task { await acceptContactRequest(incognito: incognito, contactRequestId: contactRequest.apiId) }
|
||||
Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequest.apiId) }
|
||||
} else {
|
||||
chatModel.ntfContactRequest = NTFContactRequest(incognito: incognito, chatId: chatId)
|
||||
chatModel.ntfContactRequest = NTFContactRequest(chatId: chatId)
|
||||
}
|
||||
} else if let (chatId, ntfAction) = ntfCallAction(content, action) {
|
||||
if let invitation = chatModel.callInvitations.removeValue(forKey: chatId) {
|
||||
@@ -161,12 +159,6 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
|
||||
identifier: ntfActionAcceptContact,
|
||||
title: NSLocalizedString("Accept", comment: "accept contact request via notification"),
|
||||
options: .foreground
|
||||
),
|
||||
// TODO [short links] if !userAddress.shortLinkDataSet; or remove if no access to chat model here
|
||||
UNNotificationAction(
|
||||
identifier: ntfActionAcceptContactIncognito,
|
||||
title: NSLocalizedString("Accept incognito", comment: "accept contact request via notification"),
|
||||
options: .foreground
|
||||
)
|
||||
],
|
||||
intentIdentifiers: [],
|
||||
|
||||
@@ -1758,16 +1758,16 @@ func apiUpdateGroup(_ groupId: Int64, _ groupProfile: GroupProfile) async throws
|
||||
throw r.unexpected
|
||||
}
|
||||
|
||||
func apiCreateGroupLink(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> (CreatedConnLink, GroupMemberRole) {
|
||||
func apiCreateGroupLink(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> GroupLink {
|
||||
let short = UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS)
|
||||
let r: ChatResponse2 = try await chatSendCmd(.apiCreateGroupLink(groupId: groupId, memberRole: memberRole, short: short))
|
||||
if case let .groupLinkCreated(_, _, connLink, memberRole) = r { return (connLink, memberRole) }
|
||||
if case let .groupLinkCreated(_, _, groupLink) = r { return groupLink }
|
||||
throw r.unexpected
|
||||
}
|
||||
|
||||
func apiGroupLinkMemberRole(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> (CreatedConnLink, GroupMemberRole) {
|
||||
func apiGroupLinkMemberRole(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> GroupLink {
|
||||
let r: ChatResponse2 = try await chatSendCmd(.apiGroupLinkMemberRole(groupId: groupId, memberRole: memberRole))
|
||||
if case let .groupLink(_, _, connLink, memberRole) = r { return (connLink, memberRole) }
|
||||
if case let .groupLink(_, _, groupLink) = r { return groupLink }
|
||||
throw r.unexpected
|
||||
}
|
||||
|
||||
@@ -1777,20 +1777,20 @@ func apiDeleteGroupLink(_ groupId: Int64) async throws {
|
||||
throw r.unexpected
|
||||
}
|
||||
|
||||
func apiGetGroupLink(_ groupId: Int64) throws -> (CreatedConnLink, GroupMemberRole)? {
|
||||
func apiGetGroupLink(_ groupId: Int64) throws -> GroupLink? {
|
||||
let r: APIResult<ChatResponse2> = chatApiSendCmdSync(.apiGetGroupLink(groupId: groupId))
|
||||
switch r {
|
||||
case let .result(.groupLink(_, _, connLink, memberRole)):
|
||||
return (connLink, memberRole)
|
||||
case let .result(.groupLink(_, _, groupLink)):
|
||||
return groupLink
|
||||
case .error(.errorStore(storeError: .groupLinkNotFound)):
|
||||
return nil
|
||||
default: throw r.unexpected
|
||||
}
|
||||
}
|
||||
|
||||
func apiAddGroupShortLink(_ groupId: Int64) async throws -> (CreatedConnLink, GroupMemberRole) {
|
||||
func apiAddGroupShortLink(_ groupId: Int64) async throws -> GroupLink {
|
||||
let r: ChatResponse2 = try await chatSendCmd(.apiAddGroupShortLink(groupId: groupId))
|
||||
if case let .groupLink(_, _, connLink, memberRole) = r { return (connLink, memberRole) }
|
||||
if case let .groupLink(_, _, groupLink) = r { return groupLink }
|
||||
throw r.unexpected
|
||||
}
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ struct SimpleXApp: App {
|
||||
if let ncr = chatModel.ntfContactRequest {
|
||||
await MainActor.run { chatModel.ntfContactRequest = nil }
|
||||
if case let .contactRequest(contactRequest) = chatModel.getChat(ncr.chatId)?.chatInfo {
|
||||
Task { await acceptContactRequest(incognito: ncr.incognito, contactRequestId: contactRequest.apiId) }
|
||||
Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequest.apiId) }
|
||||
}
|
||||
}
|
||||
} catch let error {
|
||||
|
||||
@@ -47,7 +47,7 @@ struct ChatView: View {
|
||||
@State private var selectedMember: GMember? = nil
|
||||
// opening GroupLinkView on link button (incognito)
|
||||
@State private var showGroupLinkSheet: Bool = false
|
||||
@State private var groupLink: CreatedConnLink?
|
||||
@State private var groupLink: GroupLink?
|
||||
@State private var groupLinkMemberRole: GroupMemberRole = .member
|
||||
@State private var forwardedChatItems: [ChatItem] = []
|
||||
@State private var selectedChatItems: Set<Int64>? = nil
|
||||
@@ -1040,8 +1040,9 @@ struct ChatView: View {
|
||||
if case let .group(gInfo, _) = chat.chatInfo {
|
||||
Task {
|
||||
do {
|
||||
if let link = try apiGetGroupLink(gInfo.groupId) {
|
||||
(groupLink, groupLinkMemberRole) = link
|
||||
if let gLink = try apiGetGroupLink(gInfo.groupId) {
|
||||
groupLink = gLink
|
||||
groupLinkMemberRole = gLink.acceptMemberRole
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("ChatView apiGetGroupLink: \(responseError(error))")
|
||||
|
||||
@@ -56,29 +56,36 @@ func showRejectRequestAlert(_ contactRequestId: Int64) {
|
||||
}
|
||||
|
||||
func showAcceptRequestAlert(_ contactRequestId: Int64) {
|
||||
showAlert(
|
||||
NSLocalizedString("Accept contact request", comment: "alert title"),
|
||||
actions: {[
|
||||
var actions: [UIAlertAction] = []
|
||||
actions.append(
|
||||
UIAlertAction(
|
||||
title: NSLocalizedString("Accept", comment: "alert action"),
|
||||
style: .default,
|
||||
handler: { _ in
|
||||
Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequestId) }
|
||||
}
|
||||
)
|
||||
)
|
||||
if !ChatModel.shared.addressShortLinkDataSet {
|
||||
actions.append(
|
||||
UIAlertAction(
|
||||
title: NSLocalizedString("Accept", comment: "alert action"),
|
||||
title: NSLocalizedString("Accept incognito", comment: "alert action"),
|
||||
style: .default,
|
||||
handler: { _ in
|
||||
Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequestId) }
|
||||
}
|
||||
),
|
||||
// TODO [short links] if !userAddress.shortLinkDataSet; check other places
|
||||
// UIAlertAction(
|
||||
// title: NSLocalizedString("Accept incognito", comment: "alert action"),
|
||||
// style: .default,
|
||||
// handler: { _ in
|
||||
// Task { await acceptContactRequest(incognito: true, contactRequest: contactRequest) }
|
||||
// }
|
||||
// ),
|
||||
UIAlertAction(
|
||||
title: NSLocalizedString("Cancel", comment: "alert action"),
|
||||
style: .default
|
||||
Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequestId) }
|
||||
}
|
||||
)
|
||||
]}
|
||||
)
|
||||
}
|
||||
actions.append(
|
||||
UIAlertAction(
|
||||
title: NSLocalizedString("Cancel", comment: "alert action"),
|
||||
style: .default
|
||||
)
|
||||
)
|
||||
showAlert(
|
||||
NSLocalizedString("Accept contact request", comment: "alert title"),
|
||||
actions: { actions }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ struct GroupChatInfoView: View {
|
||||
@State var localAlias: String
|
||||
@FocusState private var aliasTextFieldFocused: Bool
|
||||
@State private var alert: GroupChatInfoViewAlert? = nil
|
||||
@State private var groupLink: CreatedConnLink?
|
||||
@State private var groupLink: GroupLink?
|
||||
@State private var groupLinkMemberRole: GroupMemberRole = .member
|
||||
@State private var groupLinkNavLinkActive: Bool = false
|
||||
@State private var addMembersNavLinkActive: Bool = false
|
||||
@@ -221,8 +221,9 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
sendReceipts = SendReceipts.fromBool(groupInfo.chatSettings.sendRcpts, userDefault: sendReceiptsUserDefault)
|
||||
do {
|
||||
if let link = try apiGetGroupLink(groupInfo.groupId) {
|
||||
(groupLink, groupLinkMemberRole) = link
|
||||
if let gLink = try apiGetGroupLink(groupInfo.groupId) {
|
||||
groupLink = gLink
|
||||
groupLinkMemberRole = gLink.acceptMemberRole
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("GroupChatInfoView apiGetGroupLink: \(responseError(error))")
|
||||
|
||||
@@ -12,7 +12,7 @@ import SimpleXChat
|
||||
struct GroupLinkView: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
var groupId: Int64
|
||||
@Binding var groupLink: CreatedConnLink?
|
||||
@Binding var groupLink: GroupLink?
|
||||
@Binding var groupLinkMemberRole: GroupMemberRole
|
||||
var showTitle: Bool = false
|
||||
var creatingGroup: Bool = false
|
||||
@@ -78,19 +78,27 @@ struct GroupLinkView: View {
|
||||
}
|
||||
}
|
||||
.frame(height: 36)
|
||||
SimpleXCreatedLinkQRCode(link: groupLink, short: $showShortLink)
|
||||
.id("simplex-qrcode-view-for-\(groupLink.simplexChatUri(short: showShortLink))")
|
||||
SimpleXCreatedLinkQRCode(link: groupLink.connLinkContact, short: $showShortLink)
|
||||
.id("simplex-qrcode-view-for-\(groupLink.connLinkContact.simplexChatUri(short: showShortLink))")
|
||||
Button {
|
||||
showShareSheet(items: [groupLink.simplexChatUri(short: showShortLink)])
|
||||
showShareSheet(items: [groupLink.connLinkContact.simplexChatUri(short: showShortLink)])
|
||||
} label: {
|
||||
Label("Share link", systemImage: "square.and.arrow.up")
|
||||
}
|
||||
|
||||
if (groupLink.connShortLink == nil && UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS)) {
|
||||
Button {
|
||||
addShortLink()
|
||||
} label: {
|
||||
Label("Add short link", systemImage: "plus")
|
||||
if UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS) {
|
||||
if groupLink.connLinkContact.connShortLink == nil {
|
||||
Button {
|
||||
addShortLink()
|
||||
} label: {
|
||||
Label("Add short link", systemImage: "plus")
|
||||
}
|
||||
} else if !groupLink.shortLinkDataSet {
|
||||
Button {
|
||||
addShortLink()
|
||||
} label: {
|
||||
Label("Share group profile via link", systemImage: "plus")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,8 +114,8 @@ struct GroupLinkView: View {
|
||||
.disabled(creatingLink)
|
||||
}
|
||||
} header: {
|
||||
if let groupLink, groupLink.connShortLink != nil {
|
||||
ToggleShortLinkHeader(text: Text(""), link: groupLink, short: $showShortLink)
|
||||
if let groupLink, groupLink.connLinkContact.connShortLink != nil {
|
||||
ToggleShortLinkHeader(text: Text(""), link: groupLink.connLinkContact, short: $showShortLink)
|
||||
}
|
||||
}
|
||||
.alert(item: $alert) { alert in
|
||||
@@ -134,7 +142,7 @@ struct GroupLinkView: View {
|
||||
.onChange(of: groupLinkMemberRole) { _ in
|
||||
Task {
|
||||
do {
|
||||
_ = try await apiGroupLinkMemberRole(groupId, memberRole: groupLinkMemberRole)
|
||||
groupLink = try await apiGroupLinkMemberRole(groupId, memberRole: groupLinkMemberRole)
|
||||
} catch let error {
|
||||
let a = getErrorAlert(error, "Error updating group link")
|
||||
alert = .error(title: a.title, error: a.message)
|
||||
@@ -155,10 +163,10 @@ struct GroupLinkView: View {
|
||||
Task {
|
||||
do {
|
||||
creatingLink = true
|
||||
let link = try await apiCreateGroupLink(groupId)
|
||||
let gLink = try await apiCreateGroupLink(groupId)
|
||||
await MainActor.run {
|
||||
creatingLink = false
|
||||
(groupLink, groupLinkMemberRole) = link
|
||||
groupLink = gLink
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("GroupLinkView apiCreateGroupLink: \(responseError(error))")
|
||||
@@ -175,10 +183,10 @@ struct GroupLinkView: View {
|
||||
Task {
|
||||
do {
|
||||
creatingLink = true
|
||||
let link = try await apiAddGroupShortLink(groupId)
|
||||
let gLink = try await apiAddGroupShortLink(groupId)
|
||||
await MainActor.run {
|
||||
creatingLink = false
|
||||
(groupLink, groupLinkMemberRole) = link
|
||||
groupLink = gLink
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("apiAddGroupShortLink: \(responseError(error))")
|
||||
@@ -194,8 +202,14 @@ struct GroupLinkView: View {
|
||||
|
||||
struct GroupLinkView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
@State var groupLink: CreatedConnLink? = CreatedConnLink(connFullLink: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", connShortLink: nil)
|
||||
@State var noGroupLink: CreatedConnLink? = nil
|
||||
@State var groupLink: GroupLink? = GroupLink(
|
||||
userContactLinkId: 1,
|
||||
connLinkContact: CreatedConnLink(connFullLink: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", connShortLink: nil),
|
||||
shortLinkDataSet: false,
|
||||
groupLinkId: "abc",
|
||||
acceptMemberRole: .member
|
||||
)
|
||||
@State var noGroupLink: GroupLink? = nil
|
||||
|
||||
return Group {
|
||||
GroupLinkView(groupId: 1, groupLink: $groupLink, groupLinkMemberRole: Binding.constant(.member))
|
||||
|
||||
@@ -136,12 +136,14 @@ struct ChatListNavLink: View {
|
||||
Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequestId) }
|
||||
} label: { SwipeLabel(NSLocalizedString("Accept", comment: "swipe action"), systemImage: "checkmark", inverted: oneHandUI) }
|
||||
.tint(theme.colors.primary)
|
||||
Button {
|
||||
Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequestId) }
|
||||
} label: {
|
||||
SwipeLabel(NSLocalizedString("Accept incognito", comment: "swipe action"), systemImage: "theatermasks.fill", inverted: oneHandUI)
|
||||
if !ChatModel.shared.addressShortLinkDataSet {
|
||||
Button {
|
||||
Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequestId) }
|
||||
} label: {
|
||||
SwipeLabel(NSLocalizedString("Accept incognito", comment: "swipe action"), systemImage: "theatermasks.fill", inverted: oneHandUI)
|
||||
}
|
||||
.tint(.indigo)
|
||||
}
|
||||
.tint(.indigo)
|
||||
Button {
|
||||
AlertManager.shared.showAlert(rejectContactRequestAlert(contactRequestId))
|
||||
} label: {
|
||||
@@ -461,12 +463,14 @@ struct ChatListNavLink: View {
|
||||
Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequest.apiId) }
|
||||
} label: { SwipeLabel(NSLocalizedString("Accept", comment: "swipe action"), systemImage: "checkmark", inverted: oneHandUI) }
|
||||
.tint(theme.colors.primary)
|
||||
Button {
|
||||
Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequest.apiId) }
|
||||
} label: {
|
||||
SwipeLabel(NSLocalizedString("Accept incognito", comment: "swipe action"), systemImage: "theatermasks.fill", inverted: oneHandUI)
|
||||
if !ChatModel.shared.addressShortLinkDataSet {
|
||||
Button {
|
||||
Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequest.apiId) }
|
||||
} label: {
|
||||
SwipeLabel(NSLocalizedString("Accept incognito", comment: "swipe action"), systemImage: "theatermasks.fill", inverted: oneHandUI)
|
||||
}
|
||||
.tint(.indigo)
|
||||
}
|
||||
.tint(.indigo)
|
||||
Button {
|
||||
AlertManager.shared.showAlert(rejectContactRequestAlert(contactRequest.apiId))
|
||||
} label: {
|
||||
@@ -478,7 +482,9 @@ struct ChatListNavLink: View {
|
||||
.onTapGesture { showContactRequestDialog = true }
|
||||
.confirmationDialog("Accept connection request?", isPresented: $showContactRequestDialog, titleVisibility: .visible) {
|
||||
Button("Accept") { Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequest.apiId) } }
|
||||
Button("Accept incognito") { Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequest.apiId) } }
|
||||
if !ChatModel.shared.addressShortLinkDataSet {
|
||||
Button("Accept incognito") { Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequest.apiId) } }
|
||||
}
|
||||
Button("Reject (sender NOT notified)", role: .destructive) { Task { await rejectContactRequest(contactRequest.apiId) } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,6 +343,8 @@ struct ChatPreviewView: View {
|
||||
if contact.activeConn == nil && contact.profile.contactLink != nil && contact.active {
|
||||
chatPreviewInfoText("Tap to Connect")
|
||||
.foregroundColor(theme.colors.primary)
|
||||
} else if contact.nextAcceptContactRequest {
|
||||
chatPreviewInfoText("swipe or open to connect")
|
||||
} else if contact.sendMsgToConnect {
|
||||
chatPreviewInfoText("send to connect")
|
||||
} else if !contact.sndReady && contact.activeConn != nil && contact.active {
|
||||
|
||||
@@ -94,12 +94,14 @@ struct ContactListNavLink: View {
|
||||
Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequestId) }
|
||||
} label: { Label("Accept", systemImage: "checkmark") }
|
||||
.tint(theme.colors.primary)
|
||||
Button {
|
||||
Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequestId) }
|
||||
} label: {
|
||||
Label("Accept incognito", systemImage: "theatermasks")
|
||||
if !ChatModel.shared.addressShortLinkDataSet {
|
||||
Button {
|
||||
Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequestId) }
|
||||
} label: {
|
||||
Label("Accept incognito", systemImage: "theatermasks")
|
||||
}
|
||||
.tint(.indigo)
|
||||
}
|
||||
.tint(.indigo)
|
||||
Button {
|
||||
alert = SomeAlert(alert: rejectContactRequestAlert(contactRequestId), id: "rejectContactRequestAlert")
|
||||
} label: {
|
||||
@@ -258,12 +260,14 @@ struct ContactListNavLink: View {
|
||||
Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequest.apiId) }
|
||||
} label: { Label("Accept", systemImage: "checkmark") }
|
||||
.tint(theme.colors.primary)
|
||||
Button {
|
||||
Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequest.apiId) }
|
||||
} label: {
|
||||
Label("Accept incognito", systemImage: "theatermasks")
|
||||
if !ChatModel.shared.addressShortLinkDataSet {
|
||||
Button {
|
||||
Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequest.apiId) }
|
||||
} label: {
|
||||
Label("Accept incognito", systemImage: "theatermasks")
|
||||
}
|
||||
.tint(.indigo)
|
||||
}
|
||||
.tint(.indigo)
|
||||
Button {
|
||||
alert = SomeAlert(alert: rejectContactRequestAlert(contactRequest.apiId), id: "rejectContactRequestAlert")
|
||||
} label: {
|
||||
@@ -273,7 +277,9 @@ struct ContactListNavLink: View {
|
||||
}
|
||||
.confirmationDialog("Accept connection request?", isPresented: $showContactRequestDialog, titleVisibility: .visible) {
|
||||
Button("Accept") { Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequest.apiId) } }
|
||||
Button("Accept incognito") { Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequest.apiId) } }
|
||||
if !ChatModel.shared.addressShortLinkDataSet {
|
||||
Button("Accept incognito") { Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequest.apiId) } }
|
||||
}
|
||||
Button("Reject (sender NOT notified)", role: .destructive) { Task { await rejectContactRequest(contactRequest.apiId) } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ struct AddGroupView: View {
|
||||
@State private var showTakePhoto = false
|
||||
@State private var chosenImage: UIImage? = nil
|
||||
@State private var showInvalidNameAlert = false
|
||||
@State private var groupLink: CreatedConnLink?
|
||||
@State private var groupLink: GroupLink?
|
||||
@State private var groupLinkMemberRole: GroupMemberRole = .member
|
||||
|
||||
var body: some View {
|
||||
|
||||
@@ -77,9 +77,10 @@ struct CreateSimpleXAddress: View {
|
||||
progressIndicator = true
|
||||
Task {
|
||||
do {
|
||||
let connLinkContact = try await apiCreateUserAddress(short: false)
|
||||
let short = false
|
||||
let connLinkContact = try await apiCreateUserAddress(short: short)
|
||||
DispatchQueue.main.async {
|
||||
m.userAddress = UserContactLink(connLinkContact: connLinkContact)
|
||||
m.userAddress = UserContactLink(connLinkContact: connLinkContact, shortLinkDataSet: short)
|
||||
}
|
||||
await MainActor.run { progressIndicator = false }
|
||||
} catch let error {
|
||||
|
||||
@@ -153,8 +153,12 @@ struct UserAddressView: View {
|
||||
}
|
||||
}
|
||||
addressSettingsButton(userAddress)
|
||||
if (userAddress.connLinkContact.connShortLink == nil && UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS)) {
|
||||
addShortLinkButton()
|
||||
if UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS) {
|
||||
if userAddress.connLinkContact.connShortLink == nil {
|
||||
addShortLinkButton()
|
||||
} else if !userAddress.shortLinkDataSet {
|
||||
addProfileToShortLinkButton()
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
ToggleShortLinkHeader(text: Text("For social media"), link: userAddress.connLinkContact, short: $showShortLink)
|
||||
@@ -199,7 +203,7 @@ struct UserAddressView: View {
|
||||
let short = UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS)
|
||||
let connLinkContact = try await apiCreateUserAddress(short: short)
|
||||
DispatchQueue.main.async {
|
||||
chatModel.userAddress = UserContactLink(connLinkContact: connLinkContact)
|
||||
chatModel.userAddress = UserContactLink(connLinkContact: connLinkContact, shortLinkDataSet: short)
|
||||
alert = .shareOnCreate
|
||||
progressIndicator = false
|
||||
}
|
||||
@@ -214,12 +218,30 @@ struct UserAddressView: View {
|
||||
|
||||
private func addShortLinkButton() -> some View {
|
||||
Button {
|
||||
addShortLink()
|
||||
showAddShortLinkAlert()
|
||||
} label: {
|
||||
Label("Add short link", systemImage: "plus")
|
||||
}
|
||||
}
|
||||
|
||||
private func addProfileToShortLinkButton() -> some View {
|
||||
Button {
|
||||
showAddShortLinkAlert()
|
||||
} label: {
|
||||
Label("Share profile via link", systemImage: "plus")
|
||||
}
|
||||
}
|
||||
|
||||
private func showAddShortLinkAlert() {
|
||||
showAlert(
|
||||
title: NSLocalizedString("Share profile via link", comment: "alert title"),
|
||||
message: NSLocalizedString("Profile will be shared via the address short link. This change to the address cannot be reversed, other than fully deleting it. Do you wish to update the address?", comment: "alert message"),
|
||||
buttonTitle: NSLocalizedString("Update (and share profile)", comment: "alert button"),
|
||||
buttonAction: { addShortLink() },
|
||||
cancelButton: true
|
||||
)
|
||||
}
|
||||
|
||||
private func addShortLink() {
|
||||
progressIndicator = true
|
||||
Task {
|
||||
@@ -231,7 +253,7 @@ struct UserAddressView: View {
|
||||
await MainActor.run { progressIndicator = false }
|
||||
} catch let error {
|
||||
logger.error("apiAddMyAddressShortLink: \(responseError(error))")
|
||||
let a = getErrorAlert(error, "Error creating address")
|
||||
let a = getErrorAlert(error, "Error adding address short link")
|
||||
alert = .error(title: a.title, error: a.message)
|
||||
await MainActor.run { progressIndicator = false }
|
||||
}
|
||||
@@ -527,7 +549,7 @@ struct UserAddressSettingsView: View {
|
||||
|
||||
private func autoAcceptSection() -> some View {
|
||||
Section {
|
||||
if !aas.business {
|
||||
if !ChatModel.shared.addressShortLinkDataSet && !aas.business {
|
||||
acceptIncognitoToggle()
|
||||
}
|
||||
welcomeMessageEditor()
|
||||
@@ -594,7 +616,10 @@ private func saveAAS(_ aas: Binding<AutoAcceptState>, _ savedAAS: Binding<AutoAc
|
||||
struct UserAddressView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let chatModel = ChatModel()
|
||||
chatModel.userAddress = UserContactLink(connLinkContact: CreatedConnLink(connFullLink: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", connShortLink: nil))
|
||||
chatModel.userAddress = UserContactLink(
|
||||
connLinkContact: CreatedConnLink(connFullLink: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", connShortLink: nil),
|
||||
shortLinkDataSet: false
|
||||
)
|
||||
|
||||
|
||||
return Group {
|
||||
|
||||
@@ -26,7 +26,6 @@ import Control.Logger.Simple
|
||||
import Control.Monad
|
||||
import Control.Monad.Except
|
||||
import Control.Monad.IO.Class
|
||||
import Data.Int (Int64)
|
||||
import Data.List (find, intercalate)
|
||||
import Data.List.NonEmpty (NonEmpty (..))
|
||||
import qualified Data.Map.Strict as M
|
||||
@@ -52,6 +51,7 @@ import Simplex.Chat.Markdown (FormattedText (..), Format (..), parseMaybeMarkdow
|
||||
import Simplex.Chat.Messages
|
||||
import Simplex.Chat.Options
|
||||
import Simplex.Chat.Protocol (MsgContent (..))
|
||||
import Simplex.Chat.Store (GroupLink (..))
|
||||
import Simplex.Chat.Store.Direct (getContact)
|
||||
import Simplex.Chat.Store.Groups (getGroupInfo, getGroupLink, getGroupSummary, setGroupCustomData)
|
||||
import Simplex.Chat.Store.Profiles (GroupLinkInfo (..), getGroupLinkInfo)
|
||||
@@ -349,7 +349,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName
|
||||
let GroupInfo {groupId, groupProfile = GroupProfile {displayName}} = g
|
||||
notifyOwner gr $ "Joined the group " <> displayName <> ", creating the link…"
|
||||
sendChatCmd cc (APICreateGroupLink groupId GRMember False) >>= \case
|
||||
Right CRGroupLinkCreated {connLinkContact = CCLink gLink _} -> do
|
||||
Right CRGroupLinkCreated {groupLink = GroupLink {connLinkContact = CCLink gLink _}} -> do
|
||||
setGroupStatus st gr GRSPendingUpdate
|
||||
notifyOwner
|
||||
gr
|
||||
@@ -446,7 +446,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName
|
||||
groupProfileUpdate = profileUpdate <$> sendChatCmd cc (APIGetGroupLink groupId)
|
||||
where
|
||||
profileUpdate = \case
|
||||
Right CRGroupLink {connLinkContact = CCLink cr sl_} ->
|
||||
Right CRGroupLink {groupLink = GroupLink {connLinkContact = CCLink cr sl_}} ->
|
||||
let hadLinkBefore = profileHasGroupLink fromGroup
|
||||
hasLinkNow = profileHasGroupLink toGroup
|
||||
profileHasGroupLink GroupInfo {groupProfile = gp} =
|
||||
@@ -712,11 +712,11 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName
|
||||
let GroupInfo {groupProfile = GroupProfile {displayName = n}} = g
|
||||
case mRole_ of
|
||||
Nothing ->
|
||||
getGroupLinkRole cc user g >>= \case
|
||||
Just (_, CCLink gLink _, _, mRole) -> do
|
||||
let anotherRole = case mRole of GRObserver -> GRMember; _ -> GRObserver
|
||||
getGroupLink' cc user g >>= \case
|
||||
Just GroupLink {connLinkContact = CCLink gLink _, acceptMemberRole} -> do
|
||||
let anotherRole = case acceptMemberRole of GRObserver -> GRMember; _ -> GRObserver
|
||||
sendReply $
|
||||
initialRole n mRole
|
||||
initialRole n acceptMemberRole
|
||||
<> ("Send */role " <> tshow gId <> " " <> strEncodeTxt anotherRole <> "* to change it.\n\n")
|
||||
<> onlyViaLink gLink
|
||||
Nothing -> sendReply $ "Error: failed reading the initial member role for the group " <> n
|
||||
@@ -893,11 +893,11 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName
|
||||
let groupRef = groupReference' groupId gName
|
||||
withGroupAndReg sendReply groupId gName $ \_ _ ->
|
||||
sendChatCmd cc (APIGetGroupLink groupId) >>= \case
|
||||
Right CRGroupLink {connLinkContact = CCLink cReq _, memberRole} ->
|
||||
Right CRGroupLink {groupLink = GroupLink {connLinkContact = CCLink cReq _, acceptMemberRole}} ->
|
||||
sendReply $ T.unlines
|
||||
[ "The link to join the group " <> groupRef <> ":",
|
||||
strEncodeTxt $ simplexChatContact cReq,
|
||||
"New member role: " <> strEncodeTxt memberRole
|
||||
"New member role: " <> strEncodeTxt acceptMemberRole
|
||||
]
|
||||
Left (ChatErrorStore (SEGroupLinkNotFound _)) ->
|
||||
sendReply $ "The group " <> groupRef <> " has no public link."
|
||||
@@ -1045,15 +1045,15 @@ vr :: ChatController -> VersionRangeChat
|
||||
vr ChatController {config = ChatConfig {chatVRange}} = chatVRange
|
||||
{-# INLINE vr #-}
|
||||
|
||||
getGroupLinkRole :: ChatController -> User -> GroupInfo -> IO (Maybe (Int64, CreatedLinkContact, GroupLinkId, GroupMemberRole))
|
||||
getGroupLinkRole cc user gInfo =
|
||||
getGroupLink' :: ChatController -> User -> GroupInfo -> IO (Maybe GroupLink)
|
||||
getGroupLink' cc user gInfo =
|
||||
withDB "getGroupLink" cc $ \db -> getGroupLink db user gInfo
|
||||
|
||||
setGroupLinkRole :: ChatController -> GroupInfo -> GroupMemberRole -> IO (Maybe ConnReqContact)
|
||||
setGroupLinkRole cc GroupInfo {groupId} mRole = resp <$> sendChatCmd cc (APIGroupLinkMemberRole groupId mRole)
|
||||
where
|
||||
resp = \case
|
||||
Right (CRGroupLink _ _ (CCLink gLink _) _) -> Just gLink
|
||||
Right (CRGroupLink {groupLink = GroupLink {connLinkContact = CCLink gLink _sLnk}}) -> Just gLink
|
||||
_ -> Nothing
|
||||
|
||||
unexpectedError :: Text -> Text
|
||||
|
||||
@@ -62,7 +62,7 @@ import Simplex.Chat.Protocol
|
||||
import Simplex.Chat.Remote.AppVersion
|
||||
import Simplex.Chat.Remote.Types
|
||||
import Simplex.Chat.Stats (PresentedServersSummary)
|
||||
import Simplex.Chat.Store (AutoAccept, ChatLockEntity, StoreError (..), UserContactLink, GroupLinkInfo, UserMsgReceiptSettings)
|
||||
import Simplex.Chat.Store (AutoAccept, ChatLockEntity, GroupLink, GroupLinkInfo, StoreError (..), UserContactLink, UserMsgReceiptSettings)
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Chat.Types.Preferences
|
||||
import Simplex.Chat.Types.Shared
|
||||
@@ -723,8 +723,8 @@ data ChatResponse
|
||||
| CRGroupUpdated {user :: User, fromGroup :: GroupInfo, toGroup :: GroupInfo, member_ :: Maybe GroupMember}
|
||||
| CRGroupProfile {user :: User, groupInfo :: GroupInfo}
|
||||
| CRGroupDescription {user :: User, groupInfo :: GroupInfo} -- only used in CLI
|
||||
| CRGroupLinkCreated {user :: User, groupInfo :: GroupInfo, connLinkContact :: CreatedLinkContact, memberRole :: GroupMemberRole}
|
||||
| CRGroupLink {user :: User, groupInfo :: GroupInfo, connLinkContact :: CreatedLinkContact, memberRole :: GroupMemberRole}
|
||||
| CRGroupLinkCreated {user :: User, groupInfo :: GroupInfo, groupLink :: GroupLink}
|
||||
| CRGroupLink {user :: User, groupInfo :: GroupInfo, groupLink :: GroupLink}
|
||||
| CRGroupLinkDeleted {user :: User, groupInfo :: GroupInfo}
|
||||
| CRNewMemberContact {user :: User, contact :: Contact, groupInfo :: GroupInfo, member :: GroupMember}
|
||||
| CRNewMemberContactSentInv {user :: User, contact :: Contact, groupInfo :: GroupInfo, member :: GroupMember}
|
||||
|
||||
@@ -1144,19 +1144,17 @@ processChatCommand' vr = \case
|
||||
withFastStore' $ \db -> deleteNoteFolderCIs db user nf
|
||||
pure $ CRChatCleared user (AChatInfo SCTLocal $ LocalChat nf)
|
||||
_ -> throwCmdError "not supported"
|
||||
-- TODO [short links] prohibit incognito if short link data is set for address
|
||||
-- TODO - add user_contact_links.short_link_data_set; UserContactLink.shortLinkDataSet
|
||||
-- TODO - same for auto-accept:
|
||||
-- TODO - set incognito to false on setting short link data
|
||||
-- TODO - ignore on accept (for foolproofing)
|
||||
-- TODO - hide in UI
|
||||
APIAcceptContact incognito connReqId -> withUser $ \_ -> do
|
||||
userContactLinkId <- withFastStore $ \db -> getUserContactLinkIdByCReq db connReqId
|
||||
withUserContactLock "acceptContact" userContactLinkId $ do
|
||||
(user@User {userId}, cReq) <- withFastStore $ \db -> getContactRequest' db connReqId
|
||||
APIAcceptContact incognito connReqId -> withUser $ \user@User {userId} -> do
|
||||
(uclId, (ucl, gLinkInfo_)) <- withFastStore $ \db -> do
|
||||
uclId <- getUserContactLinkIdByCReq db connReqId
|
||||
uclGLinkInfo <- getUserContactLinkById db userId uclId
|
||||
pure (uclId, uclGLinkInfo)
|
||||
let UserContactLink {shortLinkDataSet} = ucl
|
||||
when (shortLinkDataSet && incognito) $ throwCmdError "incognito not allowed for address with short link data"
|
||||
withUserContactLock "acceptContact" uclId $ do
|
||||
cReq <- withFastStore $ \db -> getContactRequest db user connReqId
|
||||
(ct, conn@Connection {connId}, sqSecured) <- acceptContactRequest user cReq incognito
|
||||
ucl <- withFastStore $ \db -> getUserContactLinkById db userId userContactLinkId
|
||||
let contactUsed = (\(_, gLinkInfo_) -> isNothing gLinkInfo_) ucl
|
||||
let contactUsed = isNothing gLinkInfo_
|
||||
ct' <- withStore' $ \db -> do
|
||||
deleteContactRequestRec db user cReq
|
||||
updateContactAccepted db user ct contactUsed
|
||||
@@ -1764,9 +1762,9 @@ processChatCommand' vr = \case
|
||||
-- TODO [short links] change prepared entity user
|
||||
-- TODO - UI would call these APIs before APIConnectPrepared... APIs
|
||||
-- TODO - UI to transition to new user keeping chat opened
|
||||
APIChangeContactUser contactId newUserId -> withUser $ \user -> do
|
||||
APIChangeContactUser _contactId _newUserId -> withUser $ \_user -> do
|
||||
ok_
|
||||
APIChangeGroupUser groupId newUserId -> withUser $ \user -> do
|
||||
APIChangeGroupUser _groupId _newUserId -> withUser $ \_user -> do
|
||||
ok_
|
||||
APIConnectPreparedContact contactId incognito msgContent_ -> withUser $ \user -> do
|
||||
Contact {connLinkToConnect} <- withFastStore $ \db -> getContact db vr user contactId
|
||||
@@ -1837,37 +1835,13 @@ processChatCommand' vr = \case
|
||||
processChatCommand $ APIListContacts userId
|
||||
APICreateMyAddress userId short -> withUserId userId $ \user -> procCmd $ do
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
-- TODO [short links] incognito interaction with contact address
|
||||
-- TODO - problems:
|
||||
-- TODO 1 now that user profile is advertised in short link, giving an option to
|
||||
-- TODO share incognito profile on accept doesn't make sense.
|
||||
-- TODO 2 even advertising a random incognito profile in short link is somewhat broken,
|
||||
-- TODO as it would be the same for all connecting clients, but then changed on accept (in current implementation),
|
||||
-- TODO so it would be clear it's incognito profile; it might as well be clearly a placeholder
|
||||
-- TODO (e.g. "Hidden profile" in profile name), and to avoid distinguishing incognito and main profiles,
|
||||
-- TODO it can be a setting that can be set for main profile too.
|
||||
-- TODO 3 changing short link data from main to incognito profile also defeats the purpose of incognito profile,
|
||||
-- TODO as by scanning the short link in the past, connecting clients may know main profile (even if it's
|
||||
-- TODO currently set as incognito).
|
||||
-- TODO - some possibilities:
|
||||
-- TODO 1 always base choice on autoAccept -> acceptIncognito choice, don't give option on user accept action
|
||||
-- TODO 2 in this case, replace short link user data on change of this setting? (doesn't solve problem 3)
|
||||
-- TODO 3 give setting to include "Hidden profile" in short link, even if autoAccept is not set to incognito
|
||||
-- TODO 4 share same random profile on each accept, this way connecting clients technically
|
||||
-- TODO wouldn't be able to distinguish incognito profile (placeholder problem);
|
||||
-- TODO 5 only give choice to accept with main or incognito profile if random profile or placeholder is shared
|
||||
-- TODO in short link user data; if main profile is shared, don't give choice
|
||||
-- TODO 6 don't allow to change from main profile in short link to "Hidden profile" (or incognito autoAccept),
|
||||
-- TODO only allow to change vice versa, in one direction (solves problem 3)
|
||||
-- TODO 7 remove incognito functionality from address altogether
|
||||
-- TODO - it seems measures 3, 5, 6 are the most reasonable, or removing incognito functionality for addresses altogether
|
||||
let userData =
|
||||
if short
|
||||
then Just $ encodeShortLinkData (ContactShortLinkData (userProfileToSend user Nothing Nothing False) Nothing)
|
||||
else Nothing
|
||||
(connId, ccLink) <- withAgent $ \a -> createConnection a (aUserId user) True SCMContact userData Nothing IKPQOn subMode
|
||||
ccLink' <- shortenCreatedLink ccLink
|
||||
withFastStore $ \db -> createUserContactLink db user connId ccLink' subMode
|
||||
withFastStore $ \db -> createUserContactLink db user connId ccLink' short subMode
|
||||
pure $ CRUserContactLinkCreated user ccLink'
|
||||
CreateMyAddress short -> withUser $ \User {userId} ->
|
||||
processChatCommand $ APICreateMyAddress userId short
|
||||
@@ -1889,10 +1863,8 @@ processChatCommand' vr = \case
|
||||
ShowMyAddress -> withUser' $ \User {userId} ->
|
||||
processChatCommand $ APIShowMyAddress userId
|
||||
APIAddMyAddressShortLink userId -> withUserId' userId $ \user -> do
|
||||
(ucl@UserContactLink {connLinkContact = CCLink connFullLink sLnk_, autoAccept}, conn) <-
|
||||
(ucl@UserContactLink {connLinkContact = CCLink connFullLink _sLnk_, autoAccept}, conn) <-
|
||||
withFastStore $ \db -> (,) <$> getUserAddress db user <*> getUserAddressConnection db vr user
|
||||
when (isJust sLnk_) $ throwCmdError "address already has short link"
|
||||
-- TODO [short links] allow to add short link without data if autoAccept was set to incognito?
|
||||
let shortLinkProfile = userProfileToSend user Nothing Nothing False
|
||||
shortLinkMsg = autoAccept >>= autoReply >>= (Just . msgContentText)
|
||||
userData = encodeShortLinkData (ContactShortLinkData shortLinkProfile shortLinkMsg)
|
||||
@@ -1900,7 +1872,8 @@ processChatCommand' vr = \case
|
||||
case entityId conn of
|
||||
Just uclId -> do
|
||||
withFastStore' $ \db -> setUserContactLinkShortLink db uclId sLnk
|
||||
let ucl' = (ucl :: UserContactLink) {connLinkContact = CCLink connFullLink (Just sLnk)}
|
||||
let autoAccept' = autoAccept >>= \aa -> Just aa {acceptIncognito = False}
|
||||
ucl' = (ucl :: UserContactLink) {connLinkContact = CCLink connFullLink (Just sLnk), shortLinkDataSet = True, autoAccept = autoAccept'}
|
||||
pure $ CRUserContactLink user ucl'
|
||||
Nothing -> throwChatError $ CEException "no user contact link id"
|
||||
APISetProfileAddress userId False -> withUserId userId $ \user@User {profile = p} -> do
|
||||
@@ -1915,7 +1888,9 @@ processChatCommand' vr = \case
|
||||
processChatCommand $ APISetProfileAddress userId onOff
|
||||
APIAddressAutoAccept userId autoAccept_ -> withUserId userId $ \user -> do
|
||||
-- TODO [short links] update adress short link data if message changed
|
||||
forM_ autoAccept_ $ \AutoAccept {businessAddress, acceptIncognito} ->
|
||||
UserContactLink {shortLinkDataSet} <- withFastStore (`getUserAddress` user)
|
||||
forM_ autoAccept_ $ \AutoAccept {businessAddress, acceptIncognito} -> do
|
||||
when (shortLinkDataSet && acceptIncognito) $ throwCmdError "incognito not allowed for address with short link data"
|
||||
when (businessAddress && acceptIncognito) $ throwCmdError "requests to business address cannot be accepted incognito"
|
||||
contactLink <- withFastStore (\db -> updateUserAddressAutoAccept db user autoAccept_)
|
||||
pure $ CRUserContactLinkUpdated user contactLink
|
||||
@@ -2482,37 +2457,39 @@ processChatCommand' vr = \case
|
||||
crClientData = encodeJSON $ CRDataGroup groupLinkId
|
||||
(connId, ccLink) <- withAgent $ \a -> createConnection a (aUserId user) True SCMContact userData (Just crClientData) IKPQOff subMode
|
||||
ccLink' <- createdGroupLink <$> shortenCreatedLink ccLink
|
||||
withFastStore $ \db -> createGroupLink db user gInfo connId ccLink' groupLinkId mRole subMode
|
||||
pure $ CRGroupLinkCreated user gInfo ccLink' mRole
|
||||
gLink <- withFastStore $ \db -> createGroupLink db user gInfo connId ccLink' groupLinkId mRole short subMode
|
||||
pure $ CRGroupLinkCreated user gInfo gLink
|
||||
APIGroupLinkMemberRole groupId mRole' -> withUser $ \user -> withGroupLock "groupLinkMemberRole" groupId $ do
|
||||
gInfo <- withFastStore $ \db -> getGroupInfo db vr user groupId
|
||||
(groupLinkId, groupLink, _, mRole) <- withFastStore $ \db -> getGroupLink db user gInfo
|
||||
gLnk@GroupLink {acceptMemberRole} <- withFastStore $ \db -> getGroupLink db user gInfo
|
||||
assertUserGroupRole gInfo GRAdmin
|
||||
when (mRole' > GRMember) $ throwChatError $ CEGroupMemberInitialRole gInfo mRole'
|
||||
when (mRole' /= mRole) $ withFastStore' $ \db -> setGroupLinkMemberRole db user groupLinkId mRole'
|
||||
pure $ CRGroupLink user gInfo groupLink mRole'
|
||||
gLnk' <-
|
||||
if mRole' /= acceptMemberRole
|
||||
then withFastStore' $ \db -> setGroupLinkMemberRole db user gLnk mRole'
|
||||
else pure gLnk
|
||||
pure $ CRGroupLink user gInfo gLnk'
|
||||
APIDeleteGroupLink groupId -> withUser $ \user -> withGroupLock "deleteGroupLink" groupId $ do
|
||||
gInfo <- withFastStore $ \db -> getGroupInfo db vr user groupId
|
||||
deleteGroupLink' user gInfo
|
||||
pure $ CRGroupLinkDeleted user gInfo
|
||||
APIGetGroupLink groupId -> withUser $ \user -> do
|
||||
gInfo <- withFastStore $ \db -> getGroupInfo db vr user groupId
|
||||
(_, groupLink, _, mRole) <- withFastStore $ \db -> getGroupLink db user gInfo
|
||||
pure $ CRGroupLink user gInfo groupLink mRole
|
||||
gLnk <- withFastStore $ \db -> getGroupLink db user gInfo
|
||||
pure $ CRGroupLink user gInfo gLnk
|
||||
APIAddGroupShortLink groupId -> withUser $ \user -> do
|
||||
(gInfo, (uclId, _gLink@(CCLink connFullLink sLnk_), gLinkId, mRole), conn) <- withFastStore $ \db -> do
|
||||
(gInfo, gLink, conn) <- withFastStore $ \db -> do
|
||||
gInfo <- getGroupInfo db vr user groupId
|
||||
gLink <- getGroupLink db user gInfo
|
||||
conn <- getGroupLinkConnection db vr user gInfo
|
||||
pure (gInfo, gLink, conn)
|
||||
when (isJust sLnk_) $ throwCmdError "group link already has short link"
|
||||
let GroupInfo {groupProfile} = gInfo
|
||||
userData = encodeShortLinkData (GroupShortLinkData groupProfile)
|
||||
crClientData = encodeJSON $ CRDataGroup gLinkId
|
||||
GroupLink {groupLinkId} = gLink
|
||||
crClientData = encodeJSON $ CRDataGroup groupLinkId
|
||||
sLnk <- shortenShortLink' . toShortGroupLink =<< withAgent (\a -> setConnShortLink a (aConnId conn) SCMContact userData (Just crClientData))
|
||||
withFastStore' $ \db -> setUserContactLinkShortLink db uclId sLnk
|
||||
let groupLink' = CCLink connFullLink (Just sLnk)
|
||||
pure $ CRGroupLink user gInfo groupLink' mRole
|
||||
gLink' <- withFastStore' $ \db -> setGroupLinkShortLink db gLink sLnk
|
||||
pure $ CRGroupLink user gInfo gLink'
|
||||
APICreateMemberContact gId gMemberId -> withUser $ \user -> do
|
||||
(g, m) <- withFastStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> getGroupMember db vr user gId gMemberId
|
||||
assertUserGroupRole g GRAuthor
|
||||
@@ -3313,7 +3290,7 @@ processChatCommand' vr = \case
|
||||
case ct of
|
||||
CCTContact ->
|
||||
withFastStore' (\db -> getUserContactLinkViaShortLink db user l') >>= \case
|
||||
Just (UserContactLink (CCLink cReq _) _) -> pure (ACCL SCMContact $ CCLink cReq (Just l'), CPContactAddress CAPOwnLink)
|
||||
Just (UserContactLink (CCLink cReq _) _ _) -> pure (ACCL SCMContact $ CCLink cReq (Just l'), CPContactAddress CAPOwnLink)
|
||||
Nothing -> do
|
||||
(cReq, cData) <- getShortLinkConnReq user l'
|
||||
let contactSLinkData_ = decodeShortLinkData $ linkUserData cData
|
||||
|
||||
@@ -577,9 +577,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
probeMatchingContactsAndMembers ct' (contactConnIncognito ct') doProbeContacts
|
||||
withStore' $ \db -> resetContactConnInitiated db user conn'
|
||||
forM_ viaUserContactLink $ \userContactLinkId -> do
|
||||
ucl <- withStore $ \db -> getUserContactLinkById db userId userContactLinkId
|
||||
let (UserContactLink {autoAccept}, gli_) = ucl
|
||||
when (connChatVersion < batchSend2Version) $ sendAutoReply ct' autoAccept
|
||||
(ucl, gli_) <- withStore $ \db -> getUserContactLinkById db userId userContactLinkId
|
||||
when (connChatVersion < batchSend2Version) $ sendAutoReply ucl ct'
|
||||
-- TODO REMOVE LEGACY vvv
|
||||
forM_ gli_ $ \GroupLinkInfo {groupId, memberRole = gLinkMemRole} -> do
|
||||
groupInfo <- withStore $ \db -> getGroupInfo db vr user groupId
|
||||
@@ -645,9 +644,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
lift $ setContactNetworkStatus ct NSConnected
|
||||
toView $ CEvtContactSndReady user ct
|
||||
forM_ viaUserContactLink $ \userContactLinkId -> do
|
||||
ucl <- withStore $ \db -> getUserContactLinkById db userId userContactLinkId
|
||||
let (UserContactLink {autoAccept}, _) = ucl
|
||||
when (connChatVersion >= batchSend2Version) $ sendAutoReply ct autoAccept
|
||||
(ucl, _) <- withStore $ \db -> getUserContactLinkById db userId userContactLinkId
|
||||
when (connChatVersion >= batchSend2Version) $ sendAutoReply ucl ct
|
||||
QCONT ->
|
||||
void $ continueSending connEntity conn
|
||||
MWARN msgId err -> do
|
||||
@@ -667,13 +665,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
-- TODO add debugging output
|
||||
_ -> pure ()
|
||||
where
|
||||
-- TODO [short links] don't send auto-reply message if it should have been created by connecting client
|
||||
-- TODO - based on version + whether address has short link data (shortLinkDataSet)
|
||||
sendAutoReply ct = \case
|
||||
Just AutoAccept {autoReply = Just mc} -> do
|
||||
(msg, _) <- sendDirectContactMessage user ct (XMsgNew $ MCSimple (extMsgContent mc Nothing))
|
||||
ci <- saveSndChatItem user (CDDirectSnd ct) msg (CISndMsgContent mc)
|
||||
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct) ci]
|
||||
sendAutoReply UserContactLink {shortLinkDataSet, autoAccept} ct = case autoAccept of
|
||||
Just AutoAccept {autoReply = Just mc}
|
||||
| not shortLinkDataSet || connChatVersion < shortLinkDataVersion -> do
|
||||
(msg, _) <- sendDirectContactMessage user ct (XMsgNew $ MCSimple (extMsgContent mc Nothing))
|
||||
ci <- saveSndChatItem user (CDDirectSnd ct) msg (CISndMsgContent mc)
|
||||
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct) ci]
|
||||
_ -> pure ()
|
||||
|
||||
processGroupMessage :: AEvent e -> ConnectionEntity -> Connection -> GroupInfo -> GroupMember -> CM ()
|
||||
@@ -1227,8 +1224,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
where
|
||||
profileContactRequest :: InvitationId -> VersionRangeChat -> Profile -> Maybe XContactId -> Maybe MsgContent -> PQSupport -> CM ()
|
||||
profileContactRequest invId chatVRange p@Profile {displayName} xContactId_ mc_ reqPQSup = do
|
||||
ucl <- withStore $ \db -> getUserContactLinkById db userId uclId
|
||||
let (UserContactLink {connLinkContact = CCLink connReq _, autoAccept}, gLinkInfo_) = ucl
|
||||
uclGLinkInfo <- withStore $ \db -> getUserContactLinkById db userId uclId
|
||||
let (UserContactLink {connLinkContact = CCLink connReq _, shortLinkDataSet, autoAccept}, gLinkInfo_) = uclGLinkInfo
|
||||
isSimplexTeam = sameConnReqContact connReq adminContactReq
|
||||
v = maxVersion chatVRange
|
||||
case autoAccept of
|
||||
@@ -1265,7 +1262,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
Just ct -> toView $ CEvtContactRequestAlreadyAccepted user ct
|
||||
Nothing -> do
|
||||
-- [incognito] generate profile to send, create connection with incognito profile
|
||||
incognitoProfile <- if acceptIncognito then Just . NewIncognito <$> liftIO generateRandomProfile else pure Nothing
|
||||
incognitoProfile <-
|
||||
if not shortLinkDataSet && acceptIncognito
|
||||
then Just . NewIncognito <$> liftIO generateRandomProfile
|
||||
else pure Nothing
|
||||
ct <- acceptContactRequestAsync user uclId invId chatVRange p xContactId_ reqPQSup incognitoProfile
|
||||
forM_ mc_ $ \mc ->
|
||||
createInternalChatItem user (CDDirectRcv ct) (CIRcvMsgContent mc) Nothing
|
||||
|
||||
@@ -78,12 +78,13 @@ import Simplex.Messaging.Version hiding (version)
|
||||
-- 12 - support sending and receiving content reports (2025-01-03)
|
||||
-- 14 - support sending and receiving group join rejection (2025-02-24)
|
||||
-- 15 - support specifying message scopes for group messages (2025-03-12)
|
||||
-- 16 - support short link data (2025-06-10)
|
||||
|
||||
-- This should not be used directly in code, instead use `maxVersion chatVRange` from ChatConfig.
|
||||
-- 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.
|
||||
currentChatVersion :: VersionChat
|
||||
currentChatVersion = VersionChat 15
|
||||
currentChatVersion = VersionChat 16
|
||||
|
||||
-- This should not be used directly in code, instead use `chatVRange` from ChatConfig (see comment above)
|
||||
supportedChatVRange :: VersionRangeChat
|
||||
@@ -142,6 +143,10 @@ groupJoinRejectVersion = VersionChat 14
|
||||
groupKnockingVersion :: VersionChat
|
||||
groupKnockingVersion = VersionChat 15
|
||||
|
||||
-- support short link data in invitation, contact and group links
|
||||
shortLinkDataVersion :: VersionChat
|
||||
shortLinkDataVersion = VersionChat 16
|
||||
|
||||
agentToChatVersion :: VersionSMPA -> VersionChat
|
||||
agentToChatVersion v
|
||||
| v < pqdrSMPAgentVersion = initialChatVersion
|
||||
|
||||
@@ -6,6 +6,7 @@ module Simplex.Chat.Store
|
||||
ChatLockEntity (..),
|
||||
UserMsgReceiptSettings (..),
|
||||
UserContactLink (..),
|
||||
GroupLink (..),
|
||||
GroupLinkInfo (..),
|
||||
AutoAccept (..),
|
||||
createChatStore,
|
||||
@@ -14,6 +15,7 @@ module Simplex.Chat.Store
|
||||
)
|
||||
where
|
||||
|
||||
import Simplex.Chat.Store.Groups (GroupLink (..))
|
||||
import Simplex.Chat.Store.Profiles
|
||||
import Simplex.Chat.Store.Shared
|
||||
import Simplex.Messaging.Agent.Store.Common (DBStore (..), withTransaction)
|
||||
|
||||
@@ -60,7 +60,6 @@ module Simplex.Chat.Store.Direct
|
||||
getAcceptedContactByXContactId,
|
||||
getAcceptedBusinessChatByXContactId,
|
||||
getUserContactLinkIdByCReq,
|
||||
getContactRequest',
|
||||
getContactRequest,
|
||||
getContactRequestIdByName,
|
||||
deleteContactRequest,
|
||||
@@ -830,11 +829,6 @@ getUserContactLinkIdByCReq db contactRequestId =
|
||||
ExceptT . firstRow fromOnly (SEContactRequestNotFound contactRequestId) $
|
||||
DB.query db "SELECT user_contact_link_id FROM contact_requests WHERE contact_request_id = ?" (Only contactRequestId)
|
||||
|
||||
getContactRequest' :: DB.Connection -> Int64 -> ExceptT StoreError IO (User, UserContactRequest)
|
||||
getContactRequest' db contactRequestId = do
|
||||
user <- getUserByContactRequestId db contactRequestId
|
||||
(user,) <$> getContactRequest db user contactRequestId
|
||||
|
||||
getContactRequest :: DB.Connection -> User -> Int64 -> ExceptT StoreError IO UserContactRequest
|
||||
getContactRequest db User {userId} contactRequestId =
|
||||
ExceptT . firstRow toContactRequest (SEContactRequestNotFound contactRequestId) $
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
{-# LANGUAGE RecordWildCards #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE TemplateHaskell #-}
|
||||
{-# LANGUAGE TupleSections #-}
|
||||
{-# LANGUAGE TypeOperators #-}
|
||||
{-# OPTIONS_GHC -fno-warn-ambiguous-fields #-}
|
||||
@@ -17,6 +18,7 @@ module Simplex.Chat.Store.Groups
|
||||
GroupInfoRow,
|
||||
GroupMemberRow,
|
||||
MaybeGroupMemberRow,
|
||||
GroupLink (..),
|
||||
toGroupInfo,
|
||||
toGroupMember,
|
||||
toMaybeGroupMember,
|
||||
@@ -28,6 +30,7 @@ module Simplex.Chat.Store.Groups
|
||||
getGroupLink,
|
||||
getGroupLinkId,
|
||||
setGroupLinkMemberRole,
|
||||
setGroupLinkShortLink,
|
||||
getGroupAndMember,
|
||||
createNewGroup,
|
||||
createGroupInvitation,
|
||||
@@ -151,6 +154,7 @@ import Control.Monad
|
||||
import Control.Monad.Except
|
||||
import Control.Monad.IO.Class
|
||||
import Crypto.Random (ChaChaDRG)
|
||||
import qualified Data.Aeson.TH as J
|
||||
import Data.Bifunctor (second)
|
||||
import Data.Bitraversable (bitraverse)
|
||||
import Data.Either (rights)
|
||||
@@ -175,6 +179,7 @@ import Simplex.Messaging.Agent.Store.DB (Binary (..), BoolInt (..))
|
||||
import qualified Simplex.Messaging.Agent.Store.DB as DB
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import Simplex.Messaging.Crypto.Ratchet (pattern PQEncOff, pattern PQSupportOff)
|
||||
import Simplex.Messaging.Parsers (defaultJSON)
|
||||
import Simplex.Messaging.Protocol (SubscriptionMode (..))
|
||||
import Simplex.Messaging.Util (eitherToMaybe, firstRow', ($>>=), (<$$>))
|
||||
import Simplex.Messaging.Version
|
||||
@@ -194,16 +199,17 @@ toMaybeGroupMember userContactId ((Just groupMemberId, Just groupId, Just member
|
||||
Just $ toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages, memberBlocked) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, contactPreferences) :. (createdAt, updatedAt) :. (supportChatTs, supportChatUnread, supportChatUnanswered, supportChatMentions, supportChatLastMsgFromMemberTs))
|
||||
toMaybeGroupMember _ _ = Nothing
|
||||
|
||||
createGroupLink :: DB.Connection -> User -> GroupInfo -> ConnId -> CreatedLinkContact -> GroupLinkId -> GroupMemberRole -> SubscriptionMode -> ExceptT StoreError IO ()
|
||||
createGroupLink db User {userId} groupInfo@GroupInfo {groupId, localDisplayName} agentConnId (CCLink cReq shortLink) groupLinkId memberRole subMode =
|
||||
createGroupLink :: DB.Connection -> User -> GroupInfo -> ConnId -> CreatedLinkContact -> GroupLinkId -> GroupMemberRole -> Bool -> SubscriptionMode -> ExceptT StoreError IO GroupLink
|
||||
createGroupLink db user@User {userId} groupInfo@GroupInfo {groupId, localDisplayName} agentConnId (CCLink cReq shortLink) groupLinkId memberRole shortLinkDataSet subMode = do
|
||||
checkConstraint (SEDuplicateGroupLink groupInfo) . liftIO $ do
|
||||
currentTs <- getCurrentTime
|
||||
DB.execute
|
||||
db
|
||||
"INSERT INTO user_contact_links (user_id, group_id, group_link_id, local_display_name, conn_req_contact, short_link_contact, group_link_member_role, auto_accept, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?)"
|
||||
(userId, groupId, groupLinkId, "group_link_" <> localDisplayName, cReq, shortLink, memberRole, BI True, currentTs, currentTs)
|
||||
"INSERT INTO user_contact_links (user_id, group_id, group_link_id, local_display_name, conn_req_contact, short_link_contact, short_link_data_set, group_link_member_role, auto_accept, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)"
|
||||
((userId, groupId, groupLinkId, "group_link_" <> localDisplayName, cReq, shortLink, BI shortLinkDataSet) :. (memberRole, BI True, currentTs, currentTs))
|
||||
userContactLinkId <- insertedRowId db
|
||||
void $ createConnection_ db userId ConnUserContact (Just userContactLinkId) agentConnId ConnNew initialChatVersion chatInitialVRange Nothing Nothing Nothing 0 currentTs subMode PQSupportOff
|
||||
getGroupLink db user groupInfo
|
||||
|
||||
getGroupLinkConnection :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> ExceptT StoreError IO Connection
|
||||
getGroupLinkConnection db vr User {userId} groupInfo@GroupInfo {groupId} =
|
||||
@@ -262,21 +268,51 @@ deleteGroupLink db User {userId} GroupInfo {groupId} = do
|
||||
(userId, groupId)
|
||||
DB.execute db "DELETE FROM user_contact_links WHERE user_id = ? AND group_id = ?" (userId, groupId)
|
||||
|
||||
getGroupLink :: DB.Connection -> User -> GroupInfo -> ExceptT StoreError IO (Int64, CreatedLinkContact, GroupLinkId, GroupMemberRole)
|
||||
data GroupLink = GroupLink
|
||||
{ userContactLinkId :: Int64, -- db id
|
||||
connLinkContact :: CreatedLinkContact,
|
||||
shortLinkDataSet :: Bool,
|
||||
groupLinkId :: GroupLinkId,
|
||||
acceptMemberRole :: GroupMemberRole
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
getGroupLink :: DB.Connection -> User -> GroupInfo -> ExceptT StoreError IO GroupLink
|
||||
getGroupLink db User {userId} gInfo@GroupInfo {groupId} =
|
||||
ExceptT . firstRow groupLink (SEGroupLinkNotFound gInfo) $
|
||||
DB.query db "SELECT user_contact_link_id, conn_req_contact, short_link_contact, group_link_id, group_link_member_role FROM user_contact_links WHERE user_id = ? AND group_id = ? LIMIT 1" (userId, groupId)
|
||||
ExceptT . firstRow toGroupLink (SEGroupLinkNotFound gInfo) $
|
||||
DB.query db "SELECT user_contact_link_id, conn_req_contact, short_link_contact, short_link_data_set, group_link_id, group_link_member_role FROM user_contact_links WHERE user_id = ? AND group_id = ? LIMIT 1" (userId, groupId)
|
||||
where
|
||||
groupLink (linkId, cReq, shortLink, gLinkId, mRole_) = (linkId, CCLink cReq shortLink, gLinkId, fromMaybe GRMember mRole_)
|
||||
toGroupLink (userContactLinkId, cReq, shortLink, BI shortLinkDataSet, groupLinkId, mRole_) =
|
||||
GroupLink {
|
||||
userContactLinkId,
|
||||
connLinkContact = CCLink cReq shortLink,
|
||||
shortLinkDataSet,
|
||||
groupLinkId,
|
||||
acceptMemberRole = fromMaybe GRMember mRole_
|
||||
}
|
||||
|
||||
getGroupLinkId :: DB.Connection -> User -> GroupInfo -> IO (Maybe GroupLinkId)
|
||||
getGroupLinkId db User {userId} GroupInfo {groupId} =
|
||||
fmap join . maybeFirstRow fromOnly $
|
||||
DB.query db "SELECT group_link_id FROM user_contact_links WHERE user_id = ? AND group_id = ? LIMIT 1" (userId, groupId)
|
||||
|
||||
setGroupLinkMemberRole :: DB.Connection -> User -> Int64 -> GroupMemberRole -> IO ()
|
||||
setGroupLinkMemberRole db User {userId} userContactLinkId memberRole =
|
||||
setGroupLinkMemberRole :: DB.Connection -> User -> GroupLink -> GroupMemberRole -> IO GroupLink
|
||||
setGroupLinkMemberRole db User {userId} gLnk@GroupLink{userContactLinkId} memberRole = do
|
||||
DB.execute db "UPDATE user_contact_links SET group_link_member_role = ? WHERE user_id = ? AND user_contact_link_id = ?" (memberRole, userId, userContactLinkId)
|
||||
pure gLnk {acceptMemberRole = memberRole}
|
||||
|
||||
setGroupLinkShortLink :: DB.Connection -> GroupLink -> ShortLinkContact -> IO GroupLink
|
||||
setGroupLinkShortLink db gLnk@GroupLink {userContactLinkId, connLinkContact = CCLink connFullLink _sLnk_} shortLink = do
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
UPDATE user_contact_links
|
||||
SET short_link_contact = ?,
|
||||
short_link_data_set = ?
|
||||
WHERE user_contact_link_id = ?
|
||||
|]
|
||||
(shortLink, BI True, userContactLinkId)
|
||||
pure gLnk {connLinkContact = CCLink connFullLink (Just shortLink), shortLinkDataSet = True}
|
||||
|
||||
getGroupAndMember :: DB.Connection -> User -> Int64 -> VersionRangeChat -> ExceptT StoreError IO (GroupInfo, GroupMember)
|
||||
getGroupAndMember db User {userId, userContactId} groupMemberId vr = do
|
||||
@@ -2747,3 +2783,5 @@ updateGroupAlias db userId g@GroupInfo {groupId} localAlias = do
|
||||
updatedAt <- getCurrentTime
|
||||
DB.execute db "UPDATE groups SET local_alias = ?, updated_at = ? WHERE user_id = ? AND group_id = ?" (localAlias, updatedAt, userId, groupId)
|
||||
pure (g :: GroupInfo) {localAlias = localAlias}
|
||||
|
||||
$(J.deriveJSON defaultJSON ''GroupLink)
|
||||
|
||||
@@ -354,14 +354,14 @@ getUserContactProfiles db User {userId} =
|
||||
toContactProfile :: (ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, Maybe Preferences) -> Profile
|
||||
toContactProfile (displayName, fullName, image, contactLink, preferences) = Profile {displayName, fullName, image, contactLink, preferences}
|
||||
|
||||
createUserContactLink :: DB.Connection -> User -> ConnId -> CreatedLinkContact -> SubscriptionMode -> ExceptT StoreError IO ()
|
||||
createUserContactLink db User {userId} agentConnId (CCLink cReq shortLink) subMode =
|
||||
createUserContactLink :: DB.Connection -> User -> ConnId -> CreatedLinkContact -> Bool -> SubscriptionMode -> ExceptT StoreError IO ()
|
||||
createUserContactLink db User {userId} agentConnId (CCLink cReq shortLink) shortLinkDataSet subMode =
|
||||
checkConstraint SEDuplicateContactLink . liftIO $ do
|
||||
currentTs <- getCurrentTime
|
||||
DB.execute
|
||||
db
|
||||
"INSERT INTO user_contact_links (user_id, conn_req_contact, short_link_contact, created_at, updated_at) VALUES (?,?,?,?,?)"
|
||||
(userId, cReq, shortLink, currentTs, currentTs)
|
||||
"INSERT INTO user_contact_links (user_id, conn_req_contact, short_link_contact, short_link_data_set, created_at, updated_at) VALUES (?,?,?,?,?,?)"
|
||||
(userId, cReq, shortLink, BI shortLinkDataSet, currentTs, currentTs)
|
||||
userContactLinkId <- insertedRowId db
|
||||
void $ createConnection_ db userId ConnUserContact (Just userContactLinkId) agentConnId ConnNew initialChatVersion chatInitialVRange Nothing Nothing Nothing 0 currentTs subMode CR.PQSupportOff
|
||||
|
||||
@@ -451,6 +451,7 @@ data UserMsgReceiptSettings = UserMsgReceiptSettings
|
||||
|
||||
data UserContactLink = UserContactLink
|
||||
{ connLinkContact :: CreatedLinkContact,
|
||||
shortLinkDataSet :: Bool,
|
||||
autoAccept :: Maybe AutoAccept
|
||||
}
|
||||
deriving (Show)
|
||||
@@ -472,9 +473,9 @@ $(J.deriveJSON defaultJSON ''AutoAccept)
|
||||
|
||||
$(J.deriveJSON defaultJSON ''UserContactLink)
|
||||
|
||||
toUserContactLink :: (ConnReqContact, Maybe ShortLinkContact, BoolInt, BoolInt, BoolInt, Maybe MsgContent) -> UserContactLink
|
||||
toUserContactLink (connReq, shortLink, BI autoAccept, BI businessAddress, BI acceptIncognito, autoReply) =
|
||||
UserContactLink (CCLink connReq shortLink) $
|
||||
toUserContactLink :: (ConnReqContact, Maybe ShortLinkContact, BoolInt, BoolInt, BoolInt, BoolInt, Maybe MsgContent) -> UserContactLink
|
||||
toUserContactLink (connReq, shortLink, BI shortLinkDataSet, BI autoAccept, BI businessAddress, BI acceptIncognito, autoReply) =
|
||||
UserContactLink (CCLink connReq shortLink) shortLinkDataSet $
|
||||
if autoAccept then Just AutoAccept {businessAddress, acceptIncognito, autoReply} else Nothing
|
||||
|
||||
getUserAddress :: DB.Connection -> User -> ExceptT StoreError IO UserContactLink
|
||||
@@ -488,7 +489,7 @@ getUserContactLinkById db userId userContactLinkId =
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT conn_req_contact, short_link_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content, group_id, group_link_member_role
|
||||
SELECT conn_req_contact, short_link_contact, short_link_data_set, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content, group_id, group_link_member_role
|
||||
FROM user_contact_links
|
||||
WHERE user_id = ? AND user_contact_link_id = ?
|
||||
|]
|
||||
@@ -524,7 +525,7 @@ getUserContactLinkViaShortLink db User {userId} shortLink =
|
||||
userContactLinkQuery :: Query
|
||||
userContactLinkQuery =
|
||||
[sql|
|
||||
SELECT conn_req_contact, short_link_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content
|
||||
SELECT conn_req_contact, short_link_contact, short_link_data_set, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content
|
||||
FROM user_contact_links
|
||||
|]
|
||||
|
||||
@@ -534,10 +535,12 @@ setUserContactLinkShortLink db userContactLinkId shortLink =
|
||||
db
|
||||
[sql|
|
||||
UPDATE user_contact_links
|
||||
SET short_link_contact = ?
|
||||
SET short_link_contact = ?,
|
||||
short_link_data_set = ?,
|
||||
auto_accept_incognito = ?
|
||||
WHERE user_contact_link_id = ?
|
||||
|]
|
||||
(shortLink, userContactLinkId)
|
||||
(shortLink, BI True, BI False, userContactLinkId)
|
||||
|
||||
getContactWithoutConnViaAddress :: DB.Connection -> VersionRangeChat -> User -> (ConnReqContact, ConnReqContact) -> IO (Maybe Contact)
|
||||
getContactWithoutConnViaAddress db vr user@User {userId} (cReqSchema1, cReqSchema2) = do
|
||||
|
||||
@@ -14,6 +14,8 @@ ALTER TABLE contacts ADD COLUMN conn_short_link_to_connect BLOB;
|
||||
ALTER TABLE contacts ADD COLUMN contact_request_id INTEGER REFERENCES contact_requests ON DELETE SET NULL;
|
||||
CREATE INDEX idx_contacts_contact_request_id ON contacts(contact_request_id);
|
||||
|
||||
ALTER TABLE user_contact_links ADD COLUMN short_link_data_set INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE groups ADD COLUMN conn_full_link_to_connect BLOB;
|
||||
ALTER TABLE groups ADD COLUMN conn_short_link_to_connect BLOB;
|
||||
ALTER TABLE groups ADD COLUMN conn_link_started_connection INTEGER NOT NULL DEFAULT 0;
|
||||
@@ -28,6 +30,8 @@ ALTER TABLE contacts DROP COLUMN conn_short_link_to_connect;
|
||||
DROP INDEX idx_contacts_contact_request_id;
|
||||
ALTER TABLE contacts DROP COLUMN contact_request_id;
|
||||
|
||||
ALTER TABLE user_contact_links DROP COLUMN short_link_data_set;
|
||||
|
||||
ALTER TABLE groups DROP COLUMN conn_full_link_to_connect;
|
||||
ALTER TABLE groups DROP COLUMN conn_short_link_to_connect;
|
||||
ALTER TABLE groups DROP COLUMN conn_link_started_connection;
|
||||
|
||||
@@ -3062,7 +3062,7 @@ Plan:
|
||||
SEARCH commands USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
SELECT conn_req_contact, short_link_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content, group_id, group_link_member_role
|
||||
SELECT conn_req_contact, short_link_contact, short_link_data_set, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content, group_id, group_link_member_role
|
||||
FROM user_contact_links
|
||||
WHERE user_id = ? AND user_contact_link_id = ?
|
||||
|
||||
@@ -4854,21 +4854,21 @@ CORRELATED SCALAR SUBQUERY 1
|
||||
SEARCH cc USING COVERING INDEX idx_connections_group_member (user_id=? AND group_member_id=?)
|
||||
|
||||
Query:
|
||||
SELECT conn_req_contact, short_link_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content
|
||||
SELECT conn_req_contact, short_link_contact, short_link_data_set, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content
|
||||
FROM user_contact_links
|
||||
WHERE user_id = ? AND conn_req_contact IN (?,?)
|
||||
Plan:
|
||||
SEARCH user_contact_links USING INDEX sqlite_autoindex_user_contact_links_1 (user_id=?)
|
||||
|
||||
Query:
|
||||
SELECT conn_req_contact, short_link_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content
|
||||
SELECT conn_req_contact, short_link_contact, short_link_data_set, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content
|
||||
FROM user_contact_links
|
||||
WHERE user_id = ? AND local_display_name = '' AND group_id IS NULL
|
||||
Plan:
|
||||
SEARCH user_contact_links USING INDEX sqlite_autoindex_user_contact_links_1 (user_id=? AND local_display_name=?)
|
||||
|
||||
Query:
|
||||
SELECT conn_req_contact, short_link_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content
|
||||
SELECT conn_req_contact, short_link_contact, short_link_data_set, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content
|
||||
FROM user_contact_links
|
||||
WHERE user_id = ? AND short_link_contact = ?
|
||||
Plan:
|
||||
@@ -5022,19 +5022,6 @@ SEARCH u USING INTEGER PRIMARY KEY (rowid=?)
|
||||
SEARCH uct USING INTEGER PRIMARY KEY (rowid=?)
|
||||
SEARCH ucp USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.image, ucp.contact_link, ucp.preferences,
|
||||
u.show_ntfs, u.send_rcpts_contacts, u.send_rcpts_small_groups, u.view_pwd_hash, u.view_pwd_salt, u.user_member_profile_updated_at, u.ui_themes
|
||||
FROM users u
|
||||
JOIN contacts uct ON uct.contact_id = u.contact_id
|
||||
JOIN contact_profiles ucp ON ucp.contact_profile_id = uct.contact_profile_id
|
||||
JOIN contact_requests cr ON cr.user_id = u.user_id WHERE cr.contact_request_id = ?
|
||||
Plan:
|
||||
SEARCH cr USING INTEGER PRIMARY KEY (rowid=?)
|
||||
SEARCH u USING INTEGER PRIMARY KEY (rowid=?)
|
||||
SEARCH uct USING INTEGER PRIMARY KEY (rowid=?)
|
||||
SEARCH ucp USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.image, ucp.contact_link, ucp.preferences,
|
||||
u.show_ntfs, u.send_rcpts_contacts, u.send_rcpts_small_groups, u.view_pwd_hash, u.view_pwd_salt, u.user_member_profile_updated_at, u.ui_themes
|
||||
@@ -5648,10 +5635,10 @@ SEARCH connections USING INTEGER PRIMARY KEY (rowid=?)
|
||||
Query: INSERT INTO temp_conn_ids (conn_id) VALUES (?)
|
||||
Plan:
|
||||
|
||||
Query: INSERT INTO user_contact_links (user_id, conn_req_contact, short_link_contact, created_at, updated_at) VALUES (?,?,?,?,?)
|
||||
Query: INSERT INTO user_contact_links (user_id, conn_req_contact, short_link_contact, short_link_data_set, created_at, updated_at) VALUES (?,?,?,?,?,?)
|
||||
Plan:
|
||||
|
||||
Query: INSERT INTO user_contact_links (user_id, group_id, group_link_id, local_display_name, conn_req_contact, short_link_contact, group_link_member_role, auto_accept, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?)
|
||||
Query: INSERT INTO user_contact_links (user_id, group_id, group_link_id, local_display_name, conn_req_contact, short_link_contact, short_link_data_set, group_link_member_role, auto_accept, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)
|
||||
Plan:
|
||||
|
||||
Query: INSERT INTO users (agent_user_id, local_display_name, active_user, active_order, contact_id, show_ntfs, send_rcpts_contacts, send_rcpts_small_groups, created_at, updated_at) VALUES (?,?,?,?,0,?,?,?,?,?)
|
||||
@@ -5880,7 +5867,7 @@ Query: SELECT user_contact_link_id FROM contact_requests WHERE contact_request_i
|
||||
Plan:
|
||||
SEARCH contact_requests USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: SELECT user_contact_link_id, conn_req_contact, short_link_contact, group_link_id, group_link_member_role FROM user_contact_links WHERE user_id = ? AND group_id = ? LIMIT 1
|
||||
Query: SELECT user_contact_link_id, conn_req_contact, short_link_contact, short_link_data_set, group_link_id, group_link_member_role FROM user_contact_links WHERE user_id = ? AND group_id = ? LIMIT 1
|
||||
Plan:
|
||||
SEARCH user_contact_links USING INDEX idx_user_contact_links_group_id (group_id=?)
|
||||
|
||||
|
||||
@@ -332,6 +332,7 @@ CREATE TABLE user_contact_links(
|
||||
group_link_member_role TEXT NULL,
|
||||
business_address INTEGER DEFAULT 0,
|
||||
short_link_contact BLOB,
|
||||
short_link_data_set INTEGER NOT NULL DEFAULT 0,
|
||||
UNIQUE(user_id, local_display_name)
|
||||
);
|
||||
CREATE TABLE contact_requests(
|
||||
|
||||
@@ -50,7 +50,7 @@ import Simplex.Chat.Operators
|
||||
import Simplex.Chat.Protocol
|
||||
import Simplex.Chat.Remote.AppVersion (AppVersion (..), pattern AppVersionRange)
|
||||
import Simplex.Chat.Remote.Types
|
||||
import Simplex.Chat.Store (AutoAccept (..), StoreError (..), UserContactLink (..))
|
||||
import Simplex.Chat.Store (AutoAccept (..), GroupLink (..), StoreError (..), UserContactLink (..))
|
||||
import Simplex.Chat.Styled
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Chat.Types.Preferences
|
||||
@@ -233,8 +233,8 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, testView} liveIte
|
||||
CRGroupUpdated u g g' m -> ttyUser u $ viewGroupUpdated g g' m
|
||||
CRGroupProfile u g -> ttyUser u $ viewGroupProfile g
|
||||
CRGroupDescription u g -> ttyUser u $ viewGroupDescription g
|
||||
CRGroupLinkCreated u g ccLink mRole -> ttyUser u $ groupLink_ "Group link is created!" g ccLink mRole
|
||||
CRGroupLink u g ccLink mRole -> ttyUser u $ groupLink_ "Group link:" g ccLink mRole
|
||||
CRGroupLinkCreated u g gLink -> ttyUser u $ groupLink_ "Group link is created!" g gLink
|
||||
CRGroupLink u g gLink -> ttyUser u $ groupLink_ "Group link:" g gLink
|
||||
CRGroupLinkDeleted u g -> ttyUser u $ viewGroupLinkDeleted g
|
||||
CRNewMemberContact u _ g m -> ttyUser u ["contact for member " <> ttyGroup' g <> " " <> ttyMember m <> " is created"]
|
||||
CRNewMemberContactSentInv u _ct g m -> ttyUser u ["sent invitation to connect directly to member " <> ttyGroup' g <> " " <> ttyMember m]
|
||||
@@ -1090,13 +1090,13 @@ autoAcceptStatus_ = \case
|
||||
| otherwise = ""
|
||||
_ -> ["auto_accept off"]
|
||||
|
||||
groupLink_ :: StyledString -> GroupInfo -> CreatedLinkContact -> GroupMemberRole -> [StyledString]
|
||||
groupLink_ intro g (CCLink cReq shortLink) mRole =
|
||||
groupLink_ :: StyledString -> GroupInfo -> GroupLink -> [StyledString]
|
||||
groupLink_ intro g GroupLink {connLinkContact = CCLink cReq shortLink, acceptMemberRole} =
|
||||
[ intro,
|
||||
"",
|
||||
plain $ maybe cReqStr strEncode shortLink,
|
||||
"",
|
||||
"Anybody can connect to you and join group as " <> showRole mRole <> " with: " <> highlight' "/c <group_link_above>",
|
||||
"Anybody can connect to you and join group as " <> showRole acceptMemberRole <> " with: " <> highlight' "/c <group_link_above>",
|
||||
"to show it again: " <> highlight ("/show link #" <> viewGroupName g),
|
||||
"to delete it: " <> highlight ("/delete link #" <> viewGroupName g) <> " (joined members will remain connected to you)"
|
||||
]
|
||||
|
||||
@@ -2811,6 +2811,8 @@ testShortLinkAddressPrepareContact =
|
||||
]
|
||||
alice <## "to accept: /ac bob"
|
||||
alice <## "to reject: /rc bob (the sender will NOT be notified)"
|
||||
alice ##> "/ac i bob"
|
||||
alice <## "bad chat command: incognito not allowed for address with short link data"
|
||||
alice ##> "/ac bob"
|
||||
alice <## "bob (Bob): accepting contact request, you can send messages to contact"
|
||||
concurrently_
|
||||
|
||||
@@ -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\"}}}"
|
||||
##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing)))
|
||||
it "x.msg.new chat message with chat version range" $
|
||||
"{\"v\":\"1-15\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}"
|
||||
"{\"v\":\"1-16\",\"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)))
|
||||
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\"}}}}"
|
||||
@@ -249,13 +249,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\"}}}}}}"
|
||||
#==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} Nothing
|
||||
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-15\",\"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-16\",\"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} Nothing
|
||||
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\"}}}}}}"
|
||||
#==# 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" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-15\",\"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-16\",\"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
|
||||
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\"}}}}}}"
|
||||
@@ -270,7 +270,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-4%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-4%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}
|
||||
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-4%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-15\",\"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-4%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-16\",\"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}
|
||||
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\"}}}}}"
|
||||
|
||||
Reference in New Issue
Block a user