mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-28 20:29:53 +00:00
core, ui: short connection links with stored data (#5824)
* core, ui: optionally use short links (#5799) * core: optionally use short links * update test * update simplexmq, short group links * fix query * fix parser for _connect * ios: use short links * shorten links to remove fingerprint and onion hosts from known servers * fix parser * tests * nix * update query plans * update simplexmq, simplex: schema for short links * simplexmq * update ios * fix short links in ios * android: use short links * fix short group links, test short link connection plans * core: fix connection plan to recognize own short links * update simplexmq * space * all tests * relative symlinks in simplexmq to fix windows build * core: improve connection plan for short links (#5825) * core: improve connection plan for short links * improve connection plans * update UI * update simplexmq * ios: add preset server domains to entitlements, add short link paths to .well-known/apple-app-site-association * update simplexmq * fix group short link in iOS, fix simplex:/ scheme saved to database or used for connection plans * update simplexmq * ios: delay opening URI from outside until the app is started * update simplexmq
This commit is contained in:
parent
38c2529d8b
commit
45e395d35a
71 changed files with 1676 additions and 819 deletions
|
@ -443,12 +443,12 @@ struct ContentView: View {
|
|||
}
|
||||
|
||||
func connectViaUrl() {
|
||||
dismissAllSheets() {
|
||||
let m = ChatModel.shared
|
||||
if let url = m.appOpenUrl {
|
||||
m.appOpenUrl = nil
|
||||
let m = ChatModel.shared
|
||||
if let url = m.appOpenUrl {
|
||||
m.appOpenUrl = nil
|
||||
dismissAllSheets() {
|
||||
var path = url.path
|
||||
if (path == "/contact" || path == "/invitation") {
|
||||
if (path == "/contact" || path == "/invitation" || path == "/a" || path == "/c" || path == "/g" || path == "/i") {
|
||||
path.removeFirst()
|
||||
let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)")
|
||||
planAndConnect(
|
||||
|
|
|
@ -839,13 +839,14 @@ func apiVerifyGroupMember(_ groupId: Int64, _ groupMemberId: Int64, connectionCo
|
|||
return nil
|
||||
}
|
||||
|
||||
func apiAddContact(incognito: Bool) async -> ((String, PendingContactConnection)?, Alert?) {
|
||||
func apiAddContact(incognito: Bool) async -> ((CreatedConnLink, PendingContactConnection)?, Alert?) {
|
||||
guard let userId = ChatModel.shared.currentUser?.userId else {
|
||||
logger.error("apiAddContact: no current user")
|
||||
return (nil, nil)
|
||||
}
|
||||
let r = await chatSendCmd(.apiAddContact(userId: userId, incognito: incognito), bgTask: false)
|
||||
if case let .invitation(_, connReqInvitation, connection) = r { return ((connReqInvitation, connection), nil) }
|
||||
let short = UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS)
|
||||
let r = await chatSendCmd(.apiAddContact(userId: userId, short: short, incognito: incognito), bgTask: false)
|
||||
if case let .invitation(_, connLinkInv, connection) = r { return ((connLinkInv, connection), nil) }
|
||||
let alert = connectionErrorAlert(r)
|
||||
return (nil, alert)
|
||||
}
|
||||
|
@ -856,23 +857,26 @@ func apiSetConnectionIncognito(connId: Int64, incognito: Bool) async throws -> P
|
|||
throw r
|
||||
}
|
||||
|
||||
func apiChangeConnectionUser(connId: Int64, userId: Int64) async throws -> PendingContactConnection? {
|
||||
func apiChangeConnectionUser(connId: Int64, userId: Int64) async throws -> PendingContactConnection {
|
||||
let r = await chatSendCmd(.apiChangeConnectionUser(connId: connId, userId: userId))
|
||||
|
||||
if case let .connectionUserChanged(_, _, toConnection, _) = r {return toConnection}
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiConnectPlan(connReq: String) async throws -> ConnectionPlan {
|
||||
let userId = try currentUserId("apiConnectPlan")
|
||||
let r = await chatSendCmd(.apiConnectPlan(userId: userId, connReq: connReq))
|
||||
if case let .connectionPlan(_, connectionPlan) = r { return connectionPlan }
|
||||
logger.error("apiConnectPlan error: \(responseError(r))")
|
||||
throw r
|
||||
func apiConnectPlan(connLink: String) async -> ((CreatedConnLink, ConnectionPlan)?, Alert?) {
|
||||
guard let userId = ChatModel.shared.currentUser?.userId else {
|
||||
logger.error("apiConnectPlan: no current user")
|
||||
return (nil, nil)
|
||||
}
|
||||
let r = await chatSendCmd(.apiConnectPlan(userId: userId, connLink: connLink))
|
||||
if case let .connectionPlan(_, connLink, connPlan) = r { return ((connLink, connPlan), nil) }
|
||||
let alert = apiConnectResponseAlert(r) ?? connectionErrorAlert(r)
|
||||
return (nil, alert)
|
||||
}
|
||||
|
||||
func apiConnect(incognito: Bool, connReq: String) async -> (ConnReqType, PendingContactConnection)? {
|
||||
let (r, alert) = await apiConnect_(incognito: incognito, connReq: connReq)
|
||||
func apiConnect(incognito: Bool, connLink: CreatedConnLink) async -> (ConnReqType, PendingContactConnection)? {
|
||||
let (r, alert) = await apiConnect_(incognito: incognito, connLink: connLink)
|
||||
if let alert = alert {
|
||||
AlertManager.shared.showAlert(alert)
|
||||
return nil
|
||||
|
@ -881,12 +885,12 @@ func apiConnect(incognito: Bool, connReq: String) async -> (ConnReqType, Pending
|
|||
}
|
||||
}
|
||||
|
||||
func apiConnect_(incognito: Bool, connReq: String) async -> ((ConnReqType, PendingContactConnection)?, Alert?) {
|
||||
func apiConnect_(incognito: Bool, connLink: CreatedConnLink) async -> ((ConnReqType, PendingContactConnection)?, Alert?) {
|
||||
guard let userId = ChatModel.shared.currentUser?.userId else {
|
||||
logger.error("apiConnect: no current user")
|
||||
return (nil, nil)
|
||||
}
|
||||
let r = await chatSendCmd(.apiConnect(userId: userId, incognito: incognito, connReq: connReq))
|
||||
let r = await chatSendCmd(.apiConnect(userId: userId, incognito: incognito, connLink: connLink))
|
||||
let m = ChatModel.shared
|
||||
switch r {
|
||||
case let .sentConfirmation(_, connection):
|
||||
|
@ -899,20 +903,31 @@ func apiConnect_(incognito: Bool, connReq: String) async -> ((ConnReqType, Pendi
|
|||
}
|
||||
let alert = contactAlreadyExistsAlert(contact)
|
||||
return (nil, alert)
|
||||
default: ()
|
||||
}
|
||||
let alert = apiConnectResponseAlert(r) ?? connectionErrorAlert(r)
|
||||
return (nil, alert)
|
||||
}
|
||||
|
||||
private func apiConnectResponseAlert(_ r: ChatResponse) -> Alert? {
|
||||
switch r {
|
||||
case .chatCmdError(_, .error(.invalidConnReq)):
|
||||
let alert = mkAlert(
|
||||
mkAlert(
|
||||
title: "Invalid connection link",
|
||||
message: "Please check that you used the correct link or ask your contact to send you another one."
|
||||
)
|
||||
return (nil, alert)
|
||||
case .chatCmdError(_, .error(.unsupportedConnReq)):
|
||||
mkAlert(
|
||||
title: "Unsupported connection link",
|
||||
message: "This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link."
|
||||
)
|
||||
case .chatCmdError(_, .errorAgent(.SMP(_, .AUTH))):
|
||||
let alert = mkAlert(
|
||||
mkAlert(
|
||||
title: "Connection error (AUTH)",
|
||||
message: "Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection."
|
||||
)
|
||||
return (nil, alert)
|
||||
case let .chatCmdError(_, .errorAgent(.SMP(_, .BLOCKED(info)))):
|
||||
let alert = Alert(
|
||||
Alert(
|
||||
title: Text("Connection blocked"),
|
||||
message: Text("Connection is blocked by server operator:\n\(info.reason.text)"),
|
||||
primaryButton: .default(Text("Ok")),
|
||||
|
@ -922,25 +937,22 @@ func apiConnect_(incognito: Bool, connReq: String) async -> ((ConnReqType, Pendi
|
|||
}
|
||||
}
|
||||
)
|
||||
return (nil, alert)
|
||||
case .chatCmdError(_, .errorAgent(.SMP(_, .QUOTA))):
|
||||
let alert = mkAlert(
|
||||
mkAlert(
|
||||
title: "Undelivered messages",
|
||||
message: "The connection reached the limit of undelivered messages, your contact may be offline."
|
||||
)
|
||||
return (nil, alert)
|
||||
case let .chatCmdError(_, .errorAgent(.INTERNAL(internalErr))):
|
||||
if internalErr == "SEUniqueID" {
|
||||
let alert = mkAlert(
|
||||
mkAlert(
|
||||
title: "Already connected?",
|
||||
message: "It seems like you are already connected via this link. If it is not the case, there was an error (\(responseError(r)))."
|
||||
)
|
||||
return (nil, alert)
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
default: ()
|
||||
default: nil
|
||||
}
|
||||
let alert = connectionErrorAlert(r)
|
||||
return (nil, alert)
|
||||
}
|
||||
|
||||
func contactAlreadyExistsAlert(_ contact: Contact) -> Alert {
|
||||
|
@ -1130,10 +1142,10 @@ func apiSetChatUIThemes(chatId: ChatId, themes: ThemeModeOverrides?) async -> Bo
|
|||
}
|
||||
|
||||
|
||||
func apiCreateUserAddress() async throws -> String {
|
||||
func apiCreateUserAddress(short: Bool) async throws -> CreatedConnLink {
|
||||
let userId = try currentUserId("apiCreateUserAddress")
|
||||
let r = await chatSendCmd(.apiCreateMyAddress(userId: userId))
|
||||
if case let .userContactLinkCreated(_, connReq) = r { return connReq }
|
||||
let r = await chatSendCmd(.apiCreateMyAddress(userId: userId, short: short))
|
||||
if case let .userContactLinkCreated(_, connLink) = r { return connLink }
|
||||
throw r
|
||||
}
|
||||
|
||||
|
@ -1642,15 +1654,16 @@ func apiUpdateGroup(_ groupId: Int64, _ groupProfile: GroupProfile) async throws
|
|||
throw r
|
||||
}
|
||||
|
||||
func apiCreateGroupLink(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> (String, GroupMemberRole) {
|
||||
let r = await chatSendCmd(.apiCreateGroupLink(groupId: groupId, memberRole: memberRole))
|
||||
if case let .groupLinkCreated(_, _, connReq, memberRole) = r { return (connReq, memberRole) }
|
||||
func apiCreateGroupLink(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> (CreatedConnLink, GroupMemberRole) {
|
||||
let short = UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS)
|
||||
let r = await chatSendCmd(.apiCreateGroupLink(groupId: groupId, memberRole: memberRole, short: short))
|
||||
if case let .groupLinkCreated(_, _, connLink, memberRole) = r { return (connLink, memberRole) }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiGroupLinkMemberRole(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> (String, GroupMemberRole) {
|
||||
func apiGroupLinkMemberRole(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> (CreatedConnLink, GroupMemberRole) {
|
||||
let r = await chatSendCmd(.apiGroupLinkMemberRole(groupId: groupId, memberRole: memberRole))
|
||||
if case let .groupLink(_, _, connReq, memberRole) = r { return (connReq, memberRole) }
|
||||
if case let .groupLink(_, _, connLink, memberRole) = r { return (connLink, memberRole) }
|
||||
throw r
|
||||
}
|
||||
|
||||
|
@ -1660,11 +1673,11 @@ func apiDeleteGroupLink(_ groupId: Int64) async throws {
|
|||
throw r
|
||||
}
|
||||
|
||||
func apiGetGroupLink(_ groupId: Int64) throws -> (String, GroupMemberRole)? {
|
||||
func apiGetGroupLink(_ groupId: Int64) throws -> (CreatedConnLink, GroupMemberRole)? {
|
||||
let r = chatSendCmdSync(.apiGetGroupLink(groupId: groupId))
|
||||
switch r {
|
||||
case let .groupLink(_, _, connReq, memberRole):
|
||||
return (connReq, memberRole)
|
||||
case let .groupLink(_, _, connLink, memberRole):
|
||||
return (connLink, memberRole)
|
||||
case .chatCmdError(_, chatError: .errorStore(storeError: .groupLinkNotFound)):
|
||||
return nil
|
||||
default: throw r
|
||||
|
|
|
@ -19,6 +19,7 @@ struct SimpleXApp: App {
|
|||
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
@State private var enteredBackgroundAuthenticated: TimeInterval? = nil
|
||||
@State private var appOpenUrlLater: URL?
|
||||
|
||||
init() {
|
||||
DispatchQueue.global(qos: .background).sync {
|
||||
|
@ -42,7 +43,11 @@ struct SimpleXApp: App {
|
|||
.environmentObject(AppTheme.shared)
|
||||
.onOpenURL { url in
|
||||
logger.debug("ContentView.onOpenURL: \(url)")
|
||||
chatModel.appOpenUrl = url
|
||||
if AppChatState.shared.value == .active {
|
||||
chatModel.appOpenUrl = url
|
||||
} else {
|
||||
appOpenUrlLater = url
|
||||
}
|
||||
}
|
||||
.onAppear() {
|
||||
// Present screen for continue migration if it wasn't finished yet
|
||||
|
@ -93,7 +98,16 @@ struct SimpleXApp: App {
|
|||
if !chatModel.showCallView && !CallController.shared.hasActiveCalls() {
|
||||
await updateCallInvitations()
|
||||
}
|
||||
if let url = appOpenUrlLater {
|
||||
await MainActor.run {
|
||||
appOpenUrlLater = nil
|
||||
chatModel.appOpenUrl = url
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let url = appOpenUrlLater {
|
||||
appOpenUrlLater = nil
|
||||
chatModel.appOpenUrl = url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
@preconcurrency import SimpleXChat
|
||||
|
||||
func infoRow(_ title: LocalizedStringKey, _ value: String) -> some View {
|
||||
HStack {
|
||||
|
|
|
@ -45,7 +45,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: String?
|
||||
@State private var groupLink: CreatedConnLink?
|
||||
@State private var groupLinkMemberRole: GroupMemberRole = .member
|
||||
@State private var forwardedChatItems: [ChatItem] = []
|
||||
@State private var selectedChatItems: Set<Int64>? = nil
|
||||
|
|
|
@ -21,7 +21,7 @@ struct GroupChatInfoView: View {
|
|||
@State var localAlias: String
|
||||
@FocusState private var aliasTextFieldFocused: Bool
|
||||
@State private var alert: GroupChatInfoViewAlert? = nil
|
||||
@State private var groupLink: String?
|
||||
@State private var groupLink: CreatedConnLink?
|
||||
@State private var groupLinkMemberRole: GroupMemberRole = .member
|
||||
@State private var groupLinkNavLinkActive: Bool = false
|
||||
@State private var addMembersNavLinkActive: Bool = false
|
||||
|
|
|
@ -10,12 +10,14 @@ import SwiftUI
|
|||
import SimpleXChat
|
||||
|
||||
struct GroupLinkView: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
var groupId: Int64
|
||||
@Binding var groupLink: String?
|
||||
@Binding var groupLink: CreatedConnLink?
|
||||
@Binding var groupLinkMemberRole: GroupMemberRole
|
||||
var showTitle: Bool = false
|
||||
var creatingGroup: Bool = false
|
||||
var linkCreatedCb: (() -> Void)? = nil
|
||||
@State private var showShortLink = true
|
||||
@State private var creatingLink = false
|
||||
@State private var alert: GroupLinkAlert?
|
||||
@State private var shouldCreate = true
|
||||
|
@ -69,10 +71,10 @@ struct GroupLinkView: View {
|
|||
}
|
||||
}
|
||||
.frame(height: 36)
|
||||
SimpleXLinkQRCode(uri: groupLink)
|
||||
.id("simplex-qrcode-view-for-\(groupLink)")
|
||||
SimpleXCreatedLinkQRCode(link: groupLink, short: $showShortLink)
|
||||
.id("simplex-qrcode-view-for-\(groupLink.simplexChatUri(short: showShortLink))")
|
||||
Button {
|
||||
showShareSheet(items: [simplexChatLink(groupLink)])
|
||||
showShareSheet(items: [groupLink.simplexChatUri(short: showShortLink)])
|
||||
} label: {
|
||||
Label("Share link", systemImage: "square.and.arrow.up")
|
||||
}
|
||||
|
@ -93,6 +95,10 @@ struct GroupLinkView: View {
|
|||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
if let groupLink, groupLink.connShortLink != nil {
|
||||
ToggleShortLinkHeader(text: Text(""), link: groupLink, short: $showShortLink)
|
||||
}
|
||||
}
|
||||
.alert(item: $alert) { alert in
|
||||
switch alert {
|
||||
|
@ -158,8 +164,8 @@ struct GroupLinkView: View {
|
|||
|
||||
struct GroupLinkView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
@State var groupLink: String? = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D"
|
||||
@State var noGroupLink: String? = nil
|
||||
@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
|
||||
|
||||
return Group {
|
||||
GroupLinkView(groupId: 1, groupLink: $groupLink, groupLinkMemberRole: Binding.constant(.member))
|
||||
|
|
|
@ -14,6 +14,7 @@ struct ContactConnectionInfo: View {
|
|||
@EnvironmentObject var theme: AppTheme
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
@State var contactConnection: PendingContactConnection
|
||||
@State private var showShortLink: Bool = true
|
||||
@State private var alert: CCInfoAlert?
|
||||
@State private var localAlias = ""
|
||||
@State private var showIncognitoSheet = false
|
||||
|
@ -61,14 +62,19 @@ struct ContactConnectionInfo: View {
|
|||
}
|
||||
|
||||
if contactConnection.initiated,
|
||||
let connReqInv = contactConnection.connReqInv {
|
||||
SimpleXLinkQRCode(uri: simplexChatLink(connReqInv))
|
||||
let connLinkInv = contactConnection.connLinkInv {
|
||||
SimpleXCreatedLinkQRCode(link: connLinkInv, short: $showShortLink)
|
||||
.id("simplex-invitation-qrcode-\(connLinkInv.simplexChatUri(short: showShortLink))")
|
||||
incognitoEnabled()
|
||||
shareLinkButton(connReqInv, theme.colors.secondary)
|
||||
oneTimeLinkLearnMoreButton(theme.colors.secondary)
|
||||
shareLinkButton(connLinkInv, short: showShortLink)
|
||||
oneTimeLinkLearnMoreButton()
|
||||
} else {
|
||||
incognitoEnabled()
|
||||
oneTimeLinkLearnMoreButton(theme.colors.secondary)
|
||||
oneTimeLinkLearnMoreButton()
|
||||
}
|
||||
} header: {
|
||||
if let connLinkInv = contactConnection.connLinkInv, connLinkInv.connShortLink != nil {
|
||||
ToggleShortLinkHeader(text: Text(""), link: connLinkInv, short: $showShortLink)
|
||||
}
|
||||
} footer: {
|
||||
sharedProfileInfo(contactConnection.incognito)
|
||||
|
@ -167,26 +173,22 @@ struct ContactConnectionInfo: View {
|
|||
}
|
||||
}
|
||||
|
||||
private func shareLinkButton(_ connReqInvitation: String, _ secondaryColor: Color) -> some View {
|
||||
private func shareLinkButton(_ connLinkInvitation: CreatedConnLink, short: Bool) -> some View {
|
||||
Button {
|
||||
showShareSheet(items: [simplexChatLink(connReqInvitation)])
|
||||
showShareSheet(items: [connLinkInvitation.simplexChatUri(short: short)])
|
||||
} label: {
|
||||
settingsRow("square.and.arrow.up", color: secondaryColor) {
|
||||
Text("Share 1-time link")
|
||||
}
|
||||
Label("Share 1-time link", systemImage: "square.and.arrow.up")
|
||||
}
|
||||
}
|
||||
|
||||
private func oneTimeLinkLearnMoreButton(_ secondaryColor: Color) -> some View {
|
||||
private func oneTimeLinkLearnMoreButton() -> some View {
|
||||
NavigationLink {
|
||||
AddContactLearnMore(showTitle: false)
|
||||
.navigationTitle("One-time invitation link")
|
||||
.modifier(ThemedBackground())
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
settingsRow("info.circle", color: secondaryColor) {
|
||||
Text("Learn more")
|
||||
}
|
||||
Label("Learn more", systemImage: "info.circle")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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: String?
|
||||
@State private var groupLink: CreatedConnLink?
|
||||
@State private var groupLinkMemberRole: GroupMemberRole = .member
|
||||
|
||||
var body: some View {
|
||||
|
|
|
@ -81,7 +81,8 @@ struct NewChatView: View {
|
|||
@State var selection: NewChatOption
|
||||
@State var showQRCodeScanner = false
|
||||
@State private var invitationUsed: Bool = false
|
||||
@State private var connReqInvitation: String = ""
|
||||
@State private var connLinkInvitation: CreatedConnLink = CreatedConnLink(connFullLink: "", connShortLink: nil)
|
||||
@State private var showShortLink = true
|
||||
@State private var creatingConnReq = false
|
||||
@State var choosingProfile = false
|
||||
@State private var pastedLink: String = ""
|
||||
|
@ -174,11 +175,12 @@ struct NewChatView: View {
|
|||
|
||||
private func prepareAndInviteView() -> some View {
|
||||
ZStack { // ZStack is needed for views to not make transitions between each other
|
||||
if connReqInvitation != "" {
|
||||
if connLinkInvitation.connFullLink != "" {
|
||||
InviteView(
|
||||
invitationUsed: $invitationUsed,
|
||||
contactConnection: $contactConnection,
|
||||
connReqInvitation: $connReqInvitation,
|
||||
connLinkInvitation: $connLinkInvitation,
|
||||
showShortLink: $showShortLink,
|
||||
choosingProfile: $choosingProfile
|
||||
)
|
||||
} else if creatingConnReq {
|
||||
|
@ -190,16 +192,16 @@ struct NewChatView: View {
|
|||
}
|
||||
|
||||
private func createInvitation() {
|
||||
if connReqInvitation == "" && contactConnection == nil && !creatingConnReq {
|
||||
if connLinkInvitation.connFullLink == "" && contactConnection == nil && !creatingConnReq {
|
||||
creatingConnReq = true
|
||||
Task {
|
||||
_ = try? await Task.sleep(nanoseconds: 250_000000)
|
||||
let (r, apiAlert) = await apiAddContact(incognito: incognitoGroupDefault.get())
|
||||
if let (connReq, pcc) = r {
|
||||
if let (connLink, pcc) = r {
|
||||
await MainActor.run {
|
||||
m.updateContactConnection(pcc)
|
||||
m.showingInvitation = ShowingInvitation(pcc: pcc, connChatUsed: false)
|
||||
connReqInvitation = connReq
|
||||
connLinkInvitation = connLink
|
||||
contactConnection = pcc
|
||||
}
|
||||
} else {
|
||||
|
@ -243,7 +245,8 @@ private struct InviteView: View {
|
|||
@EnvironmentObject var theme: AppTheme
|
||||
@Binding var invitationUsed: Bool
|
||||
@Binding var contactConnection: PendingContactConnection?
|
||||
@Binding var connReqInvitation: String
|
||||
@Binding var connLinkInvitation: CreatedConnLink
|
||||
@Binding var showShortLink: Bool
|
||||
@Binding var choosingProfile: Bool
|
||||
|
||||
@AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false
|
||||
|
@ -261,7 +264,7 @@ private struct InviteView: View {
|
|||
NavigationLink {
|
||||
ActiveProfilePicker(
|
||||
contactConnection: $contactConnection,
|
||||
connReqInvitation: $connReqInvitation,
|
||||
connLinkInvitation: $connLinkInvitation,
|
||||
incognitoEnabled: $incognitoDefault,
|
||||
choosingProfile: $choosingProfile,
|
||||
selectedProfile: selectedProfile
|
||||
|
@ -296,7 +299,7 @@ private struct InviteView: View {
|
|||
|
||||
private func shareLinkView() -> some View {
|
||||
HStack {
|
||||
let link = simplexChatLink(connReqInvitation)
|
||||
let link = connLinkInvitation.simplexChatUri(short: showShortLink)
|
||||
linkTextView(link)
|
||||
Button {
|
||||
showShareSheet(items: [link])
|
||||
|
@ -310,9 +313,9 @@ private struct InviteView: View {
|
|||
}
|
||||
|
||||
private func qrCodeView() -> some View {
|
||||
Section(header: Text("Or show this code").foregroundColor(theme.colors.secondary)) {
|
||||
SimpleXLinkQRCode(uri: connReqInvitation, onShare: setInvitationUsed)
|
||||
.id("simplex-qrcode-view-for-\(connReqInvitation)")
|
||||
Section {
|
||||
SimpleXCreatedLinkQRCode(link: connLinkInvitation, short: $showShortLink, onShare: setInvitationUsed)
|
||||
.id("simplex-qrcode-view-for-\(connLinkInvitation.simplexChatUri(short: showShortLink))")
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||
|
@ -322,6 +325,8 @@ private struct InviteView: View {
|
|||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
} header: {
|
||||
ToggleShortLinkHeader(text: Text("Or show this code"), link: connLinkInvitation, short: $showShortLink)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -343,7 +348,7 @@ private struct ActiveProfilePicker: View {
|
|||
@EnvironmentObject var chatModel: ChatModel
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@Binding var contactConnection: PendingContactConnection?
|
||||
@Binding var connReqInvitation: String
|
||||
@Binding var connLinkInvitation: CreatedConnLink
|
||||
@Binding var incognitoEnabled: Bool
|
||||
@Binding var choosingProfile: Bool
|
||||
@State private var alert: SomeAlert?
|
||||
|
@ -415,12 +420,11 @@ private struct ActiveProfilePicker: View {
|
|||
}
|
||||
Task {
|
||||
do {
|
||||
if let contactConn = contactConnection,
|
||||
let conn = try await apiChangeConnectionUser(connId: contactConn.pccConnId, userId: profile.userId) {
|
||||
|
||||
if let contactConn = contactConnection {
|
||||
let conn = try await apiChangeConnectionUser(connId: contactConn.pccConnId, userId: profile.userId)
|
||||
await MainActor.run {
|
||||
contactConnection = conn
|
||||
connReqInvitation = conn.connReqInv ?? ""
|
||||
connLinkInvitation = conn.connLinkInv ?? CreatedConnLink(connFullLink: "", connShortLink: nil)
|
||||
incognitoEnabled = false
|
||||
chatModel.updateContactConnection(conn)
|
||||
}
|
||||
|
@ -836,23 +840,25 @@ func sharedProfileInfo(_ incognito: Bool) -> Text {
|
|||
}
|
||||
|
||||
enum PlanAndConnectAlert: Identifiable {
|
||||
case ownInvitationLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case invitationLinkConnecting(connectionLink: String)
|
||||
case ownContactAddressConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case contactAddressConnectingConfirmReconnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case groupLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case groupLinkConnectingConfirmReconnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case groupLinkConnecting(connectionLink: String, groupInfo: GroupInfo?)
|
||||
case ownInvitationLinkConfirmConnect(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case invitationLinkConnecting(connectionLink: CreatedConnLink)
|
||||
case ownContactAddressConfirmConnect(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case contactAddressConnectingConfirmReconnect(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case groupLinkConfirmConnect(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case groupLinkConnectingConfirmReconnect(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case groupLinkConnecting(connectionLink: CreatedConnLink, groupInfo: GroupInfo?)
|
||||
case error(shortOrFullLink: String, alert: Alert)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case let .ownInvitationLinkConfirmConnect(connectionLink, _, _): return "ownInvitationLinkConfirmConnect \(connectionLink)"
|
||||
case let .invitationLinkConnecting(connectionLink): return "invitationLinkConnecting \(connectionLink)"
|
||||
case let .ownContactAddressConfirmConnect(connectionLink, _, _): return "ownContactAddressConfirmConnect \(connectionLink)"
|
||||
case let .contactAddressConnectingConfirmReconnect(connectionLink, _, _): return "contactAddressConnectingConfirmReconnect \(connectionLink)"
|
||||
case let .groupLinkConfirmConnect(connectionLink, _, _): return "groupLinkConfirmConnect \(connectionLink)"
|
||||
case let .groupLinkConnectingConfirmReconnect(connectionLink, _, _): return "groupLinkConnectingConfirmReconnect \(connectionLink)"
|
||||
case let .groupLinkConnecting(connectionLink, _): return "groupLinkConnecting \(connectionLink)"
|
||||
case let .ownInvitationLinkConfirmConnect(connectionLink, _, _): return "ownInvitationLinkConfirmConnect \(connectionLink.connFullLink)"
|
||||
case let .invitationLinkConnecting(connectionLink): return "invitationLinkConnecting \(connectionLink.connFullLink)"
|
||||
case let .ownContactAddressConfirmConnect(connectionLink, _, _): return "ownContactAddressConfirmConnect \(connectionLink.connFullLink)"
|
||||
case let .contactAddressConnectingConfirmReconnect(connectionLink, _, _): return "contactAddressConnectingConfirmReconnect \(connectionLink.connFullLink)"
|
||||
case let .groupLinkConfirmConnect(connectionLink, _, _): return "groupLinkConfirmConnect \(connectionLink.connFullLink)"
|
||||
case let .groupLinkConnectingConfirmReconnect(connectionLink, _, _): return "groupLinkConnectingConfirmReconnect \(connectionLink.connFullLink)"
|
||||
case let .groupLinkConnecting(connectionLink, _): return "groupLinkConnecting \(connectionLink.connFullLink)"
|
||||
case let .error(shortOrFullLink, alert): return "error \(shortOrFullLink)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -935,21 +941,22 @@ func planAndConnectAlert(_ alert: PlanAndConnectAlert, dismiss: Bool, cleanup: (
|
|||
dismissButton: .default(Text("OK")) { cleanup?() }
|
||||
)
|
||||
}
|
||||
case let .error(_, alert): return alert
|
||||
}
|
||||
}
|
||||
|
||||
enum PlanAndConnectActionSheet: Identifiable {
|
||||
case askCurrentOrIncognitoProfile(connectionLink: String, connectionPlan: ConnectionPlan?, title: LocalizedStringKey)
|
||||
case askCurrentOrIncognitoProfileDestructive(connectionLink: String, connectionPlan: ConnectionPlan, title: LocalizedStringKey)
|
||||
case askCurrentOrIncognitoProfile(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan?, title: LocalizedStringKey)
|
||||
case askCurrentOrIncognitoProfileDestructive(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, title: LocalizedStringKey)
|
||||
case askCurrentOrIncognitoProfileConnectContactViaAddress(contact: Contact)
|
||||
case ownGroupLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool?, groupInfo: GroupInfo)
|
||||
case ownGroupLinkConfirmConnect(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, incognito: Bool?, groupInfo: GroupInfo)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case let .askCurrentOrIncognitoProfile(connectionLink, _, _): return "askCurrentOrIncognitoProfile \(connectionLink)"
|
||||
case let .askCurrentOrIncognitoProfileDestructive(connectionLink, _, _): return "askCurrentOrIncognitoProfileDestructive \(connectionLink)"
|
||||
case let .askCurrentOrIncognitoProfile(connectionLink, _, _): return "askCurrentOrIncognitoProfile \(connectionLink.connFullLink)"
|
||||
case let .askCurrentOrIncognitoProfileDestructive(connectionLink, _, _): return "askCurrentOrIncognitoProfileDestructive \(connectionLink.connFullLink)"
|
||||
case let .askCurrentOrIncognitoProfileConnectContactViaAddress(contact): return "askCurrentOrIncognitoProfileConnectContactViaAddress \(contact.contactId)"
|
||||
case let .ownGroupLinkConfirmConnect(connectionLink, _, _, _): return "ownGroupLinkConfirmConnect \(connectionLink)"
|
||||
case let .ownGroupLinkConfirmConnect(connectionLink, _, _, _): return "ownGroupLinkConfirmConnect \(connectionLink.connFullLink)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1008,7 +1015,7 @@ func planAndConnectActionSheet(_ sheet: PlanAndConnectActionSheet, dismiss: Bool
|
|||
}
|
||||
|
||||
func planAndConnect(
|
||||
_ connectionLink: String,
|
||||
_ shortOrFullLink: String,
|
||||
showAlert: @escaping (PlanAndConnectAlert) -> Void,
|
||||
showActionSheet: @escaping (PlanAndConnectActionSheet) -> Void,
|
||||
dismiss: Bool,
|
||||
|
@ -1018,8 +1025,8 @@ func planAndConnect(
|
|||
filterKnownGroup: ((GroupInfo) -> Void)? = nil
|
||||
) {
|
||||
Task {
|
||||
do {
|
||||
let connectionPlan = try await apiConnectPlan(connReq: connectionLink)
|
||||
let (result, alert) = await apiConnectPlan(connLink: shortOrFullLink)
|
||||
if let (connectionLink, connectionPlan) = result {
|
||||
switch connectionPlan {
|
||||
case let .invitationLink(ilp):
|
||||
switch ilp {
|
||||
|
@ -1028,32 +1035,40 @@ func planAndConnect(
|
|||
if let incognito = incognito {
|
||||
connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup)
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via one-time link"))
|
||||
await MainActor.run {
|
||||
showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via one-time link"))
|
||||
}
|
||||
}
|
||||
case .ownLink:
|
||||
logger.debug("planAndConnect, .invitationLink, .ownLink, incognito=\(incognito?.description ?? "nil")")
|
||||
if let incognito = incognito {
|
||||
showAlert(.ownInvitationLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own one-time link!"))
|
||||
await MainActor.run {
|
||||
if let incognito = incognito {
|
||||
showAlert(.ownInvitationLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own one-time link!"))
|
||||
}
|
||||
}
|
||||
case let .connecting(contact_):
|
||||
logger.debug("planAndConnect, .invitationLink, .connecting, incognito=\(incognito?.description ?? "nil")")
|
||||
if let contact = contact_ {
|
||||
if let f = filterKnownContact {
|
||||
f(contact)
|
||||
await MainActor.run {
|
||||
if let contact = contact_ {
|
||||
if let f = filterKnownContact {
|
||||
f(contact)
|
||||
} else {
|
||||
openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) }
|
||||
}
|
||||
} else {
|
||||
openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) }
|
||||
showAlert(.invitationLinkConnecting(connectionLink: connectionLink))
|
||||
}
|
||||
} else {
|
||||
showAlert(.invitationLinkConnecting(connectionLink: connectionLink))
|
||||
}
|
||||
case let .known(contact):
|
||||
logger.debug("planAndConnect, .invitationLink, .known, incognito=\(incognito?.description ?? "nil")")
|
||||
if let f = filterKnownContact {
|
||||
f(contact)
|
||||
} else {
|
||||
openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) }
|
||||
await MainActor.run {
|
||||
if let f = filterKnownContact {
|
||||
f(contact)
|
||||
} else {
|
||||
openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .contactAddress(cap):
|
||||
|
@ -1063,83 +1078,109 @@ func planAndConnect(
|
|||
if let incognito = incognito {
|
||||
connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup)
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via contact address"))
|
||||
await MainActor.run {
|
||||
showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via contact address"))
|
||||
}
|
||||
}
|
||||
case .ownLink:
|
||||
logger.debug("planAndConnect, .contactAddress, .ownLink, incognito=\(incognito?.description ?? "nil")")
|
||||
if let incognito = incognito {
|
||||
showAlert(.ownContactAddressConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own SimpleX address!"))
|
||||
await MainActor.run {
|
||||
if let incognito = incognito {
|
||||
showAlert(.ownContactAddressConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own SimpleX address!"))
|
||||
}
|
||||
}
|
||||
case .connectingConfirmReconnect:
|
||||
logger.debug("planAndConnect, .contactAddress, .connectingConfirmReconnect, incognito=\(incognito?.description ?? "nil")")
|
||||
if let incognito = incognito {
|
||||
showAlert(.contactAddressConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You have already requested connection!\nRepeat connection request?"))
|
||||
await MainActor.run {
|
||||
if let incognito = incognito {
|
||||
showAlert(.contactAddressConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You have already requested connection!\nRepeat connection request?"))
|
||||
}
|
||||
}
|
||||
case let .connectingProhibit(contact):
|
||||
logger.debug("planAndConnect, .contactAddress, .connectingProhibit, incognito=\(incognito?.description ?? "nil")")
|
||||
if let f = filterKnownContact {
|
||||
f(contact)
|
||||
} else {
|
||||
openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) }
|
||||
await MainActor.run {
|
||||
if let f = filterKnownContact {
|
||||
f(contact)
|
||||
} else {
|
||||
openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) }
|
||||
}
|
||||
}
|
||||
case let .known(contact):
|
||||
logger.debug("planAndConnect, .contactAddress, .known, incognito=\(incognito?.description ?? "nil")")
|
||||
if let f = filterKnownContact {
|
||||
f(contact)
|
||||
} else {
|
||||
openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) }
|
||||
await MainActor.run {
|
||||
if let f = filterKnownContact {
|
||||
f(contact)
|
||||
} else {
|
||||
openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) }
|
||||
}
|
||||
}
|
||||
case let .contactViaAddress(contact):
|
||||
logger.debug("planAndConnect, .contactAddress, .contactViaAddress, incognito=\(incognito?.description ?? "nil")")
|
||||
if let incognito = incognito {
|
||||
connectContactViaAddress_(contact, dismiss: dismiss, incognito: incognito, cleanup: cleanup)
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfileConnectContactViaAddress(contact: contact))
|
||||
await MainActor.run {
|
||||
showActionSheet(.askCurrentOrIncognitoProfileConnectContactViaAddress(contact: contact))
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .groupLink(glp):
|
||||
switch glp {
|
||||
case .ok:
|
||||
if let incognito = incognito {
|
||||
showAlert(.groupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Join group"))
|
||||
await MainActor.run {
|
||||
if let incognito = incognito {
|
||||
showAlert(.groupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Join group"))
|
||||
}
|
||||
}
|
||||
case let .ownLink(groupInfo):
|
||||
logger.debug("planAndConnect, .groupLink, .ownLink, incognito=\(incognito?.description ?? "nil")")
|
||||
if let f = filterKnownGroup {
|
||||
f(groupInfo)
|
||||
await MainActor.run {
|
||||
if let f = filterKnownGroup {
|
||||
f(groupInfo)
|
||||
}
|
||||
showActionSheet(.ownGroupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito, groupInfo: groupInfo))
|
||||
}
|
||||
showActionSheet(.ownGroupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito, groupInfo: groupInfo))
|
||||
case .connectingConfirmReconnect:
|
||||
logger.debug("planAndConnect, .groupLink, .connectingConfirmReconnect, incognito=\(incognito?.description ?? "nil")")
|
||||
if let incognito = incognito {
|
||||
showAlert(.groupLinkConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You are already joining the group!\nRepeat join request?"))
|
||||
await MainActor.run {
|
||||
if let incognito = incognito {
|
||||
showAlert(.groupLinkConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You are already joining the group!\nRepeat join request?"))
|
||||
}
|
||||
}
|
||||
case let .connectingProhibit(groupInfo_):
|
||||
logger.debug("planAndConnect, .groupLink, .connectingProhibit, incognito=\(incognito?.description ?? "nil")")
|
||||
showAlert(.groupLinkConnecting(connectionLink: connectionLink, groupInfo: groupInfo_))
|
||||
await MainActor.run {
|
||||
showAlert(.groupLinkConnecting(connectionLink: connectionLink, groupInfo: groupInfo_))
|
||||
}
|
||||
case let .known(groupInfo):
|
||||
logger.debug("planAndConnect, .groupLink, .known, incognito=\(incognito?.description ?? "nil")")
|
||||
if let f = filterKnownGroup {
|
||||
f(groupInfo)
|
||||
} else {
|
||||
openKnownGroup(groupInfo, dismiss: dismiss) { AlertManager.shared.showAlert(groupAlreadyExistsAlert(groupInfo)) }
|
||||
await MainActor.run {
|
||||
if let f = filterKnownGroup {
|
||||
f(groupInfo)
|
||||
} else {
|
||||
openKnownGroup(groupInfo, dismiss: dismiss) { AlertManager.shared.showAlert(groupAlreadyExistsAlert(groupInfo)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .error(chatError):
|
||||
logger.debug("planAndConnect, .error \(chatErrorString(chatError))")
|
||||
if let incognito = incognito {
|
||||
connectViaLink(connectionLink, connectionPlan: nil, dismiss: dismiss, incognito: incognito, cleanup: cleanup)
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: nil, title: "Connect via link"))
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
logger.debug("planAndConnect, plan error")
|
||||
if let incognito = incognito {
|
||||
connectViaLink(connectionLink, connectionPlan: nil, dismiss: dismiss, incognito: incognito, cleanup: cleanup)
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: nil, title: "Connect via link"))
|
||||
} else if let alert {
|
||||
await MainActor.run {
|
||||
showAlert(.error(shortOrFullLink: shortOrFullLink, alert: alert))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1161,22 +1202,22 @@ private func connectContactViaAddress_(_ contact: Contact, dismiss: Bool, incogn
|
|||
}
|
||||
|
||||
private func connectViaLink(
|
||||
_ connectionLink: String,
|
||||
_ connectionLink: CreatedConnLink,
|
||||
connectionPlan: ConnectionPlan?,
|
||||
dismiss: Bool,
|
||||
incognito: Bool,
|
||||
cleanup: (() -> Void)?
|
||||
) {
|
||||
Task {
|
||||
if let (connReqType, pcc) = await apiConnect(incognito: incognito, connReq: connectionLink) {
|
||||
if let (connReqType, pcc) = await apiConnect(incognito: incognito, connLink: connectionLink) {
|
||||
await MainActor.run {
|
||||
ChatModel.shared.updateContactConnection(pcc)
|
||||
}
|
||||
let crt: ConnReqType
|
||||
if let plan = connectionPlan {
|
||||
crt = planToConnReqType(plan)
|
||||
crt = if let plan = connectionPlan {
|
||||
planToConnReqType(plan) ?? connReqType
|
||||
} else {
|
||||
crt = connReqType
|
||||
connReqType
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
if dismiss {
|
||||
|
@ -1199,43 +1240,35 @@ private func connectViaLink(
|
|||
}
|
||||
|
||||
func openKnownContact(_ contact: Contact, dismiss: Bool, showAlreadyExistsAlert: (() -> Void)?) {
|
||||
Task {
|
||||
let m = ChatModel.shared
|
||||
if let c = m.getContactChat(contact.contactId) {
|
||||
DispatchQueue.main.async {
|
||||
if dismiss {
|
||||
dismissAllSheets(animated: true) {
|
||||
ItemsModel.shared.loadOpenChat(c.id) {
|
||||
showAlreadyExistsAlert?()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ItemsModel.shared.loadOpenChat(c.id) {
|
||||
showAlreadyExistsAlert?()
|
||||
}
|
||||
let m = ChatModel.shared
|
||||
if let c = m.getContactChat(contact.contactId) {
|
||||
if dismiss {
|
||||
dismissAllSheets(animated: true) {
|
||||
ItemsModel.shared.loadOpenChat(c.id) {
|
||||
showAlreadyExistsAlert?()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ItemsModel.shared.loadOpenChat(c.id) {
|
||||
showAlreadyExistsAlert?()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func openKnownGroup(_ groupInfo: GroupInfo, dismiss: Bool, showAlreadyExistsAlert: (() -> Void)?) {
|
||||
Task {
|
||||
let m = ChatModel.shared
|
||||
if let g = m.getGroupChat(groupInfo.groupId) {
|
||||
DispatchQueue.main.async {
|
||||
if dismiss {
|
||||
dismissAllSheets(animated: true) {
|
||||
ItemsModel.shared.loadOpenChat(g.id) {
|
||||
showAlreadyExistsAlert?()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ItemsModel.shared.loadOpenChat(g.id) {
|
||||
showAlreadyExistsAlert?()
|
||||
}
|
||||
let m = ChatModel.shared
|
||||
if let g = m.getGroupChat(groupInfo.groupId) {
|
||||
if dismiss {
|
||||
dismissAllSheets(animated: true) {
|
||||
ItemsModel.shared.loadOpenChat(g.id) {
|
||||
showAlreadyExistsAlert?()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ItemsModel.shared.loadOpenChat(g.id) {
|
||||
showAlreadyExistsAlert?()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1273,11 +1306,12 @@ enum ConnReqType: Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
private func planToConnReqType(_ connectionPlan: ConnectionPlan) -> ConnReqType {
|
||||
private func planToConnReqType(_ connectionPlan: ConnectionPlan) -> ConnReqType? {
|
||||
switch connectionPlan {
|
||||
case .invitationLink: return .invitation
|
||||
case .contactAddress: return .contact
|
||||
case .groupLink: return .groupLink
|
||||
case .invitationLink: .invitation
|
||||
case .contactAddress: .contact
|
||||
case .groupLink: .groupLink
|
||||
case .error: nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import CoreImage.CIFilterBuiltins
|
||||
import SimpleXChat
|
||||
|
||||
struct MutableQRCode: View {
|
||||
@Binding var uri: String
|
||||
|
@ -20,6 +21,16 @@ struct MutableQRCode: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct SimpleXCreatedLinkQRCode: View {
|
||||
let link: CreatedConnLink
|
||||
@Binding var short: Bool
|
||||
var onShare: (() -> Void)? = nil
|
||||
|
||||
var body: some View {
|
||||
QRCode(uri: link.simplexChatUri(short: short), onShare: onShare)
|
||||
}
|
||||
}
|
||||
|
||||
struct SimpleXLinkQRCode: View {
|
||||
let uri: String
|
||||
var withLogo: Bool = true
|
||||
|
@ -31,12 +42,6 @@ struct SimpleXLinkQRCode: View {
|
|||
}
|
||||
}
|
||||
|
||||
func simplexChatLink(_ uri: String) -> String {
|
||||
uri.starts(with: "simplex:/")
|
||||
? uri.replacingOccurrences(of: "simplex:/", with: "https://simplex.chat/")
|
||||
: uri
|
||||
}
|
||||
|
||||
struct QRCode: View {
|
||||
let uri: String
|
||||
var withLogo: Bool = true
|
||||
|
|
|
@ -31,7 +31,7 @@ struct CreateSimpleXAddress: View {
|
|||
Spacer()
|
||||
|
||||
if let userAddress = m.userAddress {
|
||||
SimpleXLinkQRCode(uri: userAddress.connReqContact)
|
||||
SimpleXCreatedLinkQRCode(link: userAddress.connLinkContact, short: Binding.constant(false))
|
||||
.frame(maxHeight: g.size.width)
|
||||
shareQRCodeButton(userAddress)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
@ -77,9 +77,9 @@ struct CreateSimpleXAddress: View {
|
|||
progressIndicator = true
|
||||
Task {
|
||||
do {
|
||||
let connReqContact = try await apiCreateUserAddress()
|
||||
let connLinkContact = try await apiCreateUserAddress(short: false)
|
||||
DispatchQueue.main.async {
|
||||
m.userAddress = UserContactLink(connReqContact: connReqContact)
|
||||
m.userAddress = UserContactLink(connLinkContact: connLinkContact)
|
||||
}
|
||||
await MainActor.run { progressIndicator = false }
|
||||
} catch let error {
|
||||
|
@ -121,7 +121,7 @@ struct CreateSimpleXAddress: View {
|
|||
|
||||
private func shareQRCodeButton(_ userAddress: UserContactLink) -> some View {
|
||||
Button {
|
||||
showShareSheet(items: [simplexChatLink(userAddress.connReqContact)])
|
||||
showShareSheet(items: [simplexChatLink(userAddress.connLinkContact.simplexChatUri(short: false))])
|
||||
} label: {
|
||||
Label("Share", systemImage: "square.and.arrow.up")
|
||||
}
|
||||
|
@ -189,7 +189,7 @@ struct SendAddressMailView: View {
|
|||
let messageBody = String(format: NSLocalizedString("""
|
||||
<p>Hi!</p>
|
||||
<p><a href="%@">Connect to me via SimpleX Chat</a></p>
|
||||
""", comment: "email text"), simplexChatLink(userAddress.connReqContact))
|
||||
""", comment: "email text"), simplexChatLink(userAddress.connLinkContact.simplexChatUri(short: false)))
|
||||
MailView(
|
||||
isShowing: self.$showMailView,
|
||||
result: $mailViewResult,
|
||||
|
|
|
@ -20,6 +20,8 @@ struct PrivacySettings: View {
|
|||
@AppStorage(GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES, store: groupDefaults) private var encryptLocalFiles = true
|
||||
@AppStorage(GROUP_DEFAULT_PRIVACY_ASK_TO_APPROVE_RELAYS, store: groupDefaults) private var askToApproveRelays = true
|
||||
@State private var simplexLinkMode = privacySimplexLinkModeDefault.get()
|
||||
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
|
||||
@AppStorage(DEFAULT_PRIVACY_SHORT_LINKS) private var shortSimplexLinks = false
|
||||
@AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false
|
||||
@AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false
|
||||
@State private var currentLAMode = privacyLocalAuthModeDefault.get()
|
||||
|
@ -111,6 +113,11 @@ struct PrivacySettings: View {
|
|||
.onChange(of: simplexLinkMode) { mode in
|
||||
privacySimplexLinkModeDefault.set(mode)
|
||||
}
|
||||
if developerTools {
|
||||
settingsRow("link.badge.plus", color: theme.colors.secondary) {
|
||||
Toggle("Use short links (BETA)", isOn: $shortSimplexLinks)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("Chats")
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
|
|
|
@ -33,6 +33,7 @@ let DEFAULT_PRIVACY_CHAT_LIST_OPEN_LINKS = "privacyChatListOpenLinks"
|
|||
let DEFAULT_PRIVACY_SIMPLEX_LINK_MODE = "privacySimplexLinkMode"
|
||||
let DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS = "privacyShowChatPreviews"
|
||||
let DEFAULT_PRIVACY_SAVE_LAST_DRAFT = "privacySaveLastDraft"
|
||||
let DEFAULT_PRIVACY_SHORT_LINKS = "privacyShortLinks"
|
||||
let DEFAULT_PRIVACY_PROTECT_SCREEN = "privacyProtectScreen"
|
||||
let DEFAULT_PRIVACY_DELIVERY_RECEIPTS_SET = "privacyDeliveryReceiptsSet"
|
||||
let DEFAULT_PRIVACY_MEDIA_BLUR_RADIUS = "privacyMediaBlurRadius"
|
||||
|
@ -99,6 +100,7 @@ let appDefaults: [String: Any] = [
|
|||
DEFAULT_PRIVACY_SIMPLEX_LINK_MODE: SimpleXLinkMode.description.rawValue,
|
||||
DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS: true,
|
||||
DEFAULT_PRIVACY_SAVE_LAST_DRAFT: true,
|
||||
DEFAULT_PRIVACY_SHORT_LINKS: false,
|
||||
DEFAULT_PRIVACY_PROTECT_SCREEN: false,
|
||||
DEFAULT_PRIVACY_DELIVERY_RECEIPTS_SET: false,
|
||||
DEFAULT_PRIVACY_MEDIA_BLUR_RADIUS: 0,
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import MessageUI
|
||||
import SimpleXChat
|
||||
@preconcurrency import SimpleXChat
|
||||
|
||||
struct UserAddressView: View {
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
|
@ -16,6 +16,7 @@ struct UserAddressView: View {
|
|||
@EnvironmentObject var theme: AppTheme
|
||||
@State var shareViaProfile = false
|
||||
@State var autoCreate = false
|
||||
@State private var showShortLink = true
|
||||
@State private var aas = AutoAcceptState()
|
||||
@State private var savedAAS = AutoAcceptState()
|
||||
@State private var showMailView = false
|
||||
|
@ -135,8 +136,8 @@ struct UserAddressView: View {
|
|||
|
||||
@ViewBuilder private func existingAddressView(_ userAddress: UserContactLink) -> some View {
|
||||
Section {
|
||||
SimpleXLinkQRCode(uri: userAddress.connReqContact)
|
||||
.id("simplex-contact-address-qrcode-\(userAddress.connReqContact)")
|
||||
SimpleXCreatedLinkQRCode(link: userAddress.connLinkContact, short: $showShortLink)
|
||||
.id("simplex-contact-address-qrcode-\(userAddress.connLinkContact.simplexChatUri(short: showShortLink))")
|
||||
shareQRCodeButton(userAddress)
|
||||
// if MFMailComposeViewController.canSendMail() {
|
||||
// shareViaEmailButton(userAddress)
|
||||
|
@ -153,8 +154,7 @@ struct UserAddressView: View {
|
|||
}
|
||||
addressSettingsButton(userAddress)
|
||||
} header: {
|
||||
Text("For social media")
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
ToggleShortLinkHeader(text: Text("For social media"), link: userAddress.connLinkContact, short: $showShortLink)
|
||||
} footer: {
|
||||
if aas.business {
|
||||
Text("Add your team members to the conversations.")
|
||||
|
@ -193,9 +193,10 @@ struct UserAddressView: View {
|
|||
progressIndicator = true
|
||||
Task {
|
||||
do {
|
||||
let connReqContact = try await apiCreateUserAddress()
|
||||
let short = UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS)
|
||||
let connLinkContact = try await apiCreateUserAddress(short: short)
|
||||
DispatchQueue.main.async {
|
||||
chatModel.userAddress = UserContactLink(connReqContact: connReqContact)
|
||||
chatModel.userAddress = UserContactLink(connLinkContact: connLinkContact)
|
||||
alert = .shareOnCreate
|
||||
progressIndicator = false
|
||||
}
|
||||
|
@ -231,7 +232,7 @@ struct UserAddressView: View {
|
|||
|
||||
private func shareQRCodeButton(_ userAddress: UserContactLink) -> some View {
|
||||
Button {
|
||||
showShareSheet(items: [simplexChatLink(userAddress.connReqContact)])
|
||||
showShareSheet(items: [simplexChatLink(userAddress.connLinkContact.simplexChatUri(short: showShortLink))])
|
||||
} label: {
|
||||
settingsRow("square.and.arrow.up", color: theme.colors.secondary) {
|
||||
Text("Share address")
|
||||
|
@ -294,6 +295,28 @@ struct UserAddressView: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct ToggleShortLinkHeader: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
let text: Text
|
||||
var link: CreatedConnLink
|
||||
@Binding var short: Bool
|
||||
|
||||
var body: some View {
|
||||
if link.connShortLink == nil {
|
||||
text.foregroundColor(theme.colors.secondary)
|
||||
} else {
|
||||
HStack {
|
||||
text.foregroundColor(theme.colors.secondary)
|
||||
Spacer()
|
||||
Text(short ? "Full link" : "Short link")
|
||||
.textCase(.none)
|
||||
.foregroundColor(theme.colors.primary)
|
||||
.onTapGesture { short.toggle() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct AutoAcceptState: Equatable {
|
||||
var enable = false
|
||||
var incognito = false
|
||||
|
@ -542,7 +565,7 @@ private func saveAAS(_ aas: Binding<AutoAcceptState>, _ savedAAS: Binding<AutoAc
|
|||
struct UserAddressView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let chatModel = ChatModel()
|
||||
chatModel.userAddress = UserContactLink(connReqContact: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D")
|
||||
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))
|
||||
|
||||
|
||||
return Group {
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
<string>applinks:simplex.chat</string>
|
||||
<string>applinks:www.simplex.chat</string>
|
||||
<string>applinks:simplex.chat?mode=developer</string>
|
||||
<string>applinks:*.simplex.im</string>
|
||||
<string>applinks:*.simplex.im?mode=developer</string>
|
||||
<string>applinks:*.simplexonflux.com</string>
|
||||
<string>applinks:*.simplexonflux.com?mode=developer</string>
|
||||
</array>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
|
|
|
@ -324,7 +324,7 @@ public func responseError(_ err: Error) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
func chatErrorString(_ err: ChatError) -> String {
|
||||
public func chatErrorString(_ err: ChatError) -> String {
|
||||
if case let .invalidJSON(json) = err { return json }
|
||||
return String(describing: err)
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ public enum ChatCommand {
|
|||
case apiLeaveGroup(groupId: Int64)
|
||||
case apiListMembers(groupId: Int64)
|
||||
case apiUpdateGroupProfile(groupId: Int64, groupProfile: GroupProfile)
|
||||
case apiCreateGroupLink(groupId: Int64, memberRole: GroupMemberRole)
|
||||
case apiCreateGroupLink(groupId: Int64, memberRole: GroupMemberRole, short: Bool)
|
||||
case apiGroupLinkMemberRole(groupId: Int64, memberRole: GroupMemberRole)
|
||||
case apiDeleteGroupLink(groupId: Int64)
|
||||
case apiGetGroupLink(groupId: Int64)
|
||||
|
@ -116,11 +116,11 @@ public enum ChatCommand {
|
|||
case apiGetGroupMemberCode(groupId: Int64, groupMemberId: Int64)
|
||||
case apiVerifyContact(contactId: Int64, connectionCode: String?)
|
||||
case apiVerifyGroupMember(groupId: Int64, groupMemberId: Int64, connectionCode: String?)
|
||||
case apiAddContact(userId: Int64, incognito: Bool)
|
||||
case apiAddContact(userId: Int64, short: Bool, incognito: Bool)
|
||||
case apiSetConnectionIncognito(connId: Int64, incognito: Bool)
|
||||
case apiChangeConnectionUser(connId: Int64, userId: Int64)
|
||||
case apiConnectPlan(userId: Int64, connReq: String)
|
||||
case apiConnect(userId: Int64, incognito: Bool, connReq: String)
|
||||
case apiConnectPlan(userId: Int64, connLink: String)
|
||||
case apiConnect(userId: Int64, incognito: Bool, connLink: CreatedConnLink)
|
||||
case apiConnectContactViaAddress(userId: Int64, incognito: Bool, contactId: Int64)
|
||||
case apiDeleteChat(type: ChatType, id: Int64, chatDeleteMode: ChatDeleteMode)
|
||||
case apiClearChat(type: ChatType, id: Int64)
|
||||
|
@ -132,7 +132,7 @@ public enum ChatCommand {
|
|||
case apiSetConnectionAlias(connId: Int64, localAlias: String)
|
||||
case apiSetUserUIThemes(userId: Int64, themes: ThemeModeOverrides?)
|
||||
case apiSetChatUIThemes(chatId: String, themes: ThemeModeOverrides?)
|
||||
case apiCreateMyAddress(userId: Int64)
|
||||
case apiCreateMyAddress(userId: Int64, short: Bool)
|
||||
case apiDeleteMyAddress(userId: Int64)
|
||||
case apiShowMyAddress(userId: Int64)
|
||||
case apiSetProfileAddress(userId: Int64, on: Bool)
|
||||
|
@ -256,7 +256,7 @@ public enum ChatCommand {
|
|||
case let .apiLeaveGroup(groupId): return "/_leave #\(groupId)"
|
||||
case let .apiListMembers(groupId): return "/_members #\(groupId)"
|
||||
case let .apiUpdateGroupProfile(groupId, groupProfile): return "/_group_profile #\(groupId) \(encodeJSON(groupProfile))"
|
||||
case let .apiCreateGroupLink(groupId, memberRole): return "/_create link #\(groupId) \(memberRole)"
|
||||
case let .apiCreateGroupLink(groupId, memberRole, short): return "/_create link #\(groupId) \(memberRole) short=\(onOff(short))"
|
||||
case let .apiGroupLinkMemberRole(groupId, memberRole): return "/_set link role #\(groupId) \(memberRole)"
|
||||
case let .apiDeleteGroupLink(groupId): return "/_delete link #\(groupId)"
|
||||
case let .apiGetGroupLink(groupId): return "/_get link #\(groupId)"
|
||||
|
@ -305,11 +305,11 @@ public enum ChatCommand {
|
|||
case let .apiVerifyContact(contactId, .none): return "/_verify code @\(contactId)"
|
||||
case let .apiVerifyGroupMember(groupId, groupMemberId, .some(connectionCode)): return "/_verify code #\(groupId) \(groupMemberId) \(connectionCode)"
|
||||
case let .apiVerifyGroupMember(groupId, groupMemberId, .none): return "/_verify code #\(groupId) \(groupMemberId)"
|
||||
case let .apiAddContact(userId, incognito): return "/_connect \(userId) incognito=\(onOff(incognito))"
|
||||
case let .apiAddContact(userId, short, incognito): return "/_connect \(userId) short=\(onOff(short)) incognito=\(onOff(incognito))"
|
||||
case let .apiSetConnectionIncognito(connId, incognito): return "/_set incognito :\(connId) \(onOff(incognito))"
|
||||
case let .apiChangeConnectionUser(connId, userId): return "/_set conn user :\(connId) \(userId)"
|
||||
case let .apiConnectPlan(userId, connReq): return "/_connect plan \(userId) \(connReq)"
|
||||
case let .apiConnect(userId, incognito, connReq): return "/_connect \(userId) incognito=\(onOff(incognito)) \(connReq)"
|
||||
case let .apiConnectPlan(userId, connLink): return "/_connect plan \(userId) \(connLink)"
|
||||
case let .apiConnect(userId, incognito, connLink): return "/_connect \(userId) incognito=\(onOff(incognito)) \(connLink.connFullLink) \(connLink.connShortLink ?? "")"
|
||||
case let .apiConnectContactViaAddress(userId, incognito, contactId): return "/_connect contact \(userId) incognito=\(onOff(incognito)) \(contactId)"
|
||||
case let .apiDeleteChat(type, id, chatDeleteMode): return "/_delete \(ref(type, id)) \(chatDeleteMode.cmdString)"
|
||||
case let .apiClearChat(type, id): return "/_clear chat \(ref(type, id))"
|
||||
|
@ -321,7 +321,7 @@ public enum ChatCommand {
|
|||
case let .apiSetConnectionAlias(connId, localAlias): return "/_set alias :\(connId) \(localAlias.trimmingCharacters(in: .whitespaces))"
|
||||
case let .apiSetUserUIThemes(userId, themes): return "/_set theme user \(userId) \(themes != nil ? encodeJSON(themes) : "")"
|
||||
case let .apiSetChatUIThemes(chatId, themes): return "/_set theme \(chatId) \(themes != nil ? encodeJSON(themes) : "")"
|
||||
case let .apiCreateMyAddress(userId): return "/_address \(userId)"
|
||||
case let .apiCreateMyAddress(userId, short): return "/_address \(userId) short=\(onOff(short))"
|
||||
case let .apiDeleteMyAddress(userId): return "/_delete_address \(userId)"
|
||||
case let .apiShowMyAddress(userId): return "/_show_address \(userId)"
|
||||
case let .apiSetProfileAddress(userId, on): return "/_profile_address \(userId) \(onOff(on))"
|
||||
|
@ -629,10 +629,10 @@ public enum ChatResponse: Decodable, Error {
|
|||
case groupMemberCode(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionCode: String)
|
||||
case connectionVerified(user: UserRef, verified: Bool, expectedCode: String)
|
||||
case tagsUpdated(user: UserRef, userTags: [ChatTag], chatTags: [Int64])
|
||||
case invitation(user: UserRef, connReqInvitation: String, connection: PendingContactConnection)
|
||||
case invitation(user: UserRef, connLinkInvitation: CreatedConnLink, connection: PendingContactConnection)
|
||||
case connectionIncognitoUpdated(user: UserRef, toConnection: PendingContactConnection)
|
||||
case connectionUserChanged(user: UserRef, fromConnection: PendingContactConnection, toConnection: PendingContactConnection, newUser: UserRef)
|
||||
case connectionPlan(user: UserRef, connectionPlan: ConnectionPlan)
|
||||
case connectionPlan(user: UserRef, connLink: CreatedConnLink, connectionPlan: ConnectionPlan)
|
||||
case sentConfirmation(user: UserRef, connection: PendingContactConnection)
|
||||
case sentInvitation(user: UserRef, connection: PendingContactConnection)
|
||||
case sentInvitationToContact(user: UserRef, contact: Contact, customUserProfile: Profile?)
|
||||
|
@ -649,7 +649,7 @@ public enum ChatResponse: Decodable, Error {
|
|||
case contactPrefsUpdated(user: User, fromContact: Contact, toContact: Contact)
|
||||
case userContactLink(user: User, contactLink: UserContactLink)
|
||||
case userContactLinkUpdated(user: User, contactLink: UserContactLink)
|
||||
case userContactLinkCreated(user: User, connReqContact: String)
|
||||
case userContactLinkCreated(user: User, connLinkContact: CreatedConnLink)
|
||||
case userContactLinkDeleted(user: User)
|
||||
case contactConnected(user: UserRef, contact: Contact, userCustomProfile: Profile?)
|
||||
case contactConnecting(user: UserRef, contact: Contact)
|
||||
|
@ -702,8 +702,8 @@ public enum ChatResponse: Decodable, Error {
|
|||
case connectedToGroupMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember, memberContact: Contact?)
|
||||
case groupRemoved(user: UserRef, groupInfo: GroupInfo) // unused
|
||||
case groupUpdated(user: UserRef, toGroup: GroupInfo)
|
||||
case groupLinkCreated(user: UserRef, groupInfo: GroupInfo, connReqContact: String, memberRole: GroupMemberRole)
|
||||
case groupLink(user: UserRef, groupInfo: GroupInfo, connReqContact: String, memberRole: GroupMemberRole)
|
||||
case groupLinkCreated(user: UserRef, groupInfo: GroupInfo, connLinkContact: CreatedConnLink, memberRole: GroupMemberRole)
|
||||
case groupLink(user: UserRef, groupInfo: GroupInfo, connLinkContact: CreatedConnLink, memberRole: GroupMemberRole)
|
||||
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)
|
||||
|
@ -989,10 +989,10 @@ public enum ChatResponse: Decodable, Error {
|
|||
case let .groupMemberCode(u, groupInfo, member, connectionCode): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionCode: \(connectionCode)")
|
||||
case let .connectionVerified(u, verified, expectedCode): return withUser(u, "verified: \(verified)\nconnectionCode: \(expectedCode)")
|
||||
case let .tagsUpdated(u, userTags, chatTags): return withUser(u, "userTags: \(String(describing: userTags))\nchatTags: \(String(describing: chatTags))")
|
||||
case let .invitation(u, connReqInvitation, connection): return withUser(u, "connReqInvitation: \(connReqInvitation)\nconnection: \(connection)")
|
||||
case let .invitation(u, connLinkInvitation, connection): return withUser(u, "connLinkInvitation: \(connLinkInvitation)\nconnection: \(connection)")
|
||||
case let .connectionIncognitoUpdated(u, toConnection): return withUser(u, String(describing: toConnection))
|
||||
case let .connectionUserChanged(u, fromConnection, toConnection, newUser): return withUser(u, "fromConnection: \(String(describing: fromConnection))\ntoConnection: \(String(describing: toConnection))\newUserId: \(String(describing: newUser.userId))")
|
||||
case let .connectionPlan(u, connectionPlan): return withUser(u, String(describing: connectionPlan))
|
||||
case let .connectionUserChanged(u, fromConnection, toConnection, newUser): return withUser(u, "fromConnection: \(String(describing: fromConnection))\ntoConnection: \(String(describing: toConnection))\nnewUserId: \(String(describing: newUser.userId))")
|
||||
case let .connectionPlan(u, connLink, connectionPlan): return withUser(u, "connLink: \(String(describing: connLink))\nconnectionPlan: \(String(describing: connectionPlan))")
|
||||
case let .sentConfirmation(u, connection): return withUser(u, String(describing: connection))
|
||||
case let .sentInvitation(u, connection): return withUser(u, String(describing: connection))
|
||||
case let .sentInvitationToContact(u, contact, _): return withUser(u, String(describing: contact))
|
||||
|
@ -1009,7 +1009,7 @@ public enum ChatResponse: Decodable, Error {
|
|||
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 .userContactLinkCreated(u, connReq): return withUser(u, connReq)
|
||||
case let .userContactLinkCreated(u, connLink): return withUser(u, String(describing: connLink))
|
||||
case .userContactLinkDeleted: return noDetails
|
||||
case let .contactConnected(u, contact, _): return withUser(u, String(describing: contact))
|
||||
case let .contactConnecting(u, contact): return withUser(u, String(describing: contact))
|
||||
|
@ -1069,8 +1069,8 @@ public enum ChatResponse: Decodable, Error {
|
|||
case let .connectedToGroupMember(u, groupInfo, member, memberContact): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\nmemberContact: \(String(describing: memberContact))")
|
||||
case let .groupRemoved(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
||||
case let .groupUpdated(u, toGroup): return withUser(u, String(describing: toGroup))
|
||||
case let .groupLinkCreated(u, groupInfo, connReqContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnReqContact: \(connReqContact)\nmemberRole: \(memberRole)")
|
||||
case let .groupLink(u, groupInfo, connReqContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnReqContact: \(connReqContact)\nmemberRole: \(memberRole)")
|
||||
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 .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)")
|
||||
|
@ -1173,10 +1173,31 @@ public enum ChatDeleteMode: Codable {
|
|||
}
|
||||
}
|
||||
|
||||
public struct CreatedConnLink: Decodable, Hashable {
|
||||
public var connFullLink: String
|
||||
public var connShortLink: String?
|
||||
|
||||
public init(connFullLink: String, connShortLink: String?) {
|
||||
self.connFullLink = connFullLink
|
||||
self.connShortLink = connShortLink
|
||||
}
|
||||
|
||||
public func simplexChatUri(short: Bool = true) -> String {
|
||||
short ? (connShortLink ?? simplexChatLink(connFullLink)) : simplexChatLink(connFullLink)
|
||||
}
|
||||
}
|
||||
|
||||
public func simplexChatLink(_ uri: String) -> String {
|
||||
uri.starts(with: "simplex:/")
|
||||
? uri.replacingOccurrences(of: "simplex:/", with: "https://simplex.chat/")
|
||||
: uri
|
||||
}
|
||||
|
||||
public enum ConnectionPlan: Decodable, Hashable {
|
||||
case invitationLink(invitationLinkPlan: InvitationLinkPlan)
|
||||
case contactAddress(contactAddressPlan: ContactAddressPlan)
|
||||
case groupLink(groupLinkPlan: GroupLinkPlan)
|
||||
case error(chatError: ChatError)
|
||||
}
|
||||
|
||||
public enum InvitationLinkPlan: Decodable, Hashable {
|
||||
|
@ -2183,16 +2204,16 @@ public enum RatchetSyncState: String, Decodable {
|
|||
}
|
||||
|
||||
public struct UserContactLink: Decodable, Hashable {
|
||||
public var connReqContact: String
|
||||
public var connLinkContact: CreatedConnLink
|
||||
public var autoAccept: AutoAccept?
|
||||
|
||||
public init(connReqContact: String, autoAccept: AutoAccept? = nil) {
|
||||
self.connReqContact = connReqContact
|
||||
public init(connLinkContact: CreatedConnLink, autoAccept: AutoAccept? = nil) {
|
||||
self.connLinkContact = connLinkContact
|
||||
self.autoAccept = autoAccept
|
||||
}
|
||||
|
||||
var responseDetails: String {
|
||||
"connReqContact: \(connReqContact)\nautoAccept: \(AutoAccept.cmdString(autoAccept))"
|
||||
"connLinkContact: \(connLinkContact)\nautoAccept: \(AutoAccept.cmdString(autoAccept))"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2404,8 +2425,8 @@ public enum ChatErrorType: Decodable, Hashable {
|
|||
case chatNotStarted
|
||||
case chatNotStopped
|
||||
case chatStoreChanged
|
||||
case connectionPlan(connectionPlan: ConnectionPlan)
|
||||
case invalidConnReq
|
||||
case unsupportedConnReq
|
||||
case invalidChatMessage(connection: Connection, message: String)
|
||||
case contactNotReady(contact: Contact)
|
||||
case contactNotActive(contact: Contact)
|
||||
|
@ -2521,6 +2542,7 @@ public enum StoreError: Decodable, Hashable {
|
|||
case hostMemberIdNotFound(groupId: Int64)
|
||||
case contactNotFoundByFileId(fileId: Int64)
|
||||
case noGroupSndStatus(itemId: Int64, groupMemberId: Int64)
|
||||
case dBException(message: String)
|
||||
}
|
||||
|
||||
public enum DatabaseError: Decodable, Hashable {
|
||||
|
|
|
@ -1853,7 +1853,7 @@ public struct PendingContactConnection: Decodable, NamedChat, Hashable {
|
|||
public var viaContactUri: Bool
|
||||
public var groupLinkId: String?
|
||||
public var customUserProfileId: Int64?
|
||||
public var connReqInv: String?
|
||||
public var connLinkInv: CreatedConnLink?
|
||||
public var localAlias: String
|
||||
var createdAt: Date
|
||||
public var updatedAt: Date
|
||||
|
@ -4063,12 +4063,14 @@ public enum SimplexLinkType: String, Decodable, Hashable {
|
|||
case contact
|
||||
case invitation
|
||||
case group
|
||||
case channel
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .contact: return NSLocalizedString("SimpleX contact address", comment: "simplex link type")
|
||||
case .invitation: return NSLocalizedString("SimpleX one-time invitation", comment: "simplex link type")
|
||||
case .group: return NSLocalizedString("SimpleX group link", comment: "simplex link type")
|
||||
case .channel: return NSLocalizedString("SimpleX channel link", comment: "simplex link type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,8 +77,33 @@
|
|||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="simplex.chat" />
|
||||
<data android:host="smp4.simplex.im" />
|
||||
<data android:host="smp5.simplex.im" />
|
||||
<data android:host="smp6.simplex.im" />
|
||||
<data android:host="smp7.simplex.im" /> <!-- TODO remove -->
|
||||
<data android:host="smp8.simplex.im" />
|
||||
<data android:host="smp9.simplex.im" />
|
||||
<data android:host="smp10.simplex.im" />
|
||||
<data android:host="smp11.simplex.im" />
|
||||
<data android:host="smp12.simplex.im" />
|
||||
<data android:host="smp14.simplex.im" />
|
||||
<data android:host="smp15.simplex.im" />
|
||||
<data android:host="smp16.simplex.im" />
|
||||
<data android:host="smp17.simplex.im" />
|
||||
<data android:host="smp18.simplex.im" />
|
||||
<data android:host="smp19.simplex.im" />
|
||||
<data android:host="smp1.simplexonflux.com" />
|
||||
<data android:host="smp2.simplexonflux.com" />
|
||||
<data android:host="smp3.simplexonflux.com" />
|
||||
<data android:host="smp4.simplexonflux.com" />
|
||||
<data android:host="smp5.simplexonflux.com" />
|
||||
<data android:host="smp6.simplexonflux.com" />
|
||||
<data android:pathPrefix="/invitation" />
|
||||
<data android:pathPrefix="/contact" />
|
||||
<data android:pathPrefix="/a" />
|
||||
<data android:pathPrefix="/c" />
|
||||
<data android:pathPrefix="/g" />
|
||||
<data android:pathPrefix="/i" />
|
||||
</intent-filter>
|
||||
<!-- Receive files from other apps -->
|
||||
<intent-filter>
|
||||
|
|
|
@ -1078,7 +1078,7 @@ interface ChatItemsChangesListener {
|
|||
|
||||
data class ShowingInvitation(
|
||||
val connId: String,
|
||||
val connReq: String,
|
||||
val connLink: CreatedConnLink,
|
||||
val connChatUsed: Boolean,
|
||||
val conn: PendingContactConnection
|
||||
)
|
||||
|
@ -2198,7 +2198,7 @@ class PendingContactConnection(
|
|||
val viaContactUri: Boolean,
|
||||
val groupLinkId: String? = null,
|
||||
val customUserProfileId: Long? = null,
|
||||
val connReqInv: String? = null,
|
||||
val connLinkInv: CreatedConnLink? = null,
|
||||
override val localAlias: String,
|
||||
override val createdAt: Instant,
|
||||
override val updatedAt: Instant
|
||||
|
@ -3968,12 +3968,14 @@ sealed class Format {
|
|||
enum class SimplexLinkType(val linkType: String) {
|
||||
contact("contact"),
|
||||
invitation("invitation"),
|
||||
group("group");
|
||||
group("group"),
|
||||
channel("channel");
|
||||
|
||||
val description: String get() = generalGetString(when (this) {
|
||||
contact -> MR.strings.simplex_link_contact
|
||||
invitation -> MR.strings.simplex_link_invitation
|
||||
group -> MR.strings.simplex_link_group
|
||||
channel -> MR.strings.simplex_link_channel
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@ import androidx.compose.foundation.layout.*
|
|||
import androidx.compose.material.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.Saver
|
||||
import androidx.compose.runtime.saveable.listSaver
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
|
@ -119,6 +121,7 @@ class AppPreferences {
|
|||
)
|
||||
val privacyShowChatPreviews = mkBoolPreference(SHARED_PREFS_PRIVACY_SHOW_CHAT_PREVIEWS, true)
|
||||
val privacySaveLastDraft = mkBoolPreference(SHARED_PREFS_PRIVACY_SAVE_LAST_DRAFT, true)
|
||||
val privacyShortLinks = mkBoolPreference(SHARED_PREFS_PRIVACY_SHORT_LINKS, false)
|
||||
val privacyDeliveryReceiptsSet = mkBoolPreference(SHARED_PREFS_PRIVACY_DELIVERY_RECEIPTS_SET, false)
|
||||
val privacyEncryptLocalFiles = mkBoolPreference(SHARED_PREFS_PRIVACY_ENCRYPT_LOCAL_FILES, true)
|
||||
val privacyAskToApproveRelays = mkBoolPreference(SHARED_PREFS_PRIVACY_ASK_TO_APPROVE_RELAYS, true)
|
||||
|
@ -378,6 +381,7 @@ class AppPreferences {
|
|||
private const val SHARED_PREFS_PRIVACY_SIMPLEX_LINK_MODE = "PrivacySimplexLinkMode"
|
||||
private const val SHARED_PREFS_PRIVACY_SHOW_CHAT_PREVIEWS = "PrivacyShowChatPreviews"
|
||||
private const val SHARED_PREFS_PRIVACY_SAVE_LAST_DRAFT = "PrivacySaveLastDraft"
|
||||
private const val SHARED_PREFS_PRIVACY_SHORT_LINKS = "PrivacyShortLinks"
|
||||
private const val SHARED_PREFS_PRIVACY_DELIVERY_RECEIPTS_SET = "PrivacyDeliveryReceiptsSet"
|
||||
private const val SHARED_PREFS_PRIVACY_ENCRYPT_LOCAL_FILES = "PrivacyEncryptLocalFiles"
|
||||
private const val SHARED_PREFS_PRIVACY_ASK_TO_APPROVE_RELAYS = "PrivacyAskToApproveRelays"
|
||||
|
@ -1364,11 +1368,12 @@ object ChatController {
|
|||
|
||||
|
||||
|
||||
suspend fun apiAddContact(rh: Long?, incognito: Boolean): Pair<Pair<String, PendingContactConnection>?, (() -> Unit)?> {
|
||||
suspend fun apiAddContact(rh: Long?, incognito: Boolean): Pair<Pair<CreatedConnLink, PendingContactConnection>?, (() -> Unit)?> {
|
||||
val userId = try { currentUserId("apiAddContact") } catch (e: Exception) { return null to null }
|
||||
val r = sendCmd(rh, CC.APIAddContact(userId, incognito))
|
||||
val short = appPrefs.privacyShortLinks.get()
|
||||
val r = sendCmd(rh, CC.APIAddContact(userId, short = short, incognito = incognito))
|
||||
return when (r) {
|
||||
is CR.Invitation -> (r.connReqInvitation to r.connection) to null
|
||||
is CR.Invitation -> (r.connLinkInvitation to r.connection) to null
|
||||
else -> {
|
||||
if (!(networkErrorAlert(r))) {
|
||||
return null to { apiErrorAlert("apiAddContact", generalGetString(MR.strings.connection_error), r) }
|
||||
|
@ -1406,34 +1411,45 @@ object ChatController {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun apiConnectPlan(rh: Long?, connReq: String): ConnectionPlan? {
|
||||
suspend fun apiConnectPlan(rh: Long?, connLink: String): Pair<CreatedConnLink, ConnectionPlan>? {
|
||||
val userId = kotlin.runCatching { currentUserId("apiConnectPlan") }.getOrElse { return null }
|
||||
val r = sendCmd(rh, CC.APIConnectPlan(userId, connReq))
|
||||
if (r is CR.CRConnectionPlan) return r.connectionPlan
|
||||
Log.e(TAG, "apiConnectPlan bad response: ${r.responseType} ${r.details}")
|
||||
val r = sendCmd(rh, CC.APIConnectPlan(userId, connLink))
|
||||
if (r is CR.CRConnectionPlan) return r.connLink to r.connectionPlan
|
||||
apiConnectResponseAlert(r)
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiConnect(rh: Long?, incognito: Boolean, connReq: String): PendingContactConnection? {
|
||||
suspend fun apiConnect(rh: Long?, incognito: Boolean, connLink: CreatedConnLink): PendingContactConnection? {
|
||||
val userId = try { currentUserId("apiConnect") } catch (e: Exception) { return null }
|
||||
val r = sendCmd(rh, CC.APIConnect(userId, incognito, connReq))
|
||||
val r = sendCmd(rh, CC.APIConnect(userId, incognito, connLink))
|
||||
when {
|
||||
r is CR.SentConfirmation -> return r.connection
|
||||
r is CR.SentInvitation -> return r.connection
|
||||
r is CR.ContactAlreadyExists -> {
|
||||
r is CR.ContactAlreadyExists ->
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.contact_already_exists),
|
||||
String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), r.contact.displayName)
|
||||
)
|
||||
return null
|
||||
}
|
||||
else -> apiConnectResponseAlert(r)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun apiConnectResponseAlert(r: CR) {
|
||||
when {
|
||||
r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorChat
|
||||
&& r.chatError.errorType is ChatErrorType.InvalidConnReq -> {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.invalid_connection_link),
|
||||
generalGetString(MR.strings.please_check_correct_link_and_maybe_ask_for_a_new_one)
|
||||
)
|
||||
return null
|
||||
}
|
||||
r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorChat
|
||||
&& r.chatError.errorType is ChatErrorType.UnsupportedConnReq -> {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.unsupported_connection_link),
|
||||
generalGetString(MR.strings.link_requires_newer_app_version_please_upgrade)
|
||||
)
|
||||
}
|
||||
r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent
|
||||
&& r.chatError.agentError is AgentErrorType.SMP
|
||||
|
@ -1442,7 +1458,6 @@ object ChatController {
|
|||
generalGetString(MR.strings.connection_error_auth),
|
||||
generalGetString(MR.strings.connection_error_auth_desc)
|
||||
)
|
||||
return null
|
||||
}
|
||||
r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent
|
||||
&& r.chatError.agentError is AgentErrorType.SMP
|
||||
|
@ -1451,7 +1466,6 @@ object ChatController {
|
|||
generalGetString(MR.strings.connection_error_blocked),
|
||||
generalGetString(MR.strings.connection_error_blocked_desc).format(r.chatError.agentError.smpErr.blockInfo.reason.text),
|
||||
)
|
||||
return null
|
||||
}
|
||||
r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent
|
||||
&& r.chatError.agentError is AgentErrorType.SMP
|
||||
|
@ -1460,13 +1474,11 @@ object ChatController {
|
|||
generalGetString(MR.strings.connection_error_quota),
|
||||
generalGetString(MR.strings.connection_error_quota_desc)
|
||||
)
|
||||
return null
|
||||
}
|
||||
else -> {
|
||||
if (!(networkErrorAlert(r))) {
|
||||
apiErrorAlert("apiConnect", generalGetString(MR.strings.connection_error), r)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1619,11 +1631,11 @@ object ChatController {
|
|||
return false
|
||||
}
|
||||
|
||||
suspend fun apiCreateUserAddress(rh: Long?): String? {
|
||||
suspend fun apiCreateUserAddress(rh: Long?, short: Boolean): CreatedConnLink? {
|
||||
val userId = kotlin.runCatching { currentUserId("apiCreateUserAddress") }.getOrElse { return null }
|
||||
val r = sendCmd(rh, CC.ApiCreateMyAddress(userId))
|
||||
val r = sendCmd(rh, CC.ApiCreateMyAddress(userId, short))
|
||||
return when (r) {
|
||||
is CR.UserContactLinkCreated -> r.connReqContact
|
||||
is CR.UserContactLinkCreated -> r.connLinkContact
|
||||
else -> {
|
||||
if (!(networkErrorAlert(r))) {
|
||||
apiErrorAlert("apiCreateUserAddress", generalGetString(MR.strings.error_creating_address), r)
|
||||
|
@ -2060,9 +2072,10 @@ object ChatController {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun apiCreateGroupLink(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): Pair<String, GroupMemberRole>? {
|
||||
return when (val r = sendCmd(rh, CC.APICreateGroupLink(groupId, memberRole))) {
|
||||
is CR.GroupLinkCreated -> r.connReqContact to r.memberRole
|
||||
suspend fun apiCreateGroupLink(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): Pair<CreatedConnLink, GroupMemberRole>? {
|
||||
val short = appPrefs.privacyShortLinks.get()
|
||||
return when (val r = sendCmd(rh, CC.APICreateGroupLink(groupId, memberRole, short))) {
|
||||
is CR.GroupLinkCreated -> r.connLinkContact to r.memberRole
|
||||
else -> {
|
||||
if (!(networkErrorAlert(r))) {
|
||||
apiErrorAlert("apiCreateGroupLink", generalGetString(MR.strings.error_creating_link_for_group), r)
|
||||
|
@ -2072,9 +2085,9 @@ object ChatController {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun apiGroupLinkMemberRole(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): Pair<String, GroupMemberRole>? {
|
||||
suspend fun apiGroupLinkMemberRole(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): Pair<CreatedConnLink, GroupMemberRole>? {
|
||||
return when (val r = sendCmd(rh, CC.APIGroupLinkMemberRole(groupId, memberRole))) {
|
||||
is CR.GroupLink -> r.connReqContact to r.memberRole
|
||||
is CR.GroupLink -> r.connLinkContact to r.memberRole
|
||||
else -> {
|
||||
if (!(networkErrorAlert(r))) {
|
||||
apiErrorAlert("apiGroupLinkMemberRole", generalGetString(MR.strings.error_updating_link_for_group), r)
|
||||
|
@ -2096,9 +2109,9 @@ object ChatController {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun apiGetGroupLink(rh: Long?, groupId: Long): Pair<String, GroupMemberRole>? {
|
||||
suspend fun apiGetGroupLink(rh: Long?, groupId: Long): Pair<CreatedConnLink, GroupMemberRole>? {
|
||||
return when (val r = sendCmd(rh, CC.APIGetGroupLink(groupId))) {
|
||||
is CR.GroupLink -> r.connReqContact to r.memberRole
|
||||
is CR.GroupLink -> r.connLinkContact to r.memberRole
|
||||
else -> {
|
||||
Log.e(TAG, "apiGetGroupLink bad response: ${r.responseType} ${r.details}")
|
||||
null
|
||||
|
@ -3466,7 +3479,7 @@ sealed class CC {
|
|||
class ApiLeaveGroup(val groupId: Long): CC()
|
||||
class ApiListMembers(val groupId: Long): CC()
|
||||
class ApiUpdateGroupProfile(val groupId: Long, val groupProfile: GroupProfile): CC()
|
||||
class APICreateGroupLink(val groupId: Long, val memberRole: GroupMemberRole): CC()
|
||||
class APICreateGroupLink(val groupId: Long, val memberRole: GroupMemberRole, val short: Boolean): CC()
|
||||
class APIGroupLinkMemberRole(val groupId: Long, val memberRole: GroupMemberRole): CC()
|
||||
class APIDeleteGroupLink(val groupId: Long): CC()
|
||||
class APIGetGroupLink(val groupId: Long): CC()
|
||||
|
@ -3505,11 +3518,11 @@ sealed class CC {
|
|||
class APIGetGroupMemberCode(val groupId: Long, val groupMemberId: Long): CC()
|
||||
class APIVerifyContact(val contactId: Long, val connectionCode: String?): CC()
|
||||
class APIVerifyGroupMember(val groupId: Long, val groupMemberId: Long, val connectionCode: String?): CC()
|
||||
class APIAddContact(val userId: Long, val incognito: Boolean): CC()
|
||||
class APIAddContact(val userId: Long, val short: Boolean, val incognito: Boolean): CC()
|
||||
class ApiSetConnectionIncognito(val connId: Long, val incognito: Boolean): CC()
|
||||
class ApiChangeConnectionUser(val connId: Long, val userId: Long): CC()
|
||||
class APIConnectPlan(val userId: Long, val connReq: String): CC()
|
||||
class APIConnect(val userId: Long, val incognito: Boolean, val connReq: String): CC()
|
||||
class APIConnectPlan(val userId: Long, val connLink: String): CC()
|
||||
class APIConnect(val userId: Long, val incognito: Boolean, val connLink: CreatedConnLink): CC()
|
||||
class ApiConnectContactViaAddress(val userId: Long, val incognito: Boolean, val contactId: Long): CC()
|
||||
class ApiDeleteChat(val type: ChatType, val id: Long, val chatDeleteMode: ChatDeleteMode): CC()
|
||||
class ApiClearChat(val type: ChatType, val id: Long): CC()
|
||||
|
@ -3521,7 +3534,7 @@ sealed class CC {
|
|||
class ApiSetConnectionAlias(val connId: Long, val localAlias: String): CC()
|
||||
class ApiSetUserUIThemes(val userId: Long, val themes: ThemeModeOverrides?): CC()
|
||||
class ApiSetChatUIThemes(val chatId: String, val themes: ThemeModeOverrides?): CC()
|
||||
class ApiCreateMyAddress(val userId: Long): CC()
|
||||
class ApiCreateMyAddress(val userId: Long, val short: Boolean): CC()
|
||||
class ApiDeleteMyAddress(val userId: Long): CC()
|
||||
class ApiShowMyAddress(val userId: Long): CC()
|
||||
class ApiSetProfileAddress(val userId: Long, val on: Boolean): CC()
|
||||
|
@ -3651,7 +3664,7 @@ sealed class CC {
|
|||
is ApiLeaveGroup -> "/_leave #$groupId"
|
||||
is ApiListMembers -> "/_members #$groupId"
|
||||
is ApiUpdateGroupProfile -> "/_group_profile #$groupId ${json.encodeToString(groupProfile)}"
|
||||
is APICreateGroupLink -> "/_create link #$groupId ${memberRole.name.lowercase()}"
|
||||
is APICreateGroupLink -> "/_create link #$groupId ${memberRole.name.lowercase()} short=${onOff(short)}"
|
||||
is APIGroupLinkMemberRole -> "/_set link role #$groupId ${memberRole.name.lowercase()}"
|
||||
is APIDeleteGroupLink -> "/_delete link #$groupId"
|
||||
is APIGetGroupLink -> "/_get link #$groupId"
|
||||
|
@ -3690,11 +3703,11 @@ sealed class CC {
|
|||
is APIGetGroupMemberCode -> "/_get code #$groupId $groupMemberId"
|
||||
is APIVerifyContact -> "/_verify code @$contactId" + if (connectionCode != null) " $connectionCode" else ""
|
||||
is APIVerifyGroupMember -> "/_verify code #$groupId $groupMemberId" + if (connectionCode != null) " $connectionCode" else ""
|
||||
is APIAddContact -> "/_connect $userId incognito=${onOff(incognito)}"
|
||||
is APIAddContact -> "/_connect $userId short=${onOff(short)} incognito=${onOff(incognito)}"
|
||||
is ApiSetConnectionIncognito -> "/_set incognito :$connId ${onOff(incognito)}"
|
||||
is ApiChangeConnectionUser -> "/_set conn user :$connId $userId"
|
||||
is APIConnectPlan -> "/_connect plan $userId $connReq"
|
||||
is APIConnect -> "/_connect $userId incognito=${onOff(incognito)} $connReq"
|
||||
is APIConnectPlan -> "/_connect plan $userId $connLink"
|
||||
is APIConnect -> "/_connect $userId incognito=${onOff(incognito)} ${connLink.connFullLink} ${connLink.connShortLink ?: ""}"
|
||||
is ApiConnectContactViaAddress -> "/_connect contact $userId incognito=${onOff(incognito)} $contactId"
|
||||
is ApiDeleteChat -> "/_delete ${chatRef(type, id)} ${chatDeleteMode.cmdString}"
|
||||
is ApiClearChat -> "/_clear chat ${chatRef(type, id)}"
|
||||
|
@ -3706,7 +3719,7 @@ sealed class CC {
|
|||
is ApiSetConnectionAlias -> "/_set alias :$connId ${localAlias.trim()}"
|
||||
is ApiSetUserUIThemes -> "/_set theme user $userId ${if (themes != null) json.encodeToString(themes) else ""}"
|
||||
is ApiSetChatUIThemes -> "/_set theme $chatId ${if (themes != null) json.encodeToString(themes) else ""}"
|
||||
is ApiCreateMyAddress -> "/_address $userId"
|
||||
is ApiCreateMyAddress -> "/_address $userId short=${onOff(short)}"
|
||||
is ApiDeleteMyAddress -> "/_delete_address $userId"
|
||||
is ApiShowMyAddress -> "/_show_address $userId"
|
||||
is ApiSetProfileAddress -> "/_profile_address $userId ${onOff(on)}"
|
||||
|
@ -5799,10 +5812,10 @@ sealed class CR {
|
|||
@Serializable @SerialName("groupMemberCode") class GroupMemberCode(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val connectionCode: String): CR()
|
||||
@Serializable @SerialName("connectionVerified") class ConnectionVerified(val user: UserRef, val verified: Boolean, val expectedCode: String): CR()
|
||||
@Serializable @SerialName("tagsUpdated") class TagsUpdated(val user: UserRef, val userTags: List<ChatTag>, val chatTags: List<Long>): CR()
|
||||
@Serializable @SerialName("invitation") class Invitation(val user: UserRef, val connReqInvitation: String, val connection: PendingContactConnection): CR()
|
||||
@Serializable @SerialName("invitation") class Invitation(val user: UserRef, val connLinkInvitation: CreatedConnLink, val connection: PendingContactConnection): CR()
|
||||
@Serializable @SerialName("connectionIncognitoUpdated") class ConnectionIncognitoUpdated(val user: UserRef, val toConnection: PendingContactConnection): CR()
|
||||
@Serializable @SerialName("connectionUserChanged") class ConnectionUserChanged(val user: UserRef, val fromConnection: PendingContactConnection, val toConnection: PendingContactConnection, val newUser: UserRef): CR()
|
||||
@Serializable @SerialName("connectionPlan") class CRConnectionPlan(val user: UserRef, val connectionPlan: ConnectionPlan): CR()
|
||||
@Serializable @SerialName("connectionPlan") class CRConnectionPlan(val user: UserRef, val connLink: CreatedConnLink, val connectionPlan: ConnectionPlan): CR()
|
||||
@Serializable @SerialName("sentConfirmation") class SentConfirmation(val user: UserRef, val connection: PendingContactConnection): CR()
|
||||
@Serializable @SerialName("sentInvitation") class SentInvitation(val user: UserRef, val connection: PendingContactConnection): CR()
|
||||
@Serializable @SerialName("sentInvitationToContact") class SentInvitationToContact(val user: UserRef, val contact: Contact, val customUserProfile: Profile?): CR()
|
||||
|
@ -5819,7 +5832,7 @@ sealed class CR {
|
|||
@Serializable @SerialName("contactPrefsUpdated") class ContactPrefsUpdated(val user: UserRef, val fromContact: Contact, val toContact: Contact): CR()
|
||||
@Serializable @SerialName("userContactLink") class UserContactLink(val user: User, val contactLink: UserContactLinkRec): CR()
|
||||
@Serializable @SerialName("userContactLinkUpdated") class UserContactLinkUpdated(val user: User, val contactLink: UserContactLinkRec): CR()
|
||||
@Serializable @SerialName("userContactLinkCreated") class UserContactLinkCreated(val user: User, val connReqContact: String): CR()
|
||||
@Serializable @SerialName("userContactLinkCreated") class UserContactLinkCreated(val user: User, val connLinkContact: CreatedConnLink): CR()
|
||||
@Serializable @SerialName("userContactLinkDeleted") class UserContactLinkDeleted(val user: User): CR()
|
||||
@Serializable @SerialName("contactConnected") class ContactConnected(val user: UserRef, val contact: Contact, val userCustomProfile: Profile? = null): CR()
|
||||
@Serializable @SerialName("contactConnecting") class ContactConnecting(val user: UserRef, val contact: Contact): CR()
|
||||
|
@ -5876,8 +5889,8 @@ sealed class CR {
|
|||
@Serializable @SerialName("connectedToGroupMember") class ConnectedToGroupMember(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val memberContact: Contact? = null): CR()
|
||||
@Serializable @SerialName("groupRemoved") class GroupRemoved(val user: UserRef, val groupInfo: GroupInfo): CR() // unused
|
||||
@Serializable @SerialName("groupUpdated") class GroupUpdated(val user: UserRef, val toGroup: GroupInfo): CR()
|
||||
@Serializable @SerialName("groupLinkCreated") class GroupLinkCreated(val user: UserRef, val groupInfo: GroupInfo, val connReqContact: String, val memberRole: GroupMemberRole): CR()
|
||||
@Serializable @SerialName("groupLink") class GroupLink(val user: UserRef, val groupInfo: GroupInfo, val connReqContact: String, val memberRole: GroupMemberRole): CR()
|
||||
@Serializable @SerialName("groupLinkCreated") class GroupLinkCreated(val user: UserRef, val groupInfo: GroupInfo, val connLinkContact: CreatedConnLink, val memberRole: GroupMemberRole): CR()
|
||||
@Serializable @SerialName("groupLink") class GroupLink(val user: UserRef, val groupInfo: GroupInfo, val connLinkContact: CreatedConnLink, val memberRole: GroupMemberRole): CR()
|
||||
@Serializable @SerialName("groupLinkDeleted") class GroupLinkDeleted(val user: UserRef, val groupInfo: GroupInfo): CR()
|
||||
@Serializable @SerialName("newMemberContact") class NewMemberContact(val user: UserRef, val contact: Contact, val groupInfo: GroupInfo, val member: GroupMember): CR()
|
||||
@Serializable @SerialName("newMemberContactSentInv") class NewMemberContactSentInv(val user: UserRef, val contact: Contact, val groupInfo: GroupInfo, val member: GroupMember): CR()
|
||||
|
@ -6165,10 +6178,10 @@ sealed class CR {
|
|||
is GroupMemberCode -> withUser(user, "groupInfo: ${json.encodeToString(groupInfo)}\nmember: ${json.encodeToString(member)}\nconnectionCode: $connectionCode")
|
||||
is ConnectionVerified -> withUser(user, "verified: $verified\nconnectionCode: $expectedCode")
|
||||
is TagsUpdated -> withUser(user, "userTags: ${json.encodeToString(userTags)}\nchatTags: ${json.encodeToString(chatTags)}")
|
||||
is Invitation -> withUser(user, "connReqInvitation: $connReqInvitation\nconnection: $connection")
|
||||
is Invitation -> withUser(user, "connLinkInvitation: ${json.encodeToString(connLinkInvitation)}\nconnection: $connection")
|
||||
is ConnectionIncognitoUpdated -> withUser(user, json.encodeToString(toConnection))
|
||||
is ConnectionUserChanged -> withUser(user, "fromConnection: ${json.encodeToString(fromConnection)}\ntoConnection: ${json.encodeToString(toConnection)}\nnewUser: ${json.encodeToString(newUser)}" )
|
||||
is CRConnectionPlan -> withUser(user, json.encodeToString(connectionPlan))
|
||||
is CRConnectionPlan -> withUser(user, "connLink: ${json.encodeToString(connLink)}\nconnectionPlan: ${json.encodeToString(connectionPlan)}")
|
||||
is SentConfirmation -> withUser(user, json.encodeToString(connection))
|
||||
is SentInvitation -> withUser(user, json.encodeToString(connection))
|
||||
is SentInvitationToContact -> withUser(user, json.encodeToString(contact))
|
||||
|
@ -6185,7 +6198,7 @@ sealed class CR {
|
|||
is ContactPrefsUpdated -> withUser(user, "fromContact: $fromContact\ntoContact: \n${json.encodeToString(toContact)}")
|
||||
is UserContactLink -> withUser(user, contactLink.responseDetails)
|
||||
is UserContactLinkUpdated -> withUser(user, contactLink.responseDetails)
|
||||
is UserContactLinkCreated -> withUser(user, connReqContact)
|
||||
is UserContactLinkCreated -> withUser(user, json.encodeToString(connLinkContact))
|
||||
is UserContactLinkDeleted -> withUser(user, noDetails())
|
||||
is ContactConnected -> withUser(user, json.encodeToString(contact))
|
||||
is ContactConnecting -> withUser(user, json.encodeToString(contact))
|
||||
|
@ -6239,8 +6252,8 @@ sealed class CR {
|
|||
is ConnectedToGroupMember -> withUser(user, "groupInfo: $groupInfo\nmember: $member\nmemberContact: $memberContact")
|
||||
is GroupRemoved -> withUser(user, json.encodeToString(groupInfo))
|
||||
is GroupUpdated -> withUser(user, json.encodeToString(toGroup))
|
||||
is GroupLinkCreated -> withUser(user, "groupInfo: $groupInfo\nconnReqContact: $connReqContact\nmemberRole: $memberRole")
|
||||
is GroupLink -> withUser(user, "groupInfo: $groupInfo\nconnReqContact: $connReqContact\nmemberRole: $memberRole")
|
||||
is GroupLinkCreated -> withUser(user, "groupInfo: $groupInfo\nconnLinkContact: $connLinkContact\nmemberRole: $memberRole")
|
||||
is GroupLink -> withUser(user, "groupInfo: $groupInfo\nconnLinkContact: $connLinkContact\nmemberRole: $memberRole")
|
||||
is GroupLinkDeleted -> withUser(user, json.encodeToString(groupInfo))
|
||||
is NewMemberContact -> withUser(user, "contact: $contact\ngroupInfo: $groupInfo\nmember: $member")
|
||||
is NewMemberContactSentInv -> withUser(user, "contact: $contact\ngroupInfo: $groupInfo\nmember: $member")
|
||||
|
@ -6350,11 +6363,34 @@ sealed class ChatDeleteMode {
|
|||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class CreatedConnLink(val connFullLink: String, val connShortLink: String?) {
|
||||
fun simplexChatUri(short: Boolean): String =
|
||||
if (short) connShortLink ?: simplexChatLink(connFullLink)
|
||||
else simplexChatLink(connFullLink)
|
||||
|
||||
companion object {
|
||||
val nullableStateSaver: Saver<CreatedConnLink?, Pair<String?, String?>> = Saver(
|
||||
save = { link -> link?.connFullLink to link?.connShortLink },
|
||||
restore = { saved ->
|
||||
val connFullLink = saved.first
|
||||
if (connFullLink == null) null
|
||||
else CreatedConnLink(connFullLink = connFullLink, connShortLink = saved.second)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun simplexChatLink(uri: String): String =
|
||||
if (uri.startsWith("simplex:/")) uri.replace("simplex:/", "https://simplex.chat/")
|
||||
else uri
|
||||
|
||||
@Serializable
|
||||
sealed class ConnectionPlan {
|
||||
@Serializable @SerialName("invitationLink") class InvitationLink(val invitationLinkPlan: InvitationLinkPlan): ConnectionPlan()
|
||||
@Serializable @SerialName("contactAddress") class ContactAddress(val contactAddressPlan: ContactAddressPlan): ConnectionPlan()
|
||||
@Serializable @SerialName("groupLink") class GroupLink(val groupLinkPlan: GroupLinkPlan): ConnectionPlan()
|
||||
@Serializable @SerialName("error") class Error(val chatError: ChatError): ConnectionPlan()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
@ -6487,8 +6523,8 @@ enum class RatchetSyncState {
|
|||
}
|
||||
|
||||
@Serializable
|
||||
class UserContactLinkRec(val connReqContact: String, val autoAccept: AutoAccept? = null) {
|
||||
val responseDetails: String get() = "connReqContact: ${connReqContact}\nautoAccept: ${AutoAccept.cmdString(autoAccept)}"
|
||||
class UserContactLinkRec(val connLinkContact: CreatedConnLink, val autoAccept: AutoAccept? = null) {
|
||||
val responseDetails: String get() = "connLinkContact: ${connLinkContact}\nautoAccept: ${AutoAccept.cmdString(autoAccept)}"
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
@ -6580,6 +6616,7 @@ sealed class ChatErrorType {
|
|||
is ChatStoreChanged -> "chatStoreChanged"
|
||||
is ConnectionPlanChatError -> "connectionPlan"
|
||||
is InvalidConnReq -> "invalidConnReq"
|
||||
is UnsupportedConnReq -> "unsupportedConnReq"
|
||||
is InvalidChatMessage -> "invalidChatMessage"
|
||||
is ContactNotReady -> "contactNotReady"
|
||||
is ContactNotActive -> "contactNotActive"
|
||||
|
@ -6658,6 +6695,7 @@ sealed class ChatErrorType {
|
|||
@Serializable @SerialName("chatStoreChanged") object ChatStoreChanged: ChatErrorType()
|
||||
@Serializable @SerialName("connectionPlan") class ConnectionPlanChatError(val connectionPlan: ConnectionPlan): ChatErrorType()
|
||||
@Serializable @SerialName("invalidConnReq") object InvalidConnReq: ChatErrorType()
|
||||
@Serializable @SerialName("unsupportedConnReq") object UnsupportedConnReq: ChatErrorType()
|
||||
@Serializable @SerialName("invalidChatMessage") class InvalidChatMessage(val connection: Connection, val message: String): ChatErrorType()
|
||||
@Serializable @SerialName("contactNotReady") class ContactNotReady(val contact: Contact): ChatErrorType()
|
||||
@Serializable @SerialName("contactNotActive") class ContactNotActive(val contact: Contact): ChatErrorType()
|
||||
|
@ -6777,6 +6815,7 @@ sealed class StoreError {
|
|||
is ContactNotFoundByFileId -> "contactNotFoundByFileId"
|
||||
is NoGroupSndStatus -> "noGroupSndStatus"
|
||||
is LargeMsg -> "largeMsg"
|
||||
is DBException -> "dBException"
|
||||
}
|
||||
|
||||
@Serializable @SerialName("duplicateName") object DuplicateName: StoreError()
|
||||
|
@ -6837,6 +6876,7 @@ sealed class StoreError {
|
|||
@Serializable @SerialName("contactNotFoundByFileId") class ContactNotFoundByFileId(val fileId: Long): StoreError()
|
||||
@Serializable @SerialName("noGroupSndStatus") class NoGroupSndStatus(val itemId: Long, val groupMemberId: Long): StoreError()
|
||||
@Serializable @SerialName("largeMsg") object LargeMsg: StoreError()
|
||||
@Serializable @SerialName("dBException") class DBException(val message: String): StoreError()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
|
|
@ -263,7 +263,7 @@ fun ChatView(
|
|||
// The idea is to preload information before showing a modal because large groups can take time to load all members
|
||||
var preloadedContactInfo: Pair<ConnectionStats?, Profile?>? = null
|
||||
var preloadedCode: String? = null
|
||||
var preloadedLink: Pair<String, GroupMemberRole>? = null
|
||||
var preloadedLink: Pair<CreatedConnLink, GroupMemberRole>? = null
|
||||
if (chatInfo is ChatInfo.Direct) {
|
||||
preloadedContactInfo = chatModel.controller.apiContactInfo(chatRh, chatInfo.apiId)
|
||||
preloadedCode = chatModel.controller.apiGetContactCode(chatRh, chatInfo.apiId)?.second
|
||||
|
@ -291,7 +291,7 @@ fun ChatView(
|
|||
showSearch.value = true
|
||||
}
|
||||
} else if (chatInfo is ChatInfo.Group) {
|
||||
var link: Pair<String, GroupMemberRole>? by remember(chatInfo.id) { mutableStateOf(preloadedLink) }
|
||||
var link: Pair<CreatedConnLink, GroupMemberRole>? by remember(chatInfo.id) { mutableStateOf(preloadedLink) }
|
||||
KeyChangeEffect(chatInfo.id) {
|
||||
setGroupMembers(chatRh, chatInfo.groupInfo, chatModel)
|
||||
link = chatModel.controller.apiGetGroupLink(chatRh, chatInfo.groupInfo.groupId)
|
||||
|
@ -632,7 +632,7 @@ fun ChatView(
|
|||
is ChatInfo.ContactConnection -> {
|
||||
val close = { chatModel.chatId.value = null }
|
||||
ModalView(close, showClose = appPlatform.isAndroid, content = {
|
||||
ContactConnectionInfoView(chatModel, chatRh, chatInfo.contactConnection.connReqInv, chatInfo.contactConnection, false, close)
|
||||
ContactConnectionInfoView(chatModel, chatRh, chatInfo.contactConnection.connLinkInv, chatInfo.contactConnection, false, close)
|
||||
})
|
||||
LaunchedEffect(chatInfo.id) {
|
||||
onComposed(chatInfo.id)
|
||||
|
|
|
@ -53,12 +53,12 @@ val MEMBER_ROW_VERTICAL_PADDING = 8.dp
|
|||
fun ModalData.GroupChatInfoView(
|
||||
rhId: Long?,
|
||||
chatId: String,
|
||||
groupLink: String?,
|
||||
groupLink: CreatedConnLink?,
|
||||
groupLinkMemberRole: GroupMemberRole?,
|
||||
selectedItems: MutableState<Set<Long>?>,
|
||||
appBar: MutableState<@Composable (BoxScope.() -> Unit)?>,
|
||||
scrollToItemId: MutableState<Long?>,
|
||||
onGroupLinkUpdated: (Pair<String, GroupMemberRole>?) -> Unit,
|
||||
onGroupLinkUpdated: (Pair<CreatedConnLink, GroupMemberRole>?) -> Unit,
|
||||
close: () -> Unit,
|
||||
onSearchClicked: () -> Unit
|
||||
) {
|
||||
|
@ -328,7 +328,7 @@ fun ModalData.GroupChatInfoLayout(
|
|||
activeSortedMembers: List<GroupMember>,
|
||||
developerTools: Boolean,
|
||||
onLocalAliasChanged: (String) -> Unit,
|
||||
groupLink: String?,
|
||||
groupLink: CreatedConnLink?,
|
||||
selectedItems: MutableState<Set<Long>?>,
|
||||
appBar: MutableState<@Composable (BoxScope.() -> Unit)?>,
|
||||
scrollToItemId: MutableState<Long?>,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package chat.simplex.common.views.chat.group
|
||||
|
||||
import SectionBottomSpacer
|
||||
import SectionViewWithButton
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
|
@ -15,11 +16,11 @@ import dev.icerock.moko.resources.compose.stringResource
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.platform.ColumnWithScrollBar
|
||||
import chat.simplex.common.platform.shareText
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.newchat.*
|
||||
import chat.simplex.common.views.usersettings.SettingsActionItem
|
||||
import chat.simplex.res.MR
|
||||
|
||||
@Composable
|
||||
|
@ -27,13 +28,13 @@ fun GroupLinkView(
|
|||
chatModel: ChatModel,
|
||||
rhId: Long?,
|
||||
groupInfo: GroupInfo,
|
||||
connReqContact: String?,
|
||||
connLinkContact: CreatedConnLink?,
|
||||
memberRole: GroupMemberRole?,
|
||||
onGroupLinkUpdated: ((Pair<String, GroupMemberRole>?) -> Unit)?,
|
||||
onGroupLinkUpdated: ((Pair<CreatedConnLink, GroupMemberRole>?) -> Unit)?,
|
||||
creatingGroup: Boolean = false,
|
||||
close: (() -> Unit)? = null
|
||||
) {
|
||||
var groupLink by rememberSaveable { mutableStateOf(connReqContact) }
|
||||
var groupLink by rememberSaveable(stateSaver = CreatedConnLink.nullableStateSaver) { mutableStateOf(connLinkContact) }
|
||||
val groupLinkMemberRole = rememberSaveable { mutableStateOf(memberRole) }
|
||||
var creatingLink by rememberSaveable { mutableStateOf(false) }
|
||||
fun createLink() {
|
||||
|
@ -99,7 +100,7 @@ fun GroupLinkView(
|
|||
|
||||
@Composable
|
||||
fun GroupLinkLayout(
|
||||
groupLink: String?,
|
||||
groupLink: CreatedConnLink?,
|
||||
groupInfo: GroupInfo,
|
||||
groupLinkMemberRole: MutableState<GroupMemberRole?>,
|
||||
creatingLink: Boolean,
|
||||
|
@ -150,7 +151,15 @@ fun GroupLinkLayout(
|
|||
}
|
||||
initialLaunch = false
|
||||
}
|
||||
SimpleXLinkQRCode(groupLink)
|
||||
val showShortLink = remember { mutableStateOf(true) }
|
||||
Spacer(Modifier.height(DEFAULT_PADDING_HALF))
|
||||
if (groupLink.connShortLink == null) {
|
||||
SimpleXCreatedLinkQRCode(groupLink, short = false)
|
||||
} else {
|
||||
SectionViewWithButton(titleButton = { ToggleShortLinkButton(showShortLink) }) {
|
||||
SimpleXCreatedLinkQRCode(groupLink, short = showShortLink.value)
|
||||
}
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
|
@ -160,7 +169,7 @@ fun GroupLinkLayout(
|
|||
SimpleButton(
|
||||
stringResource(MR.strings.share_link),
|
||||
icon = painterResource(MR.images.ic_share),
|
||||
click = { clipboard.shareText(simplexChatLink(groupLink)) }
|
||||
click = { clipboard.shareText(groupLink.simplexChatUri(short = showShortLink.value)) }
|
||||
)
|
||||
if (creatingGroup && close != null) {
|
||||
ContinueButton(close)
|
||||
|
|
|
@ -547,7 +547,7 @@ fun ContactConnectionMenuItems(rhId: Long?, chatInfo: ChatInfo.ContactConnection
|
|||
ModalManager.center.closeModals()
|
||||
ModalManager.end.closeModals()
|
||||
ModalManager.center.showModalCloseable(true, showClose = appPlatform.isAndroid) { close ->
|
||||
ContactConnectionInfoView(chatModel, rhId, chatInfo.contactConnection.connReqInv, chatInfo.contactConnection, true, close)
|
||||
ContactConnectionInfoView(chatModel, rhId, chatInfo.contactConnection.connLinkInv, chatInfo.contactConnection, true, close)
|
||||
}
|
||||
showMenu.value = false
|
||||
},
|
||||
|
|
|
@ -53,6 +53,24 @@ fun SectionView(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SectionViewWithButton(title: String? = null, titleButton: (@Composable () -> Unit)?, contentPadding: PaddingValues = PaddingValues(), headerBottomPadding: Dp = DEFAULT_PADDING, content: (@Composable ColumnScope.() -> Unit)) {
|
||||
Column {
|
||||
if (title != null || titleButton != null) {
|
||||
Row(modifier = Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = headerBottomPadding).fillMaxWidth()) {
|
||||
if (title != null) {
|
||||
Text(title, color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2, fontSize = 12.sp)
|
||||
}
|
||||
if (titleButton != null) {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
titleButton()
|
||||
}
|
||||
}
|
||||
}
|
||||
Column(Modifier.padding(contentPadding).fillMaxWidth()) { content() }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun <T> SectionViewSelectable(
|
||||
title: String?,
|
||||
|
|
|
@ -57,7 +57,7 @@ fun AddGroupView(chatModel: ChatModel, rh: RemoteHostInfo?, close: () -> Unit, c
|
|||
}
|
||||
} else {
|
||||
ModalManager.end.showModalCloseable(true) { close ->
|
||||
GroupLinkView(chatModel, rhId, groupInfo, connReqContact = null, memberRole = null, onGroupLinkUpdated = null, creatingGroup = true, close)
|
||||
GroupLinkView(chatModel, rhId, groupInfo, connLinkContact = null, memberRole = null, onGroupLinkUpdated = null, creatingGroup = true, close)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ enum class ConnectionLinkType {
|
|||
|
||||
suspend fun planAndConnect(
|
||||
rhId: Long?,
|
||||
uri: String,
|
||||
shortOrFullLink: String,
|
||||
incognito: Boolean?,
|
||||
close: (() -> Unit)?,
|
||||
cleanup: (() -> Unit)? = null,
|
||||
|
@ -27,18 +27,19 @@ suspend fun planAndConnect(
|
|||
filterKnownGroup: ((GroupInfo) -> Unit)? = null,
|
||||
): CompletableDeferred<Boolean> {
|
||||
val completable = CompletableDeferred<Boolean>()
|
||||
val close: (() -> Unit)? = {
|
||||
val close: (() -> Unit) = {
|
||||
close?.invoke()
|
||||
// if close was called, it means the connection was created
|
||||
completable.complete(true)
|
||||
}
|
||||
val cleanup: (() -> Unit)? = {
|
||||
val cleanup: (() -> Unit) = {
|
||||
cleanup?.invoke()
|
||||
completable.complete(!completable.isActive)
|
||||
}
|
||||
val connectionPlan = chatModel.controller.apiConnectPlan(rhId, uri)
|
||||
if (connectionPlan != null) {
|
||||
val link = strHasSingleSimplexLink(uri.trim())
|
||||
val result = chatModel.controller.apiConnectPlan(rhId, shortOrFullLink)
|
||||
if (result != null) {
|
||||
val (connectionLink, connectionPlan) = result
|
||||
val link = strHasSingleSimplexLink(shortOrFullLink.trim())
|
||||
val linkText = if (link?.format is Format.SimplexLink)
|
||||
"<br><br><u>${link.simplexLinkText(link.format.linkType, link.format.smpHosts)}</u>"
|
||||
else
|
||||
|
@ -48,10 +49,10 @@ suspend fun planAndConnect(
|
|||
InvitationLinkPlan.Ok -> {
|
||||
Log.d(TAG, "planAndConnect, .InvitationLink, .Ok, incognito=$incognito")
|
||||
if (incognito != null) {
|
||||
connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup)
|
||||
connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, rhId, uri, connectionPlan, close,
|
||||
chatModel, rhId, connectionLink, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_via_invitation_link),
|
||||
text = generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link) + linkText,
|
||||
connectDestructive = false,
|
||||
|
@ -66,7 +67,7 @@ suspend fun planAndConnect(
|
|||
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
|
||||
text = generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link) + linkText,
|
||||
confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
|
||||
onConfirm = { withBGApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } },
|
||||
onConfirm = { withBGApi { connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) } },
|
||||
onDismiss = cleanup,
|
||||
onDismissRequest = cleanup,
|
||||
destructive = true,
|
||||
|
@ -74,7 +75,7 @@ suspend fun planAndConnect(
|
|||
)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, rhId, uri, connectionPlan, close,
|
||||
chatModel, rhId, connectionLink, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
|
||||
text = generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link) + linkText,
|
||||
connectDestructive = true,
|
||||
|
@ -95,7 +96,7 @@ suspend fun planAndConnect(
|
|||
String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName) + linkText,
|
||||
hostDevice = hostDevice(rhId),
|
||||
)
|
||||
cleanup?.invoke()
|
||||
cleanup()
|
||||
}
|
||||
} else {
|
||||
AlertManager.privacySensitive.showAlertMsg(
|
||||
|
@ -103,7 +104,7 @@ suspend fun planAndConnect(
|
|||
generalGetString(MR.strings.connect_plan_you_are_already_connecting_via_this_one_time_link) + linkText,
|
||||
hostDevice = hostDevice(rhId),
|
||||
)
|
||||
cleanup?.invoke()
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
is InvitationLinkPlan.Known -> {
|
||||
|
@ -118,7 +119,7 @@ suspend fun planAndConnect(
|
|||
String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName) + linkText,
|
||||
hostDevice = hostDevice(rhId),
|
||||
)
|
||||
cleanup?.invoke()
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -126,10 +127,10 @@ suspend fun planAndConnect(
|
|||
ContactAddressPlan.Ok -> {
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .Ok, incognito=$incognito")
|
||||
if (incognito != null) {
|
||||
connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup)
|
||||
connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, rhId, uri, connectionPlan, close,
|
||||
chatModel, rhId, connectionLink, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_via_contact_link),
|
||||
text = generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link) + linkText,
|
||||
connectDestructive = false,
|
||||
|
@ -144,7 +145,7 @@ suspend fun planAndConnect(
|
|||
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
|
||||
text = generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address) + linkText,
|
||||
confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
|
||||
onConfirm = { withBGApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } },
|
||||
onConfirm = { withBGApi { connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) } },
|
||||
destructive = true,
|
||||
onDismiss = cleanup,
|
||||
onDismissRequest = cleanup,
|
||||
|
@ -152,7 +153,7 @@ suspend fun planAndConnect(
|
|||
)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, rhId, uri, connectionPlan, close,
|
||||
chatModel, rhId, connectionLink, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
|
||||
text = generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address) + linkText,
|
||||
connectDestructive = true,
|
||||
|
@ -167,7 +168,7 @@ suspend fun planAndConnect(
|
|||
title = generalGetString(MR.strings.connect_plan_repeat_connection_request),
|
||||
text = generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address) + linkText,
|
||||
confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
|
||||
onConfirm = { withBGApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } },
|
||||
onConfirm = { withBGApi { connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) } },
|
||||
onDismiss = cleanup,
|
||||
onDismissRequest = cleanup,
|
||||
destructive = true,
|
||||
|
@ -175,7 +176,7 @@ suspend fun planAndConnect(
|
|||
)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, rhId, uri, connectionPlan, close,
|
||||
chatModel, rhId, connectionLink, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_plan_repeat_connection_request),
|
||||
text = generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address) + linkText,
|
||||
connectDestructive = true,
|
||||
|
@ -195,7 +196,7 @@ suspend fun planAndConnect(
|
|||
String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName) + linkText,
|
||||
hostDevice = hostDevice(rhId),
|
||||
)
|
||||
cleanup?.invoke()
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
is ContactAddressPlan.Known -> {
|
||||
|
@ -210,19 +211,19 @@ suspend fun planAndConnect(
|
|||
String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName) + linkText,
|
||||
hostDevice = hostDevice(rhId),
|
||||
)
|
||||
cleanup?.invoke()
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
is ContactAddressPlan.ContactViaAddress -> {
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .ContactViaAddress, incognito=$incognito")
|
||||
val contact = connectionPlan.contactAddressPlan.contact
|
||||
if (incognito != null) {
|
||||
close?.invoke()
|
||||
close()
|
||||
connectContactViaAddress(chatModel, rhId, contact.contactId, incognito)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileConnectContactViaAddress(chatModel, rhId, contact, close, openChat = false)
|
||||
}
|
||||
cleanup?.invoke()
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
is ConnectionPlan.GroupLink -> when (connectionPlan.groupLinkPlan) {
|
||||
|
@ -233,14 +234,14 @@ suspend fun planAndConnect(
|
|||
title = generalGetString(MR.strings.connect_via_group_link),
|
||||
text = generalGetString(MR.strings.you_will_join_group) + linkText,
|
||||
confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button),
|
||||
onConfirm = { withBGApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } },
|
||||
onConfirm = { withBGApi { connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) } },
|
||||
onDismiss = cleanup,
|
||||
onDismissRequest = cleanup,
|
||||
hostDevice = hostDevice(rhId),
|
||||
)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, rhId, uri, connectionPlan, close,
|
||||
chatModel, rhId, connectionLink, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_via_group_link),
|
||||
text = generalGetString(MR.strings.you_will_join_group) + linkText,
|
||||
connectDestructive = false,
|
||||
|
@ -254,7 +255,7 @@ suspend fun planAndConnect(
|
|||
if (filterKnownGroup != null) {
|
||||
filterKnownGroup(groupInfo)
|
||||
} else {
|
||||
ownGroupLinkConfirmConnect(chatModel, rhId, uri, linkText, incognito, connectionPlan, groupInfo, close, cleanup)
|
||||
ownGroupLinkConfirmConnect(chatModel, rhId, connectionLink, linkText, incognito, connectionPlan, groupInfo, close, cleanup)
|
||||
}
|
||||
}
|
||||
GroupLinkPlan.ConnectingConfirmReconnect -> {
|
||||
|
@ -264,7 +265,7 @@ suspend fun planAndConnect(
|
|||
title = generalGetString(MR.strings.connect_plan_repeat_join_request),
|
||||
text = generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link) + linkText,
|
||||
confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button),
|
||||
onConfirm = { withBGApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } },
|
||||
onConfirm = { withBGApi { connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) } },
|
||||
onDismiss = cleanup,
|
||||
onDismissRequest = cleanup,
|
||||
destructive = true,
|
||||
|
@ -272,7 +273,7 @@ suspend fun planAndConnect(
|
|||
)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, rhId, uri, connectionPlan, close,
|
||||
chatModel, rhId, connectionLink, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_plan_repeat_join_request),
|
||||
text = generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link) + linkText,
|
||||
connectDestructive = true,
|
||||
|
@ -302,7 +303,7 @@ suspend fun planAndConnect(
|
|||
hostDevice = hostDevice(rhId),
|
||||
)
|
||||
}
|
||||
cleanup?.invoke()
|
||||
cleanup()
|
||||
}
|
||||
is GroupLinkPlan.Known -> {
|
||||
Log.d(TAG, "planAndConnect, .GroupLink, .Known, incognito=$incognito")
|
||||
|
@ -324,22 +325,23 @@ suspend fun planAndConnect(
|
|||
hostDevice = hostDevice(rhId),
|
||||
)
|
||||
}
|
||||
cleanup?.invoke()
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "planAndConnect, plan error")
|
||||
if (incognito != null) {
|
||||
connectViaUri(chatModel, rhId, uri, incognito, connectionPlan = null, close, cleanup)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, rhId, uri, connectionPlan = null, close,
|
||||
title = generalGetString(MR.strings.connect_plan_connect_via_link),
|
||||
connectDestructive = false,
|
||||
cleanup = cleanup,
|
||||
)
|
||||
is ConnectionPlan.Error -> {
|
||||
Log.d(TAG, "planAndConnect, error ${connectionPlan.chatError}")
|
||||
if (incognito != null) {
|
||||
connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan = null, close, cleanup)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, rhId, connectionLink, connectionPlan = null, close,
|
||||
title = generalGetString(MR.strings.connect_plan_connect_via_link),
|
||||
connectDestructive = false,
|
||||
cleanup = cleanup,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return completable
|
||||
|
@ -348,14 +350,14 @@ suspend fun planAndConnect(
|
|||
suspend fun connectViaUri(
|
||||
chatModel: ChatModel,
|
||||
rhId: Long?,
|
||||
uri: String,
|
||||
connLink: CreatedConnLink,
|
||||
incognito: Boolean,
|
||||
connectionPlan: ConnectionPlan?,
|
||||
close: (() -> Unit)?,
|
||||
cleanup: (() -> Unit)?,
|
||||
): Boolean {
|
||||
val pcc = chatModel.controller.apiConnect(rhId, incognito, uri)
|
||||
val connLinkType = if (connectionPlan != null) planToConnectionLinkType(connectionPlan) else ConnectionLinkType.INVITATION
|
||||
val pcc = chatModel.controller.apiConnect(rhId, incognito, connLink)
|
||||
val connLinkType = if (connectionPlan != null) planToConnectionLinkType(connectionPlan) ?: ConnectionLinkType.INVITATION else ConnectionLinkType.INVITATION
|
||||
if (pcc != null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
chatModel.chatsContext.updateContactConnection(rhId, pcc)
|
||||
|
@ -376,18 +378,19 @@ suspend fun connectViaUri(
|
|||
return pcc != null
|
||||
}
|
||||
|
||||
fun planToConnectionLinkType(connectionPlan: ConnectionPlan): ConnectionLinkType {
|
||||
fun planToConnectionLinkType(connectionPlan: ConnectionPlan): ConnectionLinkType? {
|
||||
return when(connectionPlan) {
|
||||
is ConnectionPlan.InvitationLink -> ConnectionLinkType.INVITATION
|
||||
is ConnectionPlan.ContactAddress -> ConnectionLinkType.CONTACT
|
||||
is ConnectionPlan.GroupLink -> ConnectionLinkType.GROUP
|
||||
is ConnectionPlan.Error -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun askCurrentOrIncognitoProfileAlert(
|
||||
chatModel: ChatModel,
|
||||
rhId: Long?,
|
||||
uri: String,
|
||||
connectionLink: CreatedConnLink,
|
||||
connectionPlan: ConnectionPlan?,
|
||||
close: (() -> Unit)?,
|
||||
title: String,
|
||||
|
@ -404,7 +407,7 @@ fun askCurrentOrIncognitoProfileAlert(
|
|||
SectionItemView({
|
||||
AlertManager.privacySensitive.hideAlert()
|
||||
withBGApi {
|
||||
connectViaUri(chatModel, rhId, uri, incognito = false, connectionPlan, close, cleanup)
|
||||
connectViaUri(chatModel, rhId, connectionLink, incognito = false, connectionPlan, close, cleanup)
|
||||
}
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = connectColor)
|
||||
|
@ -412,7 +415,7 @@ fun askCurrentOrIncognitoProfileAlert(
|
|||
SectionItemView({
|
||||
AlertManager.privacySensitive.hideAlert()
|
||||
withBGApi {
|
||||
connectViaUri(chatModel, rhId, uri, incognito = true, connectionPlan, close, cleanup)
|
||||
connectViaUri(chatModel, rhId, connectionLink, incognito = true, connectionPlan, close, cleanup)
|
||||
}
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = connectColor)
|
||||
|
@ -443,7 +446,7 @@ fun openKnownContact(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, co
|
|||
fun ownGroupLinkConfirmConnect(
|
||||
chatModel: ChatModel,
|
||||
rhId: Long?,
|
||||
uri: String,
|
||||
connectionLink: CreatedConnLink,
|
||||
linkText: String,
|
||||
incognito: Boolean?,
|
||||
connectionPlan: ConnectionPlan?,
|
||||
|
@ -469,7 +472,7 @@ fun ownGroupLinkConfirmConnect(
|
|||
SectionItemView({
|
||||
AlertManager.privacySensitive.hideAlert()
|
||||
withBGApi {
|
||||
connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup)
|
||||
connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup)
|
||||
}
|
||||
}) {
|
||||
Text(
|
||||
|
@ -482,7 +485,7 @@ fun ownGroupLinkConfirmConnect(
|
|||
SectionItemView({
|
||||
AlertManager.privacySensitive.hideAlert()
|
||||
withBGApi {
|
||||
connectViaUri(chatModel, rhId, uri, incognito = false, connectionPlan, close, cleanup)
|
||||
connectViaUri(chatModel, rhId, connectionLink, incognito = false, connectionPlan, close, cleanup)
|
||||
}
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error)
|
||||
|
@ -491,7 +494,7 @@ fun ownGroupLinkConfirmConnect(
|
|||
SectionItemView({
|
||||
AlertManager.privacySensitive.hideAlert()
|
||||
withBGApi {
|
||||
connectViaUri(chatModel, rhId, uri, incognito = true, connectionPlan, close, cleanup)
|
||||
connectViaUri(chatModel, rhId, connectionLink, incognito = true, connectionPlan, close, cleanup)
|
||||
}
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error)
|
||||
|
|
|
@ -4,6 +4,7 @@ import SectionBottomSpacer
|
|||
import SectionDividerSpaced
|
||||
import SectionTextFooter
|
||||
import SectionView
|
||||
import SectionViewWithButton
|
||||
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
|
@ -31,14 +32,14 @@ import kotlinx.coroutines.*
|
|||
fun ContactConnectionInfoView(
|
||||
chatModel: ChatModel,
|
||||
rhId: Long?,
|
||||
connReqInvitation: String?,
|
||||
connLinkInvitation: CreatedConnLink?,
|
||||
contactConnection: PendingContactConnection,
|
||||
focusAlias: Boolean,
|
||||
close: () -> Unit
|
||||
) {
|
||||
LaunchedEffect(connReqInvitation) {
|
||||
if (connReqInvitation != null) {
|
||||
chatModel.showingInvitation.value = ShowingInvitation(contactConnection.id, connReqInvitation, false, conn = contactConnection)
|
||||
LaunchedEffect(connLinkInvitation) {
|
||||
if (connLinkInvitation != null) {
|
||||
chatModel.showingInvitation.value = ShowingInvitation(contactConnection.id, connLinkInvitation, false, conn = contactConnection)
|
||||
}
|
||||
}
|
||||
/** When [AddContactLearnMore] is open, we don't need to drop [ChatModel.showingInvitation].
|
||||
|
@ -53,16 +54,16 @@ fun ContactConnectionInfoView(
|
|||
}
|
||||
}
|
||||
}
|
||||
val clipboard = LocalClipboardManager.current
|
||||
val showShortLink = remember { mutableStateOf(true) }
|
||||
ContactConnectionInfoLayout(
|
||||
chatModel = chatModel,
|
||||
connReq = connReqInvitation,
|
||||
connLink = connLinkInvitation,
|
||||
showShortLink = showShortLink,
|
||||
contactConnection = contactConnection,
|
||||
focusAlias = focusAlias,
|
||||
rhId = rhId,
|
||||
deleteConnection = { deleteContactConnectionAlert(rhId, contactConnection, chatModel, close) },
|
||||
onLocalAliasChanged = { setContactAlias(rhId, contactConnection, it, chatModel) },
|
||||
share = { if (connReqInvitation != null) clipboard.shareText(connReqInvitation) },
|
||||
learnMore = {
|
||||
ModalManager.end.showModalCloseable { close ->
|
||||
AddContactLearnMore(close)
|
||||
|
@ -74,13 +75,13 @@ fun ContactConnectionInfoView(
|
|||
@Composable
|
||||
private fun ContactConnectionInfoLayout(
|
||||
chatModel: ChatModel,
|
||||
connReq: String?,
|
||||
connLink: CreatedConnLink?,
|
||||
showShortLink: MutableState<Boolean>,
|
||||
contactConnection: PendingContactConnection,
|
||||
focusAlias: Boolean,
|
||||
rhId: Long?,
|
||||
deleteConnection: () -> Unit,
|
||||
onLocalAliasChanged: (String) -> Unit,
|
||||
share: () -> Unit,
|
||||
learnMore: () -> Unit,
|
||||
) {
|
||||
@Composable fun incognitoEnabled() {
|
||||
|
@ -126,13 +127,19 @@ private fun ContactConnectionInfoLayout(
|
|||
LocalAliasEditor(contactConnection.id, contactConnection.localAlias, center = false, leadingIcon = true, focus = focusAlias, updateValue = onLocalAliasChanged)
|
||||
}
|
||||
|
||||
SectionView {
|
||||
if (!connReq.isNullOrEmpty() && contactConnection.initiated) {
|
||||
SimpleXLinkQRCode(connReq)
|
||||
if (connLink != null && connLink.connFullLink.isNotEmpty() && contactConnection.initiated) {
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
SectionViewWithButton(
|
||||
stringResource(MR.strings.one_time_link).uppercase(),
|
||||
titleButton = if (connLink.connShortLink == null) null else {{ ToggleShortLinkButton(showShortLink) }}
|
||||
) {
|
||||
SimpleXCreatedLinkQRCode(connLink, short = showShortLink.value)
|
||||
incognitoEnabled()
|
||||
ShareLinkButton(connReq)
|
||||
ShareLinkButton(connLink.simplexChatUri(short = showShortLink.value))
|
||||
OneTimeLinkLearnMoreButton(learnMore)
|
||||
} else {
|
||||
}
|
||||
} else {
|
||||
SectionView {
|
||||
incognitoEnabled()
|
||||
OneTimeLinkLearnMoreButton(learnMore)
|
||||
}
|
||||
|
@ -148,14 +155,14 @@ private fun ContactConnectionInfoLayout(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun ShareLinkButton(connReqInvitation: String) {
|
||||
fun ShareLinkButton(linkUri: String) {
|
||||
val clipboard = LocalClipboardManager.current
|
||||
SettingsActionItem(
|
||||
painterResource(MR.images.ic_share),
|
||||
stringResource(MR.strings.share_invitation_link),
|
||||
click = {
|
||||
chatModel.showingInvitation.value = chatModel.showingInvitation.value?.copy(connChatUsed = true)
|
||||
clipboard.shareText(simplexChatLink(connReqInvitation))
|
||||
clipboard.shareText(simplexChatLink(linkUri))
|
||||
},
|
||||
iconColor = MaterialTheme.colors.primary,
|
||||
textColor = MaterialTheme.colors.primary,
|
||||
|
@ -200,13 +207,13 @@ private fun PreviewContactConnectionInfoView() {
|
|||
SimpleXTheme {
|
||||
ContactConnectionInfoLayout(
|
||||
chatModel = ChatModel,
|
||||
connReq = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D",
|
||||
connLink = CreatedConnLink("https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", null),
|
||||
showShortLink = remember { mutableStateOf(true) },
|
||||
contactConnection = PendingContactConnection.getSampleData(),
|
||||
focusAlias = false,
|
||||
rhId = null,
|
||||
deleteConnection = {},
|
||||
onLocalAliasChanged = {},
|
||||
share = {},
|
||||
learnMore = {}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import SectionBottomSpacer
|
|||
import SectionItemView
|
||||
import SectionTextFooter
|
||||
import SectionView
|
||||
import SectionViewWithButton
|
||||
import TextIconSpaced
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
|
@ -48,17 +49,17 @@ fun ModalData.NewChatView(rh: RemoteHostInfo?, selection: NewChatOption, showQRC
|
|||
val selection = remember { stateGetOrPut("selection") { selection } }
|
||||
val showQRCodeScanner = remember { stateGetOrPut("showQRCodeScanner") { showQRCodeScanner } }
|
||||
val contactConnection: MutableState<PendingContactConnection?> = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(chatModel.showingInvitation.value?.conn) }
|
||||
val connReqInvitation by remember { derivedStateOf { chatModel.showingInvitation.value?.connReq ?: "" } }
|
||||
val connLinkInvitation by remember { derivedStateOf { chatModel.showingInvitation.value?.connLink ?: CreatedConnLink("", null) } }
|
||||
val creatingConnReq = rememberSaveable { mutableStateOf(false) }
|
||||
val pastedLink = rememberSaveable { mutableStateOf("") }
|
||||
LaunchedEffect(selection.value) {
|
||||
if (
|
||||
selection.value == NewChatOption.INVITE
|
||||
&& connReqInvitation.isEmpty()
|
||||
&& connLinkInvitation.connFullLink.isEmpty()
|
||||
&& contactConnection.value == null
|
||||
&& !creatingConnReq.value
|
||||
) {
|
||||
createInvitation(rh?.remoteHostId, creatingConnReq, connReqInvitation, contactConnection)
|
||||
createInvitation(rh?.remoteHostId, creatingConnReq, connLinkInvitation, contactConnection)
|
||||
}
|
||||
}
|
||||
DisposableEffect(Unit) {
|
||||
|
@ -143,12 +144,12 @@ fun ModalData.NewChatView(rh: RemoteHostInfo?, selection: NewChatOption, showQRC
|
|||
Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = this@BoxWithConstraints.maxHeight - 150.dp),
|
||||
verticalArrangement = if (index == NewChatOption.INVITE.ordinal && connReqInvitation.isEmpty()) Arrangement.Center else Arrangement.Top
|
||||
verticalArrangement = if (index == NewChatOption.INVITE.ordinal && connLinkInvitation.connFullLink.isEmpty()) Arrangement.Center else Arrangement.Top
|
||||
) {
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
when (index) {
|
||||
NewChatOption.INVITE.ordinal -> {
|
||||
PrepareAndInviteView(rh?.remoteHostId, contactConnection, connReqInvitation, creatingConnReq)
|
||||
PrepareAndInviteView(rh?.remoteHostId, contactConnection, connLinkInvitation, creatingConnReq)
|
||||
}
|
||||
NewChatOption.CONNECT.ordinal -> {
|
||||
ConnectView(rh?.remoteHostId, showQRCodeScanner, pastedLink, close)
|
||||
|
@ -162,17 +163,17 @@ fun ModalData.NewChatView(rh: RemoteHostInfo?, selection: NewChatOption, showQRC
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun PrepareAndInviteView(rhId: Long?, contactConnection: MutableState<PendingContactConnection?>, connReqInvitation: String, creatingConnReq: MutableState<Boolean>) {
|
||||
if (connReqInvitation.isNotEmpty()) {
|
||||
private fun PrepareAndInviteView(rhId: Long?, contactConnection: MutableState<PendingContactConnection?>, connLinkInvitation: CreatedConnLink, creatingConnReq: MutableState<Boolean>) {
|
||||
if (connLinkInvitation.connFullLink.isNotEmpty()) {
|
||||
InviteView(
|
||||
rhId,
|
||||
connReqInvitation = connReqInvitation,
|
||||
connLinkInvitation = connLinkInvitation,
|
||||
contactConnection = contactConnection,
|
||||
)
|
||||
} else if (creatingConnReq.value) {
|
||||
CreatingLinkProgressView()
|
||||
} else {
|
||||
RetryButton { createInvitation(rhId, creatingConnReq, connReqInvitation, contactConnection) }
|
||||
RetryButton { createInvitation(rhId, creatingConnReq, connLinkInvitation, contactConnection) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,7 +186,7 @@ private fun updateShownConnection(conn: PendingContactConnection) {
|
|||
chatModel.showingInvitation.value = chatModel.showingInvitation.value?.copy(
|
||||
conn = conn,
|
||||
connId = conn.id,
|
||||
connReq = conn.connReqInv ?: "",
|
||||
connLink = conn.connLinkInv ?: CreatedConnLink("", null),
|
||||
connChatUsed = true
|
||||
)
|
||||
}
|
||||
|
@ -449,15 +450,21 @@ fun ActiveProfilePicker(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun InviteView(rhId: Long?, connReqInvitation: String, contactConnection: MutableState<PendingContactConnection?>) {
|
||||
SectionView(stringResource(MR.strings.share_this_1_time_link).uppercase(), headerBottomPadding = 5.dp) {
|
||||
LinkTextView(connReqInvitation, true)
|
||||
}
|
||||
|
||||
private fun InviteView(rhId: Long?, connLinkInvitation: CreatedConnLink, contactConnection: MutableState<PendingContactConnection?>) {
|
||||
val showShortLink = remember { mutableStateOf(true) }
|
||||
Spacer(Modifier.height(10.dp))
|
||||
|
||||
SectionView(stringResource(MR.strings.or_show_this_qr_code).uppercase(), headerBottomPadding = 5.dp) {
|
||||
SimpleXLinkQRCode(connReqInvitation, onShare = { chatModel.markShowingInvitationUsed() })
|
||||
SectionView(stringResource(MR.strings.share_this_1_time_link).uppercase(), headerBottomPadding = 5.dp) {
|
||||
LinkTextView(connLinkInvitation.simplexChatUri(short = showShortLink.value), true)
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
|
||||
SectionViewWithButton(
|
||||
stringResource(MR.strings.or_show_this_qr_code).uppercase(),
|
||||
titleButton = if (connLinkInvitation.connShortLink != null) {{ ToggleShortLinkButton(showShortLink) }} else null
|
||||
) {
|
||||
SimpleXCreatedLinkQRCode(connLinkInvitation, short = showShortLink.value, onShare = { chatModel.markShowingInvitationUsed() })
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
|
@ -528,6 +535,18 @@ private fun InviteView(rhId: Long?, connReqInvitation: String, contactConnection
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ToggleShortLinkButton(short: MutableState<Boolean>) {
|
||||
Text(
|
||||
stringResource(if (short.value) MR.strings.full_link_button_text else MR.strings.short_link_button_text),
|
||||
modifier = Modifier.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null
|
||||
) { short.value = !short.value },
|
||||
style = MaterialTheme.typography.body2, fontSize = 14.sp, color = MaterialTheme.colors.primary
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AddContactLearnMoreButton() {
|
||||
IconButton(
|
||||
|
@ -675,17 +694,17 @@ private suspend fun connect(rhId: Long?, link: String, close: () -> Unit, cleanu
|
|||
private fun createInvitation(
|
||||
rhId: Long?,
|
||||
creatingConnReq: MutableState<Boolean>,
|
||||
connReqInvitation: String,
|
||||
connLinkInvitation: CreatedConnLink,
|
||||
contactConnection: MutableState<PendingContactConnection?>
|
||||
) {
|
||||
if (connReqInvitation.isNotEmpty() || contactConnection.value != null || creatingConnReq.value) return
|
||||
if (connLinkInvitation.connFullLink.isNotEmpty() || contactConnection.value != null || creatingConnReq.value) return
|
||||
creatingConnReq.value = true
|
||||
withBGApi {
|
||||
val (r, alert) = controller.apiAddContact(rhId, incognito = controller.appPrefs.incognito.get())
|
||||
if (r != null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
chatModel.chatsContext.updateContactConnection(rhId, r.second)
|
||||
chatModel.showingInvitation.value = ShowingInvitation(connId = r.second.id, connReq = simplexChatLink(r.first), connChatUsed = false, conn = r.second)
|
||||
chatModel.showingInvitation.value = ShowingInvitation(connId = r.second.id, connLink = r.first, connChatUsed = false, conn = r.second)
|
||||
contactConnection.value = r.second
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -12,13 +12,33 @@ import androidx.compose.ui.unit.dp
|
|||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import boofcv.alg.drawing.FiducialImageEngine
|
||||
import boofcv.alg.fiducial.qrcode.*
|
||||
import chat.simplex.common.model.CryptoFile
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun SimpleXCreatedLinkQRCode(
|
||||
connLink: CreatedConnLink,
|
||||
short: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
padding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING * 2f, vertical = DEFAULT_PADDING_HALF),
|
||||
tintColor: Color = Color(0xff062d56),
|
||||
withLogo: Boolean = true,
|
||||
onShare: (() -> Unit)? = null,
|
||||
) {
|
||||
QRCode(
|
||||
connLink.simplexChatUri(short),
|
||||
modifier,
|
||||
padding,
|
||||
tintColor,
|
||||
withLogo,
|
||||
onShare,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SimpleXLinkQRCode(
|
||||
connReq: String,
|
||||
|
@ -38,14 +58,6 @@ fun SimpleXLinkQRCode(
|
|||
)
|
||||
}
|
||||
|
||||
fun simplexChatLink(uri: String): String {
|
||||
return if (uri.startsWith("simplex:/")) {
|
||||
uri.replace("simplex:/", "https://simplex.chat/")
|
||||
} else {
|
||||
uri
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun QRCode(
|
||||
connReq: String,
|
||||
|
|
|
@ -88,6 +88,13 @@ fun PrivacySettingsView(
|
|||
simplexLinkMode.set(it)
|
||||
chatModel.simplexLinkMode.value = it
|
||||
})
|
||||
if (appPrefs.developerTools.get()) {
|
||||
SettingsPreferenceItem(
|
||||
null,
|
||||
stringResource(MR.strings.privacy_short_links),
|
||||
chatModel.controller.appPrefs.privacyShortLinks
|
||||
)
|
||||
}
|
||||
}
|
||||
SectionDividerSpaced()
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import SectionDividerSpaced
|
|||
import SectionItemView
|
||||
import SectionTextFooter
|
||||
import SectionView
|
||||
import SectionViewWithButton
|
||||
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
|
@ -61,7 +62,8 @@ fun UserAddressView(
|
|||
fun createAddress() {
|
||||
withBGApi {
|
||||
progressIndicator = true
|
||||
val connReqContact = chatModel.controller.apiCreateUserAddress(user?.value?.remoteHostId)
|
||||
val short = appPreferences.privacyShortLinks.get()
|
||||
val connReqContact = chatModel.controller.apiCreateUserAddress(user.value?.remoteHostId, short = short)
|
||||
if (connReqContact != null) {
|
||||
chatModel.userAddress.value = UserContactLinkRec(connReqContact)
|
||||
|
||||
|
@ -102,7 +104,7 @@ fun UserAddressView(
|
|||
sendEmail = { userAddress ->
|
||||
uriHandler.sendEmail(
|
||||
generalGetString(MR.strings.email_invite_subject),
|
||||
generalGetString(MR.strings.email_invite_body).format(simplexChatLink(userAddress.connReqContact))
|
||||
generalGetString(MR.strings.email_invite_body).format(simplexChatLink(userAddress.connLinkContact.connFullLink)) // TODO [short links] replace with short link
|
||||
)
|
||||
},
|
||||
setProfileAddress = ::setProfileAddress,
|
||||
|
@ -198,10 +200,14 @@ private fun UserAddressLayout(
|
|||
} else {
|
||||
val autoAcceptState = remember { mutableStateOf(AutoAcceptState(userAddress)) }
|
||||
val autoAcceptStateSaved = remember { mutableStateOf(autoAcceptState.value) }
|
||||
val showShortLink = remember { mutableStateOf(true) }
|
||||
|
||||
SectionView(stringResource(MR.strings.for_social_media).uppercase()) {
|
||||
SimpleXLinkQRCode(userAddress.connReqContact)
|
||||
ShareAddressButton { share(simplexChatLink(userAddress.connReqContact)) }
|
||||
SectionViewWithButton(
|
||||
stringResource(MR.strings.for_social_media).uppercase(),
|
||||
titleButton = if (userAddress.connLinkContact.connShortLink != null) {{ ToggleShortLinkButton(showShortLink) }} else null
|
||||
) {
|
||||
SimpleXCreatedLinkQRCode(userAddress.connLinkContact, short = showShortLink.value)
|
||||
ShareAddressButton { share(userAddress.connLinkContact.simplexChatUri(short = showShortLink.value)) }
|
||||
// ShareViaEmailButton { sendEmail(userAddress) }
|
||||
BusinessAddressToggle(autoAcceptState) { saveAas(autoAcceptState.value, autoAcceptStateSaved) }
|
||||
AddressSettingsButton(user, userAddress, shareViaProfile, setProfileAddress, saveAas)
|
||||
|
@ -584,7 +590,7 @@ fun PreviewUserAddressLayoutAddressCreated() {
|
|||
SimpleXTheme {
|
||||
UserAddressLayout(
|
||||
user = User.sampleData,
|
||||
userAddress = UserContactLinkRec("https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D"),
|
||||
userAddress = UserContactLinkRec(CreatedConnLink("https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", null)),
|
||||
createAddress = {},
|
||||
share = { _ -> },
|
||||
deleteAddress = {},
|
||||
|
|
|
@ -91,12 +91,14 @@
|
|||
<string name="simplex_link_contact">SimpleX contact address</string>
|
||||
<string name="simplex_link_invitation">SimpleX one-time invitation</string>
|
||||
<string name="simplex_link_group">SimpleX group link</string>
|
||||
<string name="simplex_link_channel">SimpleX channel link</string>
|
||||
<string name="simplex_link_connection">via %1$s</string>
|
||||
<string name="simplex_link_mode">SimpleX links</string>
|
||||
<string name="simplex_link_mode_description">Description</string>
|
||||
<string name="simplex_link_mode_full">Full link</string>
|
||||
<string name="simplex_link_mode_browser">Via browser</string>
|
||||
<string name="simplex_link_mode_browser_warning">Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red.</string>
|
||||
<string name="privacy_short_links">Use short links (BETA)</string>
|
||||
|
||||
<!-- Reports - ChatModel.kt -->
|
||||
<string name="report_reason_spam">Spam</string>
|
||||
|
@ -168,6 +170,8 @@
|
|||
<string name="you_are_already_connected_to_vName_via_this_link">You are already connected to %1$s.</string>
|
||||
<string name="invalid_connection_link">Invalid connection link</string>
|
||||
<string name="please_check_correct_link_and_maybe_ask_for_a_new_one">Please check that you used the correct link or ask your contact to send you another one.</string>
|
||||
<string name="unsupported_connection_link">Unsupported connection link</string>
|
||||
<string name="link_requires_newer_app_version_please_upgrade">This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link.</string>
|
||||
<string name="connection_error_auth">Connection error (AUTH)</string>
|
||||
<string name="connection_error_auth_desc">Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection.</string>
|
||||
<string name="connection_error_blocked">Connection blocked</string>
|
||||
|
@ -795,6 +799,8 @@
|
|||
<string name="one_time_link_short">1-time link</string>
|
||||
<string name="simplex_address">SimpleX address</string>
|
||||
<string name="or_show_this_qr_code">Or show this code</string>
|
||||
<string name="full_link_button_text">Full link</string>
|
||||
<string name="short_link_button_text">Short link</string>
|
||||
<string name="new_chat_share_profile">Share profile</string>
|
||||
<string name="select_chat_profile">Select chat profile</string>
|
||||
<string name="switching_profile_error_title">Error switching profile</string>
|
||||
|
|
|
@ -61,8 +61,9 @@ import Simplex.Chat.Terminal.Main (simplexChatCLI')
|
|||
import Simplex.Chat.Types
|
||||
import Simplex.Chat.Types.Shared
|
||||
import Simplex.Chat.View (serializeChatResponse, simplexChatContact, viewContactName, viewGroupName)
|
||||
import Simplex.Messaging.Agent.Protocol (AConnectionLink (..), ConnectionLink (..), CreatedConnLink (..))
|
||||
import Simplex.Messaging.Agent.Store.Common (withTransaction)
|
||||
import Simplex.Messaging.Agent.Protocol (AConnectionRequestUri (..), SConnectionMode (..), sameConnReqContact)
|
||||
import Simplex.Messaging.Agent.Protocol (SConnectionMode (..), sameConnReqContact, sameShortLinkContact)
|
||||
import qualified Simplex.Messaging.Agent.Store.DB as DB
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.TMap (TMap)
|
||||
|
@ -347,15 +348,15 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName
|
|||
setGroupRegOwner st gr owner
|
||||
let GroupInfo {groupId, groupProfile = GroupProfile {displayName}} = g
|
||||
notifyOwner gr $ "Joined the group " <> displayName <> ", creating the link…"
|
||||
sendChatCmd cc (APICreateGroupLink groupId GRMember) >>= \case
|
||||
CRGroupLinkCreated {connReqContact} -> do
|
||||
sendChatCmd cc (APICreateGroupLink groupId GRMember False) >>= \case
|
||||
CRGroupLinkCreated {connLinkContact = CCLink gLink _} -> do
|
||||
setGroupStatus st gr GRSPendingUpdate
|
||||
notifyOwner
|
||||
gr
|
||||
"Created the public link to join the group via this directory service that is always online.\n\n\
|
||||
\Please add it to the group welcome message.\n\
|
||||
\For example, add:"
|
||||
notifyOwner gr $ "Link to join the group " <> displayName <> ": " <> strEncodeTxt (simplexChatContact connReqContact)
|
||||
notifyOwner gr $ "Link to join the group " <> displayName <> ": " <> strEncodeTxt (simplexChatContact gLink)
|
||||
CRChatCmdError _ (ChatError e) -> case e of
|
||||
CEGroupUserRole {} -> notifyOwner gr "Failed creating group link, as service is no longer an admin."
|
||||
CEGroupMemberUserRemoved -> notifyOwner gr "Failed creating group link, as service is removed from the group."
|
||||
|
@ -445,13 +446,15 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName
|
|||
groupProfileUpdate = profileUpdate <$> sendChatCmd cc (APIGetGroupLink groupId)
|
||||
where
|
||||
profileUpdate = \case
|
||||
CRGroupLink {connReqContact} ->
|
||||
CRGroupLink {connLinkContact = CCLink cr sl_} ->
|
||||
let hadLinkBefore = profileHasGroupLink fromGroup
|
||||
hasLinkNow = profileHasGroupLink toGroup
|
||||
profileHasGroupLink GroupInfo {groupProfile = gp} =
|
||||
maybe False (any ftHasLink) $ parseMaybeMarkdownList =<< description gp
|
||||
ftHasLink = \case
|
||||
FormattedText (Just SimplexLink {simplexUri = ACR SCMContact cr'}) _ -> sameConnReqContact connReqContact cr'
|
||||
FormattedText (Just SimplexLink {simplexUri = ACL SCMContact cLink}) _ -> case cLink of
|
||||
CLFull cr' -> sameConnReqContact cr' cr
|
||||
CLShort sl' -> maybe False (sameShortLinkContact sl') sl_
|
||||
_ -> False
|
||||
in if
|
||||
| hadLinkBefore && hasLinkNow -> GPHasServiceLink
|
||||
|
@ -713,7 +716,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName
|
|||
case mRole_ of
|
||||
Nothing ->
|
||||
getGroupLinkRole cc user g >>= \case
|
||||
Just (_, gLink, mRole) -> do
|
||||
Just (_, CCLink gLink _, mRole) -> do
|
||||
let anotherRole = case mRole of GRObserver -> GRMember; _ -> GRObserver
|
||||
sendReply $
|
||||
initialRole n mRole
|
||||
|
@ -893,10 +896,10 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName
|
|||
let groupRef = groupReference' groupId gName
|
||||
withGroupAndReg sendReply groupId gName $ \_ _ ->
|
||||
sendChatCmd cc (APIGetGroupLink groupId) >>= \case
|
||||
CRGroupLink {connReqContact, memberRole} ->
|
||||
CRGroupLink {connLinkContact = CCLink cReq _, memberRole} ->
|
||||
sendReply $ T.unlines
|
||||
[ "The link to join the group " <> groupRef <> ":",
|
||||
strEncodeTxt $ simplexChatContact connReqContact,
|
||||
strEncodeTxt $ simplexChatContact cReq,
|
||||
"New member role: " <> strEncodeTxt memberRole
|
||||
]
|
||||
CRChatCmdError _ (ChatErrorStore (SEGroupLinkNotFound _)) ->
|
||||
|
@ -1039,7 +1042,7 @@ vr :: ChatController -> VersionRangeChat
|
|||
vr ChatController {config = ChatConfig {chatVRange}} = chatVRange
|
||||
{-# INLINE vr #-}
|
||||
|
||||
getGroupLinkRole :: ChatController -> User -> GroupInfo -> IO (Maybe (Int64, ConnReqContact, GroupMemberRole))
|
||||
getGroupLinkRole :: ChatController -> User -> GroupInfo -> IO (Maybe (Int64, CreatedLinkContact, GroupMemberRole))
|
||||
getGroupLinkRole cc user gInfo =
|
||||
withDB "getGroupLink" cc $ \db -> getGroupLink db user gInfo
|
||||
|
||||
|
@ -1047,7 +1050,7 @@ setGroupLinkRole :: ChatController -> GroupInfo -> GroupMemberRole -> IO (Maybe
|
|||
setGroupLinkRole cc GroupInfo {groupId} mRole = resp <$> sendChatCmd cc (APIGroupLinkMemberRole groupId mRole)
|
||||
where
|
||||
resp = \case
|
||||
CRGroupLink _ _ gLink _ -> Just gLink
|
||||
CRGroupLink _ _ (CCLink gLink _) _ -> Just gLink
|
||||
_ -> Nothing
|
||||
|
||||
unexpectedError :: Text -> Text
|
||||
|
|
|
@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
|
|||
source-repository-package
|
||||
type: git
|
||||
location: https://github.com/simplex-chat/simplexmq.git
|
||||
tag: 9abc0fa88dd70a7e30a041697335bb663c1140b7
|
||||
tag: 305f79d2a66a8d122bf457e023988200bb7fe00c
|
||||
|
||||
source-repository-package
|
||||
type: git
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"https://github.com/simplex-chat/simplexmq.git"."9abc0fa88dd70a7e30a041697335bb663c1140b7" = "0gaqqvhb5s9xw5mq2iy8swp7w34zrkwkbjlyhggz2q9nr5680z84";
|
||||
"https://github.com/simplex-chat/simplexmq.git"."305f79d2a66a8d122bf457e023988200bb7fe00c" = "1lawc5pf4hgc6wym2xz8gi92izi1vk98ppv3ldrpajz1mq62ifpc";
|
||||
"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";
|
||||
|
|
|
@ -55,6 +55,7 @@ library
|
|||
Simplex.Chat.Mobile.WebRTC
|
||||
Simplex.Chat.Operators
|
||||
Simplex.Chat.Operators.Conditions
|
||||
Simplex.Chat.Operators.Presets
|
||||
Simplex.Chat.Options
|
||||
Simplex.Chat.Options.DB
|
||||
Simplex.Chat.ProfileGenerator
|
||||
|
@ -102,6 +103,7 @@ library
|
|||
Simplex.Chat.Options.Postgres
|
||||
Simplex.Chat.Store.Postgres.Migrations
|
||||
Simplex.Chat.Store.Postgres.Migrations.M20241220_initial
|
||||
Simplex.Chat.Store.Postgres.Migrations.M20250402_short_links
|
||||
else
|
||||
exposed-modules:
|
||||
Simplex.Chat.Archive
|
||||
|
@ -231,6 +233,7 @@ library
|
|||
Simplex.Chat.Store.SQLite.Migrations.M20250126_mentions
|
||||
Simplex.Chat.Store.SQLite.Migrations.M20250129_delete_unused_contacts
|
||||
Simplex.Chat.Store.SQLite.Migrations.M20250130_indexes
|
||||
Simplex.Chat.Store.SQLite.Migrations.M20250402_short_links
|
||||
other-modules:
|
||||
Paths_simplex_chat
|
||||
hs-source-dirs:
|
||||
|
|
|
@ -30,6 +30,7 @@ import Data.Time.Clock (getCurrentTime)
|
|||
import Simplex.Chat.Controller
|
||||
import Simplex.Chat.Library.Commands
|
||||
import Simplex.Chat.Operators
|
||||
import Simplex.Chat.Operators.Presets
|
||||
import Simplex.Chat.Options
|
||||
import Simplex.Chat.Options.DB
|
||||
import Simplex.Chat.Protocol
|
||||
|
@ -39,7 +40,7 @@ import Simplex.Chat.Types
|
|||
import Simplex.Chat.Util (shuffle)
|
||||
import Simplex.FileTransfer.Client.Presets (defaultXFTPServers)
|
||||
import Simplex.Messaging.Agent as Agent
|
||||
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers (..), ServerCfg (..), ServerRoles (..), allRoles, createAgentStore, defaultAgentConfig, presetServerCfg)
|
||||
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers (..), ServerCfg (..), allRoles, createAgentStore, defaultAgentConfig, presetServerCfg)
|
||||
import Simplex.Messaging.Agent.Protocol
|
||||
import Simplex.Messaging.Agent.Store.Common (DBStore (dbNew))
|
||||
import qualified Simplex.Messaging.Agent.Store.DB as DB
|
||||
|
@ -51,34 +52,6 @@ import qualified Simplex.Messaging.TMap as TM
|
|||
import qualified UnliftIO.Exception as E
|
||||
import UnliftIO.STM
|
||||
|
||||
operatorSimpleXChat :: NewServerOperator
|
||||
operatorSimpleXChat =
|
||||
ServerOperator
|
||||
{ operatorId = DBNewEntity,
|
||||
operatorTag = Just OTSimplex,
|
||||
tradeName = "SimpleX Chat",
|
||||
legalName = Just "SimpleX Chat Ltd",
|
||||
serverDomains = ["simplex.im"],
|
||||
conditionsAcceptance = CARequired Nothing,
|
||||
enabled = True,
|
||||
smpRoles = allRoles,
|
||||
xftpRoles = allRoles
|
||||
}
|
||||
|
||||
operatorFlux :: NewServerOperator
|
||||
operatorFlux =
|
||||
ServerOperator
|
||||
{ operatorId = DBNewEntity,
|
||||
operatorTag = Just OTFlux,
|
||||
tradeName = "Flux",
|
||||
legalName = Just "InFlux Technologies Limited",
|
||||
serverDomains = ["simplexonflux.com"],
|
||||
conditionsAcceptance = CARequired Nothing,
|
||||
enabled = False,
|
||||
smpRoles = ServerRoles {storage = False, proxy = True},
|
||||
xftpRoles = ServerRoles {storage = False, proxy = True}
|
||||
}
|
||||
|
||||
defaultChatConfig :: ChatConfig
|
||||
defaultChatConfig =
|
||||
ChatConfig
|
||||
|
@ -112,6 +85,10 @@ defaultChatConfig =
|
|||
ntf = _defaultNtfServers,
|
||||
netCfg = defaultNetworkConfig
|
||||
},
|
||||
-- please note: if these servers are changed, this option needs to be split to two,
|
||||
-- to have a different set of servers on the receiving end and on the sending end.
|
||||
-- To preserve backward compatibility receiving end should update before the sending.
|
||||
shortLinkPresetServers = allPresetServers,
|
||||
tbqSize = 1024,
|
||||
fileChunkSize = 15780, -- do not change
|
||||
xftpDescrPartSize = 14000,
|
||||
|
@ -133,53 +110,6 @@ defaultChatConfig =
|
|||
chatHooks = defaultChatHooks
|
||||
}
|
||||
|
||||
simplexChatSMPServers :: [NewUserServer 'PSMP]
|
||||
simplexChatSMPServers =
|
||||
map
|
||||
(presetServer True)
|
||||
[ "smp://0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU=@smp8.simplex.im,beccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion",
|
||||
"smp://SkIkI6EPd2D63F4xFKfHk7I1UGZVNn6k1QWZ5rcyr6w=@smp9.simplex.im,jssqzccmrcws6bhmn77vgmhfjmhwlyr3u7puw4erkyoosywgl67slqqd.onion",
|
||||
"smp://6iIcWT_dF2zN_w5xzZEY7HI2Prbh3ldP07YTyDexPjE=@smp10.simplex.im,rb2pbttocvnbrngnwziclp2f4ckjq65kebafws6g4hy22cdaiv5dwjqd.onion",
|
||||
"smp://1OwYGt-yqOfe2IyVHhxz3ohqo3aCCMjtB-8wn4X_aoY=@smp11.simplex.im,6ioorbm6i3yxmuoezrhjk6f6qgkc4syabh7m3so74xunb5nzr4pwgfqd.onion",
|
||||
"smp://UkMFNAXLXeAAe0beCa4w6X_zp18PwxSaSjY17BKUGXQ=@smp12.simplex.im,ie42b5weq7zdkghocs3mgxdjeuycheeqqmksntj57rmejagmg4eor5yd.onion",
|
||||
"smp://enEkec4hlR3UtKx2NMpOUK_K4ZuDxjWBO1d9Y4YXVaA=@smp14.simplex.im,aspkyu2sopsnizbyfabtsicikr2s4r3ti35jogbcekhm3fsoeyjvgrid.onion",
|
||||
"smp://h--vW7ZSkXPeOUpfxlFGgauQmXNFOzGoizak7Ult7cw=@smp15.simplex.im,oauu4bgijybyhczbnxtlggo6hiubahmeutaqineuyy23aojpih3dajad.onion",
|
||||
"smp://hejn2gVIqNU6xjtGM3OwQeuk8ZEbDXVJXAlnSBJBWUA=@smp16.simplex.im,p3ktngodzi6qrf7w64mmde3syuzrv57y55hxabqcq3l5p6oi7yzze6qd.onion",
|
||||
"smp://ZKe4uxF4Z_aLJJOEsC-Y6hSkXgQS5-oc442JQGkyP8M=@smp17.simplex.im,ogtwfxyi3h2h5weftjjpjmxclhb5ugufa5rcyrmg7j4xlch7qsr5nuqd.onion",
|
||||
"smp://PtsqghzQKU83kYTlQ1VKg996dW4Cw4x_bvpKmiv8uns=@smp18.simplex.im,lyqpnwbs2zqfr45jqkncwpywpbtq7jrhxnib5qddtr6npjyezuwd3nqd.onion",
|
||||
"smp://N_McQS3F9TGoh4ER0QstUf55kGnNSd-wXfNPZ7HukcM=@smp19.simplex.im,i53bbtoqhlc365k6kxzwdp5w3cdt433s7bwh3y32rcbml2vztiyyz5id.onion"
|
||||
]
|
||||
<> map
|
||||
(presetServer False)
|
||||
[ "smp://u2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU=@smp4.simplex.im,o5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion",
|
||||
"smp://hpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg=@smp5.simplex.im,jjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion",
|
||||
"smp://PQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo=@smp6.simplex.im,bylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion"
|
||||
]
|
||||
|
||||
fluxSMPServers :: [NewUserServer 'PSMP]
|
||||
fluxSMPServers =
|
||||
map
|
||||
(presetServer True)
|
||||
[ "smp://xQW_ufMkGE20UrTlBl8QqceG1tbuylXhr9VOLPyRJmw=@smp1.simplexonflux.com,qb4yoanyl4p7o33yrknv4rs6qo7ugeb2tu2zo66sbebezs4cpyosarid.onion",
|
||||
"smp://LDnWZVlAUInmjmdpQQoIo6FUinRXGe0q3zi5okXDE4s=@smp2.simplexonflux.com,yiqtuh3q4x7hgovkomafsod52wvfjucdljqbbipg5sdssnklgongxbqd.onion",
|
||||
"smp://1jne379u7IDJSxAvXbWb_JgoE7iabcslX0LBF22Rej0=@smp3.simplexonflux.com,a5lm4k7ufei66cdck6fy63r4lmkqy3dekmmb7jkfdm5ivi6kfaojshad.onion",
|
||||
"smp://xmAmqj75I9mWrUihLUlI0ZuNLXlIwFIlHRq5Pb6cHAU=@smp4.simplexonflux.com,qpcz2axyy66u26hfdd2e23uohcf3y6c36mn7dcuilcgnwjasnrvnxjqd.onion",
|
||||
"smp://rWvBYyTamuRCBYb_KAn-nsejg879ndhiTg5Sq3k0xWA=@smp5.simplexonflux.com,4ao347qwiuluyd45xunmii4skjigzuuox53hpdsgbwxqafd4yrticead.onion",
|
||||
"smp://PN7-uqLBToqlf1NxHEaiL35lV2vBpXq8Nj8BW11bU48=@smp6.simplexonflux.com,hury6ot3ymebbr2535mlp7gcxzrjpc6oujhtfxcfh2m4fal4xw5fq6qd.onion"
|
||||
]
|
||||
|
||||
fluxXFTPServers :: [NewUserServer 'PXFTP]
|
||||
fluxXFTPServers =
|
||||
map
|
||||
(presetServer True)
|
||||
[ "xftp://92Sctlc09vHl_nAqF2min88zKyjdYJ9mgxRCJns5K2U=@xftp1.simplexonflux.com,apl3pumq3emwqtrztykyyoomdx4dg6ysql5zek2bi3rgznz7ai3odkid.onion",
|
||||
"xftp://YBXy4f5zU1CEhnbbCzVWTNVNsaETcAGmYqGNxHntiE8=@xftp2.simplexonflux.com,c5jjecisncnngysah3cz2mppediutfelco4asx65mi75d44njvua3xid.onion",
|
||||
"xftp://ARQO74ZSvv2OrulRF3CdgwPz_AMy27r0phtLSq5b664=@xftp3.simplexonflux.com,dc4mohiubvbnsdfqqn7xhlhpqs5u4tjzp7xpz6v6corwvzvqjtaqqiqd.onion",
|
||||
"xftp://ub2jmAa9U0uQCy90O-fSUNaYCj6sdhl49Jh3VpNXP58=@xftp4.simplexonflux.com,4qq5pzier3i4yhpuhcrhfbl6j25udc4czoyascrj4yswhodhfwev3nyd.onion",
|
||||
"xftp://Rh19D5e4Eez37DEE9hAlXDB3gZa1BdFYJTPgJWPO9OI=@xftp5.simplexonflux.com,q7itltdn32hjmgcqwhow4tay5ijetng3ur32bolssw32fvc5jrwvozad.onion",
|
||||
"xftp://0AznwoyfX8Od9T_acp1QeeKtxUi676IBIiQjXVwbdyU=@xftp6.simplexonflux.com,upvzf23ou6nrmaf3qgnhd6cn3d74tvivlmz3p7wdfwq6fhthjrjiiqid.onion"
|
||||
]
|
||||
|
||||
logCfg :: LogConfig
|
||||
logCfg = LogConfig {lc_file = Nothing, lc_stderr = True}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import qualified Data.ByteString.Char8 as B
|
|||
import Data.List.NonEmpty (NonEmpty)
|
||||
import qualified Data.List.NonEmpty as L
|
||||
import qualified Data.Map.Strict as M
|
||||
import Data.Maybe (isJust)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import Simplex.Chat.Controller
|
||||
|
@ -24,6 +25,7 @@ import Simplex.Chat.Messages.CIContent
|
|||
import Simplex.Chat.Protocol (MsgContent (..))
|
||||
import Simplex.Chat.Store
|
||||
import Simplex.Chat.Types (Contact (..), ContactId, IsContact (..), User (..))
|
||||
import Simplex.Messaging.Agent.Protocol (CreatedConnLink (..))
|
||||
import Simplex.Messaging.Encoding.String (strEncode)
|
||||
import System.Exit (exitFailure)
|
||||
|
||||
|
@ -49,16 +51,18 @@ initializeBotAddress = initializeBotAddress' True
|
|||
initializeBotAddress' :: Bool -> ChatController -> IO ()
|
||||
initializeBotAddress' logAddress cc = do
|
||||
sendChatCmd cc ShowMyAddress >>= \case
|
||||
CRUserContactLink _ UserContactLink {connReqContact} -> showBotAddress connReqContact
|
||||
CRUserContactLink _ UserContactLink {connLinkContact} -> showBotAddress connLinkContact
|
||||
CRChatCmdError _ (ChatErrorStore SEUserContactLinkNotFound) -> do
|
||||
when logAddress $ putStrLn "No bot address, creating..."
|
||||
sendChatCmd cc CreateMyAddress >>= \case
|
||||
CRUserContactLinkCreated _ uri -> showBotAddress uri
|
||||
-- TODO [short links] create short link by default
|
||||
sendChatCmd cc (CreateMyAddress False) >>= \case
|
||||
CRUserContactLinkCreated _ ccLink -> showBotAddress ccLink
|
||||
_ -> putStrLn "can't create bot address" >> exitFailure
|
||||
_ -> putStrLn "unexpected response" >> exitFailure
|
||||
where
|
||||
showBotAddress uri = do
|
||||
when logAddress $ putStrLn $ "Bot's contact address is: " <> B.unpack (strEncode uri)
|
||||
showBotAddress (CCLink uri shortUri) = do
|
||||
when logAddress $ putStrLn $ "Bot's contact address is: " <> B.unpack (maybe (strEncode uri) strEncode shortUri)
|
||||
when (isJust shortUri) $ putStrLn $ "Full contact address for old clients: " <> B.unpack (strEncode uri)
|
||||
void $ sendChatCmd cc $ AddressAutoAccept $ Just AutoAccept {businessAddress = False, acceptIncognito = False, autoReply = Nothing}
|
||||
|
||||
sendMessage :: ChatController -> Contact -> Text -> IO ()
|
||||
|
|
|
@ -138,6 +138,7 @@ data ChatConfig = ChatConfig
|
|||
chatVRange :: VersionRangeChat,
|
||||
confirmMigrations :: MigrationConfirmation,
|
||||
presetServers :: PresetServers,
|
||||
shortLinkPresetServers :: NonEmpty SMPServer,
|
||||
tbqSize :: Natural,
|
||||
fileChunkSize :: Integer,
|
||||
xftpDescrPartSize :: Int,
|
||||
|
@ -364,7 +365,7 @@ data ChatCommand
|
|||
| APILeaveGroup GroupId
|
||||
| APIListMembers GroupId
|
||||
| APIUpdateGroupProfile GroupId GroupProfile
|
||||
| APICreateGroupLink GroupId GroupMemberRole
|
||||
| APICreateGroupLink GroupId GroupMemberRole CreateShortLink
|
||||
| APIGroupLinkMemberRole GroupId GroupMemberRole
|
||||
| APIDeleteGroupLink GroupId
|
||||
| APIGetGroupLink GroupId
|
||||
|
@ -437,21 +438,21 @@ data ChatCommand
|
|||
| EnableGroupMember GroupName ContactName
|
||||
| ChatHelp HelpSection
|
||||
| Welcome
|
||||
| APIAddContact UserId IncognitoEnabled
|
||||
| AddContact IncognitoEnabled
|
||||
| APIAddContact UserId CreateShortLink IncognitoEnabled
|
||||
| AddContact CreateShortLink IncognitoEnabled
|
||||
| APISetConnectionIncognito Int64 IncognitoEnabled
|
||||
| APIChangeConnectionUser Int64 UserId -- new user id to switch connection to
|
||||
| APIConnectPlan UserId AConnectionRequestUri
|
||||
| APIConnect UserId IncognitoEnabled (Maybe AConnectionRequestUri)
|
||||
| Connect IncognitoEnabled (Maybe AConnectionRequestUri)
|
||||
| APIConnectPlan UserId AConnectionLink
|
||||
| APIConnect UserId IncognitoEnabled (Maybe ACreatedConnLink)
|
||||
| Connect IncognitoEnabled (Maybe AConnectionLink)
|
||||
| APIConnectContactViaAddress UserId IncognitoEnabled ContactId
|
||||
| ConnectSimplex IncognitoEnabled -- UserId (not used in UI)
|
||||
| DeleteContact ContactName ChatDeleteMode
|
||||
| ClearContact ContactName
|
||||
| APIListContacts UserId
|
||||
| ListContacts
|
||||
| APICreateMyAddress UserId
|
||||
| CreateMyAddress
|
||||
| APICreateMyAddress UserId CreateShortLink
|
||||
| CreateMyAddress CreateShortLink
|
||||
| APIDeleteMyAddress UserId
|
||||
| DeleteMyAddress
|
||||
| APIShowMyAddress UserId
|
||||
|
@ -492,7 +493,7 @@ data ChatCommand
|
|||
| ShowGroupProfile GroupName
|
||||
| UpdateGroupDescription GroupName (Maybe Text)
|
||||
| ShowGroupDescription GroupName
|
||||
| CreateGroupLink GroupName GroupMemberRole
|
||||
| CreateGroupLink GroupName GroupMemberRole CreateShortLink
|
||||
| GroupLinkMemberRole GroupName GroupMemberRole
|
||||
| DeleteGroupLink GroupName
|
||||
| ShowGroupLink GroupName
|
||||
|
@ -674,10 +675,10 @@ data ChatResponse
|
|||
| CRUserProfileNoChange {user :: User}
|
||||
| CRUserPrivacy {user :: User, updatedUser :: User}
|
||||
| CRVersionInfo {versionInfo :: CoreVersionInfo, chatMigrations :: [UpMigration], agentMigrations :: [UpMigration]}
|
||||
| CRInvitation {user :: User, connReqInvitation :: ConnReqInvitation, connection :: PendingContactConnection}
|
||||
| CRInvitation {user :: User, connLinkInvitation :: CreatedLinkInvitation, connection :: PendingContactConnection}
|
||||
| CRConnectionIncognitoUpdated {user :: User, toConnection :: PendingContactConnection}
|
||||
| CRConnectionUserChanged {user :: User, fromConnection :: PendingContactConnection, toConnection :: PendingContactConnection, newUser :: User}
|
||||
| CRConnectionPlan {user :: User, connectionPlan :: ConnectionPlan}
|
||||
| CRConnectionPlan {user :: User, connLink :: ACreatedConnLink, connectionPlan :: ConnectionPlan}
|
||||
| CRSentConfirmation {user :: User, connection :: PendingContactConnection}
|
||||
| CRSentInvitation {user :: User, connection :: PendingContactConnection, customUserProfile :: Maybe Profile}
|
||||
| CRSentInvitationToContact {user :: User, contact :: Contact, customUserProfile :: Maybe Profile}
|
||||
|
@ -687,7 +688,7 @@ data ChatResponse
|
|||
| CRContactDeleted {user :: User, contact :: Contact}
|
||||
| CRContactDeletedByContact {user :: User, contact :: Contact}
|
||||
| CRChatCleared {user :: User, chatInfo :: AChatInfo}
|
||||
| CRUserContactLinkCreated {user :: User, connReqContact :: ConnReqContact}
|
||||
| CRUserContactLinkCreated {user :: User, connLinkContact :: CreatedLinkContact}
|
||||
| CRUserContactLinkDeleted {user :: User}
|
||||
| CRReceivedContactRequest {user :: User, contactRequest :: UserContactRequest}
|
||||
| CRAcceptingContactRequest {user :: User, contact :: Contact}
|
||||
|
@ -765,8 +766,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, connReqContact :: ConnReqContact, memberRole :: GroupMemberRole}
|
||||
| CRGroupLink {user :: User, groupInfo :: GroupInfo, connReqContact :: ConnReqContact, memberRole :: GroupMemberRole}
|
||||
| CRGroupLinkCreated {user :: User, groupInfo :: GroupInfo, connLinkContact :: CreatedLinkContact, memberRole :: GroupMemberRole}
|
||||
| CRGroupLink {user :: User, groupInfo :: GroupInfo, connLinkContact :: CreatedLinkContact, memberRole :: GroupMemberRole}
|
||||
| CRGroupLinkDeleted {user :: User, groupInfo :: GroupInfo}
|
||||
| CRAcceptingGroupJoinRequestMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember}
|
||||
| CRNoMemberContactCreating {user :: User, groupInfo :: GroupInfo, member :: GroupMember} -- only used in CLI
|
||||
|
@ -941,6 +942,7 @@ data ConnectionPlan
|
|||
= CPInvitationLink {invitationLinkPlan :: InvitationLinkPlan}
|
||||
| CPContactAddress {contactAddressPlan :: ContactAddressPlan}
|
||||
| CPGroupLink {groupLinkPlan :: GroupLinkPlan}
|
||||
| CPError {chatError :: ChatError}
|
||||
deriving (Show)
|
||||
|
||||
data InvitationLinkPlan
|
||||
|
@ -984,6 +986,7 @@ connectionPlanProceed = \case
|
|||
GLPOwnLink _ -> True
|
||||
GLPConnectingConfirmReconnect -> True
|
||||
_ -> False
|
||||
CPError _ -> True
|
||||
|
||||
data ForwardConfirmation
|
||||
= FCFilesNotAccepted {fileIds :: [FileTransferId]}
|
||||
|
@ -1247,8 +1250,8 @@ data ChatErrorType
|
|||
| CEChatNotStarted
|
||||
| CEChatNotStopped
|
||||
| CEChatStoreChanged
|
||||
| CEConnectionPlan {connectionPlan :: ConnectionPlan}
|
||||
| CEInvalidConnReq
|
||||
| CEUnsupportedConnReq
|
||||
| CEInvalidChatMessage {connection :: Connection, msgMeta :: Maybe MsgMetaJSON, messageData :: Text, message :: String}
|
||||
| CEContactNotFound {contactName :: ContactName, suspectedMember :: Maybe (GroupInfo, GroupMember)}
|
||||
| CEContactNotReady {contact :: Contact}
|
||||
|
@ -1583,8 +1586,6 @@ $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CAP") ''ContactAddressPlan)
|
|||
|
||||
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "GLP") ''GroupLinkPlan)
|
||||
|
||||
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CP") ''ConnectionPlan)
|
||||
|
||||
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "FC") ''ForwardConfirmation)
|
||||
|
||||
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CE") ''ChatErrorType)
|
||||
|
@ -1599,6 +1600,8 @@ $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "DB") ''DatabaseError)
|
|||
|
||||
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "Chat") ''ChatError)
|
||||
|
||||
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CP") ''ConnectionPlan)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''AppFilePathsConfig)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''ContactSubStatus)
|
||||
|
|
|
@ -1647,16 +1647,18 @@ processChatCommand' vr = \case
|
|||
EnableGroupMember gName mName -> withMemberName gName mName $ \gId mId -> APIEnableGroupMember gId mId
|
||||
ChatHelp section -> pure $ CRChatHelp section
|
||||
Welcome -> withUser $ pure . CRWelcome
|
||||
APIAddContact userId incognito -> withUserId userId $ \user -> procCmd $ do
|
||||
APIAddContact userId short incognito -> withUserId userId $ \user -> procCmd $ do
|
||||
-- [incognito] generate profile for connection
|
||||
incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
(connId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing IKPQOn subMode
|
||||
let userData = shortLinkUserData short
|
||||
(connId, ccLink) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation userData Nothing IKPQOn subMode
|
||||
ccLink' <- shortenCreatedLink ccLink
|
||||
-- TODO PQ pass minVersion from the current range
|
||||
conn <- withFastStore' $ \db -> createDirectConnection db user connId cReq ConnNew incognitoProfile subMode initialChatVersion PQSupportOn
|
||||
pure $ CRInvitation user cReq conn
|
||||
AddContact incognito -> withUser $ \User {userId} ->
|
||||
processChatCommand $ APIAddContact userId incognito
|
||||
conn <- withFastStore' $ \db -> createDirectConnection db user connId ccLink' ConnNew incognitoProfile subMode initialChatVersion PQSupportOn
|
||||
pure $ CRInvitation user ccLink' conn
|
||||
AddContact short incognito -> withUser $ \User {userId} ->
|
||||
processChatCommand $ APIAddContact userId short incognito
|
||||
APISetConnectionIncognito connId incognito -> withUser $ \user@User {userId} -> do
|
||||
conn'_ <- withFastStore $ \db -> do
|
||||
conn@PendingContactConnection {pccConnStatus, customUserProfileId} <- getPendingContactConnection db userId connId
|
||||
|
@ -1674,9 +1676,9 @@ processChatCommand' vr = \case
|
|||
Nothing -> throwChatError CEConnectionIncognitoChangeProhibited
|
||||
APIChangeConnectionUser connId newUserId -> withUser $ \user@User {userId} -> do
|
||||
conn <- withFastStore $ \db -> getPendingContactConnection db userId connId
|
||||
let PendingContactConnection {pccConnStatus, connReqInv} = conn
|
||||
case (pccConnStatus, connReqInv) of
|
||||
(ConnNew, Just cReqInv) -> do
|
||||
let PendingContactConnection {pccConnStatus, connLinkInv} = conn
|
||||
case (pccConnStatus, connLinkInv) of
|
||||
(ConnNew, Just (CCLink cReqInv _)) -> do
|
||||
newUser <- privateGetUser newUserId
|
||||
conn' <- ifM (canKeepLink cReqInv newUser) (updateConnRecord user conn newUser) (recreateConn user conn newUser)
|
||||
pure $ CRConnectionUserChanged user conn conn' newUser
|
||||
|
@ -1697,19 +1699,21 @@ processChatCommand' vr = \case
|
|||
forM_ customUserProfileId $ \profileId ->
|
||||
deletePCCIncognitoProfile db user profileId
|
||||
pure conn'
|
||||
recreateConn user conn@PendingContactConnection {customUserProfileId} newUser = do
|
||||
recreateConn user conn@PendingContactConnection {customUserProfileId, connLinkInv} newUser = do
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
(agConnId, cReq) <- withAgent $ \a -> createConnection a (aUserId newUser) True SCMInvitation Nothing IKPQOn subMode
|
||||
let userData = shortLinkUserData $ isJust $ connShortLink =<< connLinkInv
|
||||
(agConnId, ccLink) <- withAgent $ \a -> createConnection a (aUserId newUser) True SCMInvitation userData Nothing IKPQOn subMode
|
||||
ccLink' <- shortenCreatedLink ccLink
|
||||
conn' <- withFastStore' $ \db -> do
|
||||
deleteConnectionRecord db user connId
|
||||
forM_ customUserProfileId $ \profileId ->
|
||||
deletePCCIncognitoProfile db user profileId
|
||||
createDirectConnection db newUser agConnId cReq ConnNew Nothing subMode initialChatVersion PQSupportOn
|
||||
createDirectConnection db newUser agConnId ccLink' ConnNew Nothing subMode initialChatVersion PQSupportOn
|
||||
deleteAgentConnectionAsync user (aConnId' conn)
|
||||
pure conn'
|
||||
APIConnectPlan userId cReqUri -> withUserId userId $ \user ->
|
||||
CRConnectionPlan user <$> connectPlan user cReqUri
|
||||
APIConnect userId incognito (Just (ACR SCMInvitation cReq@(CRInvitationUri crData e2e))) -> withUserId userId $ \user -> withInvitationLock "connect" (strEncode cReq) . procCmd $ do
|
||||
APIConnectPlan userId cLink -> withUserId userId $ \user ->
|
||||
uncurry (CRConnectionPlan user) <$> connectPlan user cLink
|
||||
APIConnect userId incognito (Just (ACCL SCMInvitation (CCLink cReq@(CRInvitationUri crData e2e) sLnk_))) -> withUserId userId $ \user -> withInvitationLock "connect" (strEncode cReq) . procCmd $ do
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
-- [incognito] generate profile to send
|
||||
incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
|
||||
|
@ -1732,7 +1736,8 @@ processChatCommand' vr = \case
|
|||
where
|
||||
joinNewConn chatV dm = do
|
||||
connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup'
|
||||
pcc <- withFastStore' $ \db -> createDirectConnection db user connId cReq ConnPrepared (incognitoProfile $> profileToSend) subMode chatV pqSup'
|
||||
let ccLink = CCLink cReq $ serverShortLink <$> sLnk_
|
||||
pcc <- withFastStore' $ \db -> createDirectConnection db user connId ccLink ConnPrepared (incognitoProfile $> profileToSend) subMode chatV pqSup'
|
||||
joinPreparedConn connId pcc dm
|
||||
joinPreparedConn connId pcc@PendingContactConnection {pccConnId} dm = do
|
||||
void $ withAgent $ \a -> joinConnection a (aUserId user) connId True cReq dm pqSup' subMode
|
||||
|
@ -1742,43 +1747,40 @@ processChatCommand' vr = \case
|
|||
( CRInvitationUri crData {crScheme = SSSimplex} e2e,
|
||||
CRInvitationUri crData {crScheme = simplexChat} e2e
|
||||
)
|
||||
APIConnect userId incognito (Just (ACR SCMContact cReq)) -> withUserId userId $ \user -> connectViaContact user incognito cReq
|
||||
APIConnect userId incognito (Just (ACCL SCMContact ccLink)) -> withUserId userId $ \user -> connectViaContact user incognito ccLink
|
||||
APIConnect _ _ Nothing -> throwChatError CEInvalidConnReq
|
||||
Connect incognito aCReqUri@(Just cReqUri) -> withUser $ \user@User {userId} -> do
|
||||
plan <- connectPlan user cReqUri `catchChatError` const (pure $ CPInvitationLink ILPOk)
|
||||
unless (connectionPlanProceed plan) $ throwChatError (CEConnectionPlan plan)
|
||||
case plan of
|
||||
CPContactAddress (CAPContactViaAddress Contact {contactId}) ->
|
||||
processChatCommand $ APIConnectContactViaAddress userId incognito contactId
|
||||
_ -> processChatCommand $ APIConnect userId incognito aCReqUri
|
||||
Connect incognito (Just cLink@(ACL m cLink')) -> withUser $ \user -> do
|
||||
(ccLink, plan) <- connectPlan user cLink `catchChatError` \e -> case cLink' of CLFull cReq -> pure (ACCL m (CCLink cReq Nothing), CPInvitationLink ILPOk); _ -> throwError e
|
||||
connectWithPlan user incognito ccLink plan
|
||||
Connect _ Nothing -> throwChatError CEInvalidConnReq
|
||||
APIConnectContactViaAddress userId incognito contactId -> withUserId userId $ \user -> do
|
||||
ct@Contact {activeConn, profile = LocalProfile {contactLink}} <- withFastStore $ \db -> getContact db vr user contactId
|
||||
when (isJust activeConn) $ throwChatError (CECommandError "contact already has connection")
|
||||
case contactLink of
|
||||
Just cReq -> connectContactViaAddress user incognito ct cReq
|
||||
ccLink <- case contactLink of
|
||||
Just (CLFull cReq) -> pure $ CCLink cReq Nothing
|
||||
Just (CLShort sLnk) -> do
|
||||
cReq <- getShortLinkConnReq user sLnk
|
||||
pure $ CCLink cReq $ Just sLnk
|
||||
Nothing -> throwChatError (CECommandError "no address in contact profile")
|
||||
ConnectSimplex incognito -> withUser $ \user@User {userId} -> do
|
||||
let cReqUri = ACR SCMContact adminContactReq
|
||||
plan <- connectPlan user cReqUri `catchChatError` const (pure $ CPInvitationLink ILPOk)
|
||||
unless (connectionPlanProceed plan) $ throwChatError (CEConnectionPlan plan)
|
||||
case plan of
|
||||
CPContactAddress (CAPContactViaAddress Contact {contactId}) ->
|
||||
processChatCommand $ APIConnectContactViaAddress userId incognito contactId
|
||||
_ -> processChatCommand $ APIConnect userId incognito (Just cReqUri)
|
||||
connectContactViaAddress user incognito ct ccLink
|
||||
ConnectSimplex incognito -> withUser $ \user -> do
|
||||
plan <- contactRequestPlan user adminContactReq `catchChatError` const (pure $ CPContactAddress CAPOk)
|
||||
connectWithPlan user incognito (ACCL SCMContact (CCLink adminContactReq Nothing)) plan
|
||||
DeleteContact cName cdm -> withContactName cName $ \ctId -> APIDeleteChat (ChatRef CTDirect ctId) cdm
|
||||
ClearContact cName -> withContactName cName $ APIClearChat . ChatRef CTDirect
|
||||
APIListContacts userId -> withUserId userId $ \user ->
|
||||
CRContactsList user <$> withFastStore' (\db -> getUserContacts db vr user)
|
||||
ListContacts -> withUser $ \User {userId} ->
|
||||
processChatCommand $ APIListContacts userId
|
||||
APICreateMyAddress userId -> withUserId userId $ \user -> procCmd $ do
|
||||
APICreateMyAddress userId short -> withUserId userId $ \user -> procCmd $ do
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
(connId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMContact Nothing IKPQOn subMode
|
||||
withFastStore $ \db -> createUserContactLink db user connId cReq subMode
|
||||
pure $ CRUserContactLinkCreated user cReq
|
||||
CreateMyAddress -> withUser $ \User {userId} ->
|
||||
processChatCommand $ APICreateMyAddress userId
|
||||
let userData = shortLinkUserData short
|
||||
(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
|
||||
pure $ CRUserContactLinkCreated user ccLink'
|
||||
CreateMyAddress short -> withUser $ \User {userId} ->
|
||||
processChatCommand $ APICreateMyAddress userId short
|
||||
APIDeleteMyAddress userId -> withUserId userId $ \user@User {profile = p} -> do
|
||||
conns <- withFastStore $ \db -> getUserAddressConnections db vr user
|
||||
withChatLock "deleteMyAddress" $ do
|
||||
|
@ -1800,8 +1802,9 @@ processChatCommand' vr = \case
|
|||
let p' = (fromLocalProfile p :: Profile) {contactLink = Nothing}
|
||||
updateProfile_ user p' $ withFastStore' $ \db -> setUserProfileContactLink db user Nothing
|
||||
APISetProfileAddress userId True -> withUserId userId $ \user@User {profile = p} -> do
|
||||
ucl@UserContactLink {connReqContact} <- withFastStore (`getUserAddress` user)
|
||||
let p' = (fromLocalProfile p :: Profile) {contactLink = Just connReqContact}
|
||||
ucl@UserContactLink {connLinkContact = CCLink cReq _} <- withFastStore (`getUserAddress` user)
|
||||
-- TODO [short links] replace with short links
|
||||
let p' = (fromLocalProfile p :: Profile) {contactLink = Just $ CLFull cReq}
|
||||
updateProfile_ user p' $ withFastStore' $ \db -> setUserProfileContactLink db user $ Just ucl
|
||||
SetProfileAddress onOff -> withUser $ \User {userId} ->
|
||||
processChatCommand $ APISetProfileAddress userId onOff
|
||||
|
@ -1979,7 +1982,7 @@ processChatCommand' vr = \case
|
|||
Nothing -> do
|
||||
gVar <- asks random
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
(agentConnId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing IKPQOff subMode
|
||||
(agentConnId, CCLink cReq _) <- withAgent $ \a -> createConnection a (aUserId user) True 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
|
||||
|
@ -2276,16 +2279,18 @@ processChatCommand' vr = \case
|
|||
updateGroupProfileByName gName $ \p -> p {description}
|
||||
ShowGroupDescription gName -> withUser $ \user ->
|
||||
CRGroupDescription user <$> withFastStore (\db -> getGroupInfoByName db vr user gName)
|
||||
APICreateGroupLink groupId mRole -> withUser $ \user -> withGroupLock "createGroupLink" groupId $ do
|
||||
APICreateGroupLink groupId mRole short -> withUser $ \user -> withGroupLock "createGroupLink" groupId $ do
|
||||
gInfo <- withFastStore $ \db -> getGroupInfo db vr user groupId
|
||||
assertUserGroupRole gInfo GRAdmin
|
||||
when (mRole > GRMember) $ throwChatError $ CEGroupMemberInitialRole gInfo mRole
|
||||
groupLinkId <- GroupLinkId <$> drgRandomBytes 16
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
let crClientData = encodeJSON $ CRDataGroup groupLinkId
|
||||
(connId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMContact (Just crClientData) IKPQOff subMode
|
||||
withFastStore $ \db -> createGroupLink db user gInfo connId cReq groupLinkId mRole subMode
|
||||
pure $ CRGroupLinkCreated user gInfo cReq mRole
|
||||
userData = shortLinkUserData short
|
||||
(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
|
||||
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
|
||||
|
@ -2311,7 +2316,7 @@ processChatCommand' vr = \case
|
|||
when (isJust $ memberContactId m) $ throwChatError $ CECommandError "member contact already exists"
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
-- TODO PQ should negotitate contact connection with PQSupportOn?
|
||||
(connId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing IKPQOff subMode
|
||||
(connId, CCLink cReq _) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing Nothing IKPQOff subMode
|
||||
-- [incognito] reuse membership incognito profile
|
||||
ct <- withFastStore' $ \db -> createMemberContact db user connId cReq g m mConn subMode
|
||||
-- TODO not sure it is correct to set connections status here?
|
||||
|
@ -2332,9 +2337,9 @@ processChatCommand' vr = \case
|
|||
toView $ CRNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct') ci]
|
||||
pure $ CRNewMemberContactSentInv user ct' g m
|
||||
_ -> throwChatError CEGroupMemberNotActive
|
||||
CreateGroupLink gName mRole -> withUser $ \user -> do
|
||||
CreateGroupLink gName mRole short -> withUser $ \user -> do
|
||||
groupId <- withFastStore $ \db -> getGroupIdByName db user gName
|
||||
processChatCommand $ APICreateGroupLink groupId mRole
|
||||
processChatCommand $ APICreateGroupLink groupId mRole short
|
||||
GroupLinkMemberRole gName mRole -> withUser $ \user -> do
|
||||
groupId <- withFastStore $ \db -> getGroupIdByName db user gName
|
||||
processChatCommand $ APIGroupLinkMemberRole groupId mRole
|
||||
|
@ -2671,8 +2676,8 @@ processChatCommand' vr = \case
|
|||
CTGroup -> withFastStore $ \db -> getGroupChatItemIdByText' db user cId msg
|
||||
CTLocal -> withFastStore $ \db -> getLocalChatItemIdByText' db user cId msg
|
||||
_ -> throwChatError $ CECommandError "not supported"
|
||||
connectViaContact :: User -> IncognitoEnabled -> ConnectionRequestUri 'CMContact -> CM ChatResponse
|
||||
connectViaContact user@User {userId} incognito cReq@(CRContactUri ConnReqUriData {crClientData}) = withInvitationLock "connectViaContact" (strEncode cReq) $ do
|
||||
connectViaContact :: User -> IncognitoEnabled -> CreatedLinkContact -> CM ChatResponse
|
||||
connectViaContact user@User {userId} incognito (CCLink cReq@(CRContactUri ConnReqUriData {crClientData}) sLnk) = withInvitationLock "connectViaContact" (strEncode cReq) $ do
|
||||
let groupLinkId = crClientData >>= decodeJSON >>= \(CRDataGroup gli) -> Just gli
|
||||
cReqHash = ConnReqUriHash . C.sha256Hash $ strEncode cReq
|
||||
case groupLinkId of
|
||||
|
@ -2702,11 +2707,12 @@ processChatCommand' vr = \case
|
|||
-- [incognito] generate profile to send
|
||||
incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
conn@PendingContactConnection {pccConnId} <- withFastStore' $ \db -> createConnReqConnection db userId connId cReqHash xContactId incognitoProfile groupLinkId subMode chatV pqSup
|
||||
let sLnk' = serverShortLink <$> sLnk
|
||||
conn@PendingContactConnection {pccConnId} <- withFastStore' $ \db -> createConnReqConnection db userId connId cReqHash sLnk' xContactId incognitoProfile groupLinkId subMode chatV pqSup
|
||||
joinContact user pccConnId connId cReq incognitoProfile xContactId inGroup pqSup chatV
|
||||
pure $ CRSentInvitation user conn incognitoProfile
|
||||
connectContactViaAddress :: User -> IncognitoEnabled -> Contact -> ConnectionRequestUri 'CMContact -> CM ChatResponse
|
||||
connectContactViaAddress user incognito ct cReq =
|
||||
connectContactViaAddress :: User -> IncognitoEnabled -> Contact -> CreatedLinkContact -> CM ChatResponse
|
||||
connectContactViaAddress user incognito ct (CCLink cReq shortLink) =
|
||||
withInvitationLock "connectContactViaAddress" (strEncode cReq) $ do
|
||||
newXContactId <- XContactId <$> drgRandomBytes 16
|
||||
let pqSup = PQSupportOn
|
||||
|
@ -2715,10 +2721,10 @@ processChatCommand' vr = \case
|
|||
-- [incognito] generate profile to send
|
||||
incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
(pccConnId, ct') <- withFastStore $ \db -> createAddressContactConnection db vr user ct connId cReqHash newXContactId incognitoProfile subMode chatV pqSup
|
||||
(pccConnId, ct') <- withFastStore $ \db -> createAddressContactConnection db vr user ct connId cReqHash shortLink newXContactId incognitoProfile subMode chatV pqSup
|
||||
joinContact user pccConnId connId cReq incognitoProfile newXContactId False pqSup chatV
|
||||
pure $ CRSentInvitationToContact user ct' incognitoProfile
|
||||
prepareContact :: User -> ConnectionRequestUri 'CMContact -> PQSupport -> CM (ConnId, VersionChat)
|
||||
prepareContact :: User -> ConnReqContact -> PQSupport -> CM (ConnId, VersionChat)
|
||||
prepareContact user cReq pqSup = do
|
||||
-- 0) toggle disabled - PQSupportOff
|
||||
-- 1) toggle enabled, address supports PQ (connRequestPQSupport returns Just True) - PQSupportOn, enable support with compression
|
||||
|
@ -2729,7 +2735,7 @@ processChatCommand' vr = \case
|
|||
let chatV = agentToChatVersion agentV
|
||||
connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup
|
||||
pure (connId, chatV)
|
||||
joinContact :: User -> Int64 -> ConnId -> ConnectionRequestUri 'CMContact -> Maybe Profile -> XContactId -> Bool -> PQSupport -> VersionChat -> CM ()
|
||||
joinContact :: User -> Int64 -> ConnId -> ConnReqContact -> Maybe Profile -> XContactId -> Bool -> PQSupport -> VersionChat -> CM ()
|
||||
joinContact user pccConnId connId cReq incognitoProfile xContactId inGroup pqSup chatV = do
|
||||
let profileToSend = userProfileToSend user incognitoProfile Nothing inGroup
|
||||
dm <- encodeConnInfoPQ pqSup chatV (XContact profileToSend $ Just xContactId)
|
||||
|
@ -3034,32 +3040,77 @@ processChatCommand' vr = \case
|
|||
pure (gId, chatSettings)
|
||||
_ -> throwChatError $ CECommandError "not supported"
|
||||
processChatCommand $ APISetChatSettings (ChatRef cType chatId) $ updateSettings chatSettings
|
||||
connectPlan :: User -> AConnectionRequestUri -> CM ConnectionPlan
|
||||
connectPlan user (ACR SCMInvitation (CRInvitationUri crData e2e)) = do
|
||||
withFastStore' (\db -> getConnectionEntityByConnReq db vr user cReqSchemas) >>= \case
|
||||
Nothing -> pure $ CPInvitationLink ILPOk
|
||||
Just (RcvDirectMsgConnection Connection {connStatus = ConnPrepared} Nothing) ->
|
||||
pure $ CPInvitationLink ILPOk
|
||||
Just (RcvDirectMsgConnection conn ct_) -> do
|
||||
let Connection {connStatus, contactConnInitiated} = conn
|
||||
if
|
||||
| connStatus == ConnNew && contactConnInitiated ->
|
||||
pure $ CPInvitationLink ILPOwnLink
|
||||
| not (connReady conn) ->
|
||||
pure $ CPInvitationLink (ILPConnecting ct_)
|
||||
| otherwise -> case ct_ of
|
||||
Just ct -> pure $ CPInvitationLink (ILPKnown ct)
|
||||
Nothing -> throwChatError $ CEInternalError "ready RcvDirectMsgConnection connection should have associated contact"
|
||||
Just _ -> throwChatError $ CECommandError "found connection entity is not RcvDirectMsgConnection"
|
||||
connectPlan :: User -> AConnectionLink -> CM (ACreatedConnLink, ConnectionPlan)
|
||||
connectPlan user (ACL SCMInvitation cLink) = case cLink of
|
||||
CLFull cReq -> invitationReqAndPlan cReq Nothing
|
||||
CLShort l -> do
|
||||
let l' = serverShortLink l
|
||||
withFastStore' (\db -> getConnectionEntityViaShortLink db vr user l') >>= \case
|
||||
Just (cReq, ent) ->
|
||||
(ACCL SCMInvitation (CCLink cReq (Just l')),) <$> (invitationEntityPlan ent `catchChatError` (pure . CPError))
|
||||
Nothing -> getShortLinkConnReq user l' >>= (`invitationReqAndPlan` Just l')
|
||||
where
|
||||
cReqSchemas :: (ConnReqInvitation, ConnReqInvitation)
|
||||
cReqSchemas =
|
||||
invitationReqAndPlan cReq sLnk_ = do
|
||||
plan <- inviationRequestPlan user cReq `catchChatError` (pure . CPError)
|
||||
pure (ACCL SCMInvitation (CCLink cReq sLnk_), plan)
|
||||
connectPlan user (ACL SCMContact cLink) = case cLink of
|
||||
CLFull cReq -> contactReqAndPlan cReq Nothing
|
||||
CLShort l@(CSLContact _ ct _ _) -> do
|
||||
let l' = serverShortLink l
|
||||
case ct of
|
||||
CCTContact ->
|
||||
withFastStore' (\db -> getUserContactLinkViaShortLink db user l') >>= \case
|
||||
Just (UserContactLink (CCLink cReq _) _) -> pure (ACCL SCMContact $ CCLink cReq (Just l'), CPContactAddress CAPOwnLink)
|
||||
Nothing -> getShortLinkConnReq user l' >>= (`contactReqAndPlan` Just l')
|
||||
CCTGroup ->
|
||||
withFastStore' (\db -> getGroupInfoViaUserShortLink db vr user l') >>= \case
|
||||
Just (cReq, g) -> pure (ACCL SCMContact $ CCLink cReq (Just l'), CPGroupLink (GLPOwnLink g))
|
||||
Nothing -> getShortLinkConnReq user l' >>= (`contactReqAndPlan` Just l')
|
||||
CCTChannel -> throwChatError $ CECommandError "channel links are not supported in this version"
|
||||
where
|
||||
contactReqAndPlan cReq sLnk_ = do
|
||||
plan <- contactRequestPlan user cReq `catchChatError` (pure . CPError)
|
||||
pure (ACCL SCMContact $ CCLink cReq sLnk_, plan)
|
||||
connectWithPlan :: User -> IncognitoEnabled -> ACreatedConnLink -> ConnectionPlan -> CM ChatResponse
|
||||
connectWithPlan user@User {userId} incognito ccLink plan
|
||||
| connectionPlanProceed plan = do
|
||||
case plan of CPError e -> toView $ CRChatError (Just user) e; _ -> pure ()
|
||||
case plan of
|
||||
CPContactAddress (CAPContactViaAddress Contact {contactId}) ->
|
||||
processChatCommand $ APIConnectContactViaAddress userId incognito contactId
|
||||
_ -> processChatCommand $ APIConnect userId incognito (Just ccLink)
|
||||
| otherwise = pure $ CRConnectionPlan user ccLink plan
|
||||
inviationRequestPlan :: User -> ConnReqInvitation -> CM ConnectionPlan
|
||||
inviationRequestPlan user cReq = do
|
||||
withFastStore' (\db -> getConnectionEntityByConnReq db vr user $ cReqSchemas cReq) >>= \case
|
||||
Nothing -> pure $ CPInvitationLink ILPOk
|
||||
Just ent -> invitationEntityPlan ent
|
||||
where
|
||||
cReqSchemas :: ConnReqInvitation -> (ConnReqInvitation, ConnReqInvitation)
|
||||
cReqSchemas (CRInvitationUri crData e2e) =
|
||||
( CRInvitationUri crData {crScheme = SSSimplex} e2e,
|
||||
CRInvitationUri crData {crScheme = simplexChat} e2e
|
||||
)
|
||||
connectPlan user (ACR SCMContact (CRContactUri crData)) = do
|
||||
invitationEntityPlan :: ConnectionEntity -> CM ConnectionPlan
|
||||
invitationEntityPlan = \case
|
||||
RcvDirectMsgConnection Connection {connStatus = ConnPrepared} Nothing ->
|
||||
pure $ CPInvitationLink ILPOk
|
||||
RcvDirectMsgConnection conn ct_ -> do
|
||||
let Connection {connStatus, contactConnInitiated} = conn
|
||||
if
|
||||
| connStatus == ConnNew && contactConnInitiated ->
|
||||
pure $ CPInvitationLink ILPOwnLink
|
||||
| not (connReady conn) ->
|
||||
pure $ CPInvitationLink (ILPConnecting ct_)
|
||||
| otherwise -> case ct_ of
|
||||
Just ct -> pure $ CPInvitationLink (ILPKnown ct)
|
||||
Nothing -> throwChatError $ CEInternalError "ready RcvDirectMsgConnection connection should have associated contact"
|
||||
_ -> throwChatError $ CECommandError "found connection entity is not RcvDirectMsgConnection"
|
||||
contactRequestPlan :: User -> ConnReqContact -> CM ConnectionPlan
|
||||
contactRequestPlan user (CRContactUri crData) = do
|
||||
let ConnReqUriData {crClientData} = crData
|
||||
groupLinkId = crClientData >>= decodeJSON >>= \(CRDataGroup gli) -> Just gli
|
||||
cReqHashes = bimap hash hash cReqSchemas
|
||||
case groupLinkId of
|
||||
-- contact address
|
||||
Nothing ->
|
||||
|
@ -3105,9 +3156,31 @@ processChatCommand' vr = \case
|
|||
( CRContactUri crData {crScheme = SSSimplex},
|
||||
CRContactUri crData {crScheme = simplexChat}
|
||||
)
|
||||
cReqHashes :: (ConnReqUriHash, ConnReqUriHash)
|
||||
cReqHashes = bimap hash hash cReqSchemas
|
||||
hash :: ConnReqContact -> ConnReqUriHash
|
||||
hash = ConnReqUriHash . C.sha256Hash . strEncode
|
||||
getShortLinkConnReq :: User -> ConnShortLink m -> CM (ConnectionRequestUri m)
|
||||
getShortLinkConnReq User {userId} l = do
|
||||
l' <- restoreShortLink' l
|
||||
(cReq, cData) <- withAgent (\a -> getConnShortLink a userId l')
|
||||
case cData of
|
||||
ContactLinkData {direct} | not direct -> throwChatError CEUnsupportedConnReq
|
||||
_ -> pure ()
|
||||
pure cReq
|
||||
-- This function is needed, as UI uses simplex:/ schema in message view, so that the links can be handled without browser,
|
||||
-- and short links are stored with server hostname schema, so they wouldn't match without it.
|
||||
serverShortLink :: ConnShortLink m -> ConnShortLink m
|
||||
serverShortLink = \case
|
||||
CSLInvitation _ srv lnkId linkKey -> CSLInvitation SLSServer srv lnkId linkKey
|
||||
CSLContact _ ct srv linkKey -> CSLContact SLSServer ct srv linkKey
|
||||
restoreShortLink' l = (`restoreShortLink` l) <$> asks (shortLinkPresetServers . config)
|
||||
shortLinkUserData short = if short then Just "" else Nothing
|
||||
shortenCreatedLink :: CreatedConnLink m -> CM (CreatedConnLink m)
|
||||
shortenCreatedLink (CCLink cReq sLnk) = CCLink cReq <$> mapM (\l -> (`shortenShortLink` l) <$> asks (shortLinkPresetServers . config)) sLnk
|
||||
createdGroupLink :: CreatedLinkContact -> CreatedLinkContact
|
||||
createdGroupLink (CCLink cReq shortLink) = CCLink cReq (toGroupLink <$> shortLink)
|
||||
where
|
||||
toGroupLink :: ShortLinkContact -> ShortLinkContact
|
||||
toGroupLink (CSLContact sch _ srv k) = CSLContact sch CCTGroup srv k
|
||||
updateCIGroupInvitationStatus :: User -> GroupInfo -> CIGroupInvitationStatus -> CM ()
|
||||
updateCIGroupInvitationStatus user GroupInfo {groupId} newStatus = do
|
||||
AChatItem _ _ cInfo ChatItem {content, meta = CIMeta {itemId}} <- withFastStore $ \db -> getChatItemByGroupId db vr user groupId
|
||||
|
@ -3574,7 +3647,7 @@ subscribeUserConnections vr onlyNeeded agentBatchSubscribe user = do
|
|||
viaUserContactLink,
|
||||
groupLinkId,
|
||||
customUserProfileId,
|
||||
connReqInv = Nothing,
|
||||
connLinkInv = Nothing,
|
||||
localAlias,
|
||||
createdAt,
|
||||
updatedAt = createdAt
|
||||
|
@ -4042,11 +4115,11 @@ chatCommandP =
|
|||
"/set welcome " *> char_ '#' *> (UpdateGroupDescription <$> displayNameP <* A.space <*> (Just <$> msgTextP)),
|
||||
"/delete welcome " *> char_ '#' *> (UpdateGroupDescription <$> displayNameP <*> pure Nothing),
|
||||
"/show welcome " *> char_ '#' *> (ShowGroupDescription <$> displayNameP),
|
||||
"/_create link #" *> (APICreateGroupLink <$> A.decimal <*> (memberRole <|> pure GRMember)),
|
||||
"/_create link #" *> (APICreateGroupLink <$> A.decimal <*> (memberRole <|> pure GRMember) <*> shortOnOffP),
|
||||
"/_set link role #" *> (APIGroupLinkMemberRole <$> A.decimal <*> memberRole),
|
||||
"/_delete link #" *> (APIDeleteGroupLink <$> A.decimal),
|
||||
"/_get link #" *> (APIGetGroupLink <$> A.decimal),
|
||||
"/create link #" *> (CreateGroupLink <$> displayNameP <*> (memberRole <|> pure GRMember)),
|
||||
"/create link #" *> (CreateGroupLink <$> displayNameP <*> (memberRole <|> pure GRMember) <*> shortP),
|
||||
"/set link role #" *> (GroupLinkMemberRole <$> displayNameP <*> memberRole),
|
||||
"/delete link #" *> (DeleteGroupLink <$> displayNameP),
|
||||
"/show link #" *> (ShowGroupLink <$> displayNameP),
|
||||
|
@ -4057,12 +4130,12 @@ chatCommandP =
|
|||
"/_contacts " *> (APIListContacts <$> A.decimal),
|
||||
"/contacts" $> ListContacts,
|
||||
"/_connect plan " *> (APIConnectPlan <$> A.decimal <* A.space <*> strP),
|
||||
"/_connect " *> (APIConnect <$> A.decimal <*> incognitoOnOffP <* A.space <*> ((Just <$> strP) <|> A.takeByteString $> Nothing)),
|
||||
"/_connect " *> (APIAddContact <$> A.decimal <*> incognitoOnOffP),
|
||||
"/_connect " *> (APIAddContact <$> A.decimal <*> shortOnOffP <*> incognitoOnOffP),
|
||||
"/_connect " *> (APIConnect <$> A.decimal <*> incognitoOnOffP <* A.space <*> connLinkP),
|
||||
"/_set incognito :" *> (APISetConnectionIncognito <$> A.decimal <* A.space <*> onOffP),
|
||||
"/_set conn user :" *> (APIChangeConnectionUser <$> A.decimal <* A.space <*> A.decimal),
|
||||
("/connect" <|> "/c") *> (AddContact <$> shortP <*> incognitoP),
|
||||
("/connect" <|> "/c") *> (Connect <$> incognitoP <* A.space <*> ((Just <$> strP) <|> A.takeTill isSpace $> Nothing)),
|
||||
("/connect" <|> "/c") *> (AddContact <$> incognitoP),
|
||||
ForwardMessage <$> chatNameP <* " <- @" <*> displayNameP <* A.space <*> msgTextP,
|
||||
ForwardGroupMessage <$> chatNameP <* " <- #" <*> displayNameP <* A.space <* A.char '@' <*> (Just <$> displayNameP) <* A.space <*> msgTextP,
|
||||
ForwardGroupMessage <$> chatNameP <* " <- #" <*> displayNameP <*> pure Nothing <* A.space <*> msgTextP,
|
||||
|
@ -4096,8 +4169,8 @@ chatCommandP =
|
|||
("/fstatus " <|> "/fs ") *> (FileStatus <$> A.decimal),
|
||||
"/_connect contact " *> (APIConnectContactViaAddress <$> A.decimal <*> incognitoOnOffP <* A.space <*> A.decimal),
|
||||
"/simplex" *> (ConnectSimplex <$> incognitoP),
|
||||
"/_address " *> (APICreateMyAddress <$> A.decimal),
|
||||
("/address" <|> "/ad") $> CreateMyAddress,
|
||||
"/_address " *> (APICreateMyAddress <$> A.decimal <*> shortOnOffP),
|
||||
("/address" <|> "/ad") *> (CreateMyAddress <$> shortP),
|
||||
"/_delete_address " *> (APIDeleteMyAddress <$> A.decimal),
|
||||
("/delete_address" <|> "/da") $> DeleteMyAddress,
|
||||
"/_show_address " *> (APIShowMyAddress <$> A.decimal),
|
||||
|
@ -4167,7 +4240,12 @@ chatCommandP =
|
|||
]
|
||||
where
|
||||
choice = A.choice . map (\p -> p <* A.takeWhile (== ' ') <* A.endOfInput)
|
||||
connLinkP = do
|
||||
((Just <$> strP) <|> A.takeTill (== ' ') $> Nothing)
|
||||
>>= mapM (\(ACR m cReq) -> ACCL m . CCLink cReq <$> optional (A.space *> strP))
|
||||
shortP = (A.space *> ("short" <|> "s")) $> True <|> pure False
|
||||
incognitoP = (A.space *> ("incognito" <|> "i")) $> True <|> pure False
|
||||
shortOnOffP = (A.space *> "short=" *> onOffP) <|> pure False
|
||||
incognitoOnOffP = (A.space *> "incognito=" *> onOffP) <|> pure False
|
||||
imagePrefix = (<>) <$> "data:" <*> ("image/png;base64," <|> "image/jpg;base64,")
|
||||
imageP = safeDecodeUtf8 <$> ((<>) <$> imagePrefix <*> (B64.encode <$> base64P))
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1186,8 +1186,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
|||
CORGroup gInfo -> toView $ CRBusinessRequestAlreadyAccepted user gInfo
|
||||
CORRequest cReq -> do
|
||||
ucl <- withStore $ \db -> getUserContactLinkById db userId userContactLinkId
|
||||
let (UserContactLink {connReqContact, autoAccept}, gLinkInfo_) = ucl
|
||||
isSimplexTeam = sameConnReqContact connReqContact adminContactReq
|
||||
let (UserContactLink {connLinkContact = CCLink connReq _, autoAccept}, gLinkInfo_) = ucl
|
||||
isSimplexTeam = sameConnReqContact connReq adminContactReq
|
||||
v = maxVersion chatVRange
|
||||
case autoAccept of
|
||||
Just AutoAccept {acceptIncognito, businessAddress}
|
||||
|
|
|
@ -29,11 +29,10 @@ import Data.Text (Text)
|
|||
import qualified Data.Text as T
|
||||
import Data.Text.Encoding (encodeUtf8)
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Messaging.Agent.Protocol (AConnectionRequestUri (..), ConnReqUriData (..), ConnectionRequestUri (..), SMPQueue (..))
|
||||
import Simplex.Messaging.Agent.Protocol (AConnectionLink (..), ConnReqUriData (..), ConnShortLink (..), ConnectionLink (..), ConnectionRequestUri (..), ContactConnType (..), SMPQueue (..), simplexConnReqUri, simplexShortLink)
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fstToLower, sumTypeJSON)
|
||||
import Simplex.Messaging.Protocol (ProtocolServer (..))
|
||||
import Simplex.Messaging.ServiceScheme (ServiceScheme (..))
|
||||
import Simplex.Messaging.Util (decodeJSON, safeDecodeUtf8)
|
||||
import System.Console.ANSI.Types
|
||||
import qualified Text.Email.Validate as Email
|
||||
|
@ -49,7 +48,7 @@ data Format
|
|||
| Secret
|
||||
| Colored {color :: FormatColor}
|
||||
| Uri
|
||||
| SimplexLink {linkType :: SimplexLinkType, simplexUri :: AConnectionRequestUri, smpHosts :: NonEmpty Text}
|
||||
| SimplexLink {linkType :: SimplexLinkType, simplexUri :: AConnectionLink, smpHosts :: NonEmpty Text}
|
||||
| Mention {memberName :: Text}
|
||||
| Email
|
||||
| Phone
|
||||
|
@ -62,7 +61,7 @@ mentionedNames = mapMaybe (\(FormattedText f _) -> mentionedName =<< f)
|
|||
Mention name -> Just name
|
||||
_ -> Nothing
|
||||
|
||||
data SimplexLinkType = XLContact | XLInvitation | XLGroup
|
||||
data SimplexLinkType = XLContact | XLInvitation | XLGroup | XLChannel
|
||||
deriving (Eq, Show)
|
||||
|
||||
colored :: Color -> Format
|
||||
|
@ -248,24 +247,34 @@ markdownP = mconcat <$> A.many' fragmentP
|
|||
')' -> False
|
||||
c -> isPunctuation c
|
||||
uriMarkdown s = case strDecode $ encodeUtf8 s of
|
||||
Right cReq -> markdown (simplexUriFormat cReq) s
|
||||
Right cLink -> markdown (simplexUriFormat cLink) s
|
||||
_ -> markdown Uri s
|
||||
isUri s = T.length s >= 10 && any (`T.isPrefixOf` s) ["http://", "https://", "simplex:/"]
|
||||
isEmail s = T.any (== '@') s && Email.isValid (encodeUtf8 s)
|
||||
noFormat = pure . unmarked
|
||||
simplexUriFormat :: AConnectionRequestUri -> Format
|
||||
simplexUriFormat :: AConnectionLink -> Format
|
||||
simplexUriFormat = \case
|
||||
ACR m (CRContactUri crData) ->
|
||||
let cReq = ACR m $ CRContactUri crData {crScheme = SSSimplex}
|
||||
in SimplexLink (linkType' crData) cReq $ uriHosts crData
|
||||
ACR m (CRInvitationUri crData e2e) ->
|
||||
let cReq = ACR m $ CRInvitationUri crData {crScheme = SSSimplex} e2e
|
||||
in SimplexLink XLInvitation cReq $ uriHosts crData
|
||||
where
|
||||
uriHosts ConnReqUriData {crSmpQueues} = L.map (safeDecodeUtf8 . strEncode) $ sconcat $ L.map (host . qServer) crSmpQueues
|
||||
linkType' ConnReqUriData {crClientData} = case crClientData >>= decodeJSON of
|
||||
Just (CRDataGroup _) -> XLGroup
|
||||
Nothing -> XLContact
|
||||
ACL m (CLFull cReq) -> case cReq of
|
||||
CRContactUri crData -> SimplexLink (linkType' crData) cLink $ uriHosts crData
|
||||
CRInvitationUri crData _ -> SimplexLink XLInvitation cLink $ uriHosts crData
|
||||
where
|
||||
cLink = ACL m $ CLFull $ simplexConnReqUri cReq
|
||||
uriHosts ConnReqUriData {crSmpQueues} = L.map strEncodeText $ sconcat $ L.map (host . qServer) crSmpQueues
|
||||
linkType' ConnReqUriData {crClientData} = case crClientData >>= decodeJSON of
|
||||
Just (CRDataGroup _) -> XLGroup
|
||||
Nothing -> XLContact
|
||||
ACL m (CLShort sLnk) -> case sLnk of
|
||||
CSLContact _ ct srv _ -> SimplexLink (linkType' ct) cLink $ uriHosts srv
|
||||
CSLInvitation _ srv _ _ -> SimplexLink XLInvitation cLink $ uriHosts srv
|
||||
where
|
||||
cLink = ACL m $ CLShort $ simplexShortLink sLnk
|
||||
uriHosts srv = L.map strEncodeText $ host srv
|
||||
linkType' = \case
|
||||
CCTGroup -> XLGroup
|
||||
CCTChannel -> XLChannel
|
||||
CCTContact -> XLContact
|
||||
strEncodeText :: StrEncoding a => a -> Text
|
||||
strEncodeText = safeDecodeUtf8 . strEncode
|
||||
|
||||
markdownText :: FormattedText -> Text
|
||||
markdownText (FormattedText f_ t) = case f_ of
|
||||
|
|
|
@ -275,6 +275,10 @@ data UserServer' s (p :: ProtocolType) = UserServer
|
|||
}
|
||||
deriving (Show)
|
||||
|
||||
presetServerAddress :: UserServer' s p -> ProtocolServer p
|
||||
presetServerAddress UserServer {server = ProtoServerWithAuth srv _} = srv
|
||||
{-# INLINE presetServerAddress #-}
|
||||
|
||||
data PresetOperator = PresetOperator
|
||||
{ operator :: Maybe NewServerOperator,
|
||||
smp :: [NewUserServer 'PSMP],
|
||||
|
@ -297,6 +301,9 @@ operatorServersToUse p PresetOperator {useSMP, useXFTP} = case p of
|
|||
SPSMP -> useSMP
|
||||
SPXFTP -> useXFTP
|
||||
|
||||
presetServer' :: Bool -> ProtocolServer p -> NewUserServer p
|
||||
presetServer' enabled = presetServer enabled . (`ProtoServerWithAuth` Nothing)
|
||||
|
||||
presetServer :: Bool -> ProtoServerWithAuth p -> NewUserServer p
|
||||
presetServer = newUserServer_ True
|
||||
|
||||
|
|
117
src/Simplex/Chat/Operators/Presets.hs
Normal file
117
src/Simplex/Chat/Operators/Presets.hs
Normal file
|
@ -0,0 +1,117 @@
|
|||
{-# LANGUAGE DataKinds #-}
|
||||
{-# LANGUAGE DuplicateRecordFields #-}
|
||||
{-# LANGUAGE OverloadedLists #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module Simplex.Chat.Operators.Presets where
|
||||
|
||||
import Data.List.NonEmpty (NonEmpty)
|
||||
import qualified Data.List.NonEmpty as L
|
||||
import Simplex.Chat.Operators
|
||||
import Simplex.Messaging.Agent.Env.SQLite (ServerRoles (..), allRoles)
|
||||
import Simplex.Messaging.Protocol (ProtocolType (..), SMPServer)
|
||||
|
||||
operatorSimpleXChat :: NewServerOperator
|
||||
operatorSimpleXChat =
|
||||
ServerOperator
|
||||
{ operatorId = DBNewEntity,
|
||||
operatorTag = Just OTSimplex,
|
||||
tradeName = "SimpleX Chat",
|
||||
legalName = Just "SimpleX Chat Ltd",
|
||||
serverDomains = ["simplex.im"],
|
||||
conditionsAcceptance = CARequired Nothing,
|
||||
enabled = True,
|
||||
smpRoles = allRoles,
|
||||
xftpRoles = allRoles
|
||||
}
|
||||
|
||||
operatorFlux :: NewServerOperator
|
||||
operatorFlux =
|
||||
ServerOperator
|
||||
{ operatorId = DBNewEntity,
|
||||
operatorTag = Just OTFlux,
|
||||
tradeName = "Flux",
|
||||
legalName = Just "InFlux Technologies Limited",
|
||||
serverDomains = ["simplexonflux.com"],
|
||||
conditionsAcceptance = CARequired Nothing,
|
||||
enabled = False,
|
||||
smpRoles = ServerRoles {storage = False, proxy = True},
|
||||
xftpRoles = ServerRoles {storage = False, proxy = True}
|
||||
}
|
||||
|
||||
-- Please note: if any servers are removed from the lists below, they MUST be added here.
|
||||
-- Otherwise previously created short links won't work.
|
||||
--
|
||||
-- !!! Also, if any servers need to be added, shortLinkPresetServers will need to be be split to two,
|
||||
-- so that option used for restoring links is updated earlier, for backward/forward compatibility.
|
||||
allPresetServers :: NonEmpty SMPServer
|
||||
allPresetServers = enabledSimplexChatSMPServers <> disabledSimplexChatSMPServers <> fluxSMPServers_
|
||||
-- TODO [short links] remove, added for testing
|
||||
<> ["smp://8Af90NX2TTkKEJAF1RCg69P_Odg2Z-6_J6DOKUqK3rQ=@smp7.simplex.im,dbxqutskmmbkbrs7ofi7pmopeyhgi5cxbjbh4ummgmep4r6bz4cbrcid.onion"]
|
||||
|
||||
simplexChatSMPServers :: [NewUserServer 'PSMP]
|
||||
simplexChatSMPServers =
|
||||
map (presetServer' True) (L.toList enabledSimplexChatSMPServers)
|
||||
<> map (presetServer' False) (L.toList disabledSimplexChatSMPServers)
|
||||
|
||||
-- Please note: if any servers are removed from this list, they MUST be added to allPresetServers.
|
||||
-- Otherwise previously created short links won't work.
|
||||
--
|
||||
-- !!! Also, if any servers need to be added, shortLinkPresetServers will need to be be split to two,
|
||||
-- so that option used for restoring links is updated earlier, for backward/forward compatibility.
|
||||
enabledSimplexChatSMPServers :: NonEmpty SMPServer
|
||||
enabledSimplexChatSMPServers =
|
||||
[ "smp://0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU=@smp8.simplex.im,beccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion",
|
||||
"smp://SkIkI6EPd2D63F4xFKfHk7I1UGZVNn6k1QWZ5rcyr6w=@smp9.simplex.im,jssqzccmrcws6bhmn77vgmhfjmhwlyr3u7puw4erkyoosywgl67slqqd.onion",
|
||||
"smp://6iIcWT_dF2zN_w5xzZEY7HI2Prbh3ldP07YTyDexPjE=@smp10.simplex.im,rb2pbttocvnbrngnwziclp2f4ckjq65kebafws6g4hy22cdaiv5dwjqd.onion",
|
||||
"smp://1OwYGt-yqOfe2IyVHhxz3ohqo3aCCMjtB-8wn4X_aoY=@smp11.simplex.im,6ioorbm6i3yxmuoezrhjk6f6qgkc4syabh7m3so74xunb5nzr4pwgfqd.onion",
|
||||
"smp://UkMFNAXLXeAAe0beCa4w6X_zp18PwxSaSjY17BKUGXQ=@smp12.simplex.im,ie42b5weq7zdkghocs3mgxdjeuycheeqqmksntj57rmejagmg4eor5yd.onion",
|
||||
"smp://enEkec4hlR3UtKx2NMpOUK_K4ZuDxjWBO1d9Y4YXVaA=@smp14.simplex.im,aspkyu2sopsnizbyfabtsicikr2s4r3ti35jogbcekhm3fsoeyjvgrid.onion",
|
||||
"smp://h--vW7ZSkXPeOUpfxlFGgauQmXNFOzGoizak7Ult7cw=@smp15.simplex.im,oauu4bgijybyhczbnxtlggo6hiubahmeutaqineuyy23aojpih3dajad.onion",
|
||||
"smp://hejn2gVIqNU6xjtGM3OwQeuk8ZEbDXVJXAlnSBJBWUA=@smp16.simplex.im,p3ktngodzi6qrf7w64mmde3syuzrv57y55hxabqcq3l5p6oi7yzze6qd.onion",
|
||||
"smp://ZKe4uxF4Z_aLJJOEsC-Y6hSkXgQS5-oc442JQGkyP8M=@smp17.simplex.im,ogtwfxyi3h2h5weftjjpjmxclhb5ugufa5rcyrmg7j4xlch7qsr5nuqd.onion",
|
||||
"smp://PtsqghzQKU83kYTlQ1VKg996dW4Cw4x_bvpKmiv8uns=@smp18.simplex.im,lyqpnwbs2zqfr45jqkncwpywpbtq7jrhxnib5qddtr6npjyezuwd3nqd.onion",
|
||||
"smp://N_McQS3F9TGoh4ER0QstUf55kGnNSd-wXfNPZ7HukcM=@smp19.simplex.im,i53bbtoqhlc365k6kxzwdp5w3cdt433s7bwh3y32rcbml2vztiyyz5id.onion"
|
||||
]
|
||||
|
||||
-- Please note: if any servers are removed from this list, they MUST be added to allPresetServers.
|
||||
-- Otherwise previously created short links won't work.
|
||||
--
|
||||
-- !!! Also, if any servers need to be added, shortLinkPresetServers will need to be be split to two,
|
||||
-- so that option used for restoring links is updated earlier, for backward/forward compatibility.
|
||||
disabledSimplexChatSMPServers :: NonEmpty SMPServer
|
||||
disabledSimplexChatSMPServers =
|
||||
[ "smp://u2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU=@smp4.simplex.im,o5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion",
|
||||
"smp://hpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg=@smp5.simplex.im,jjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion",
|
||||
"smp://PQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo=@smp6.simplex.im,bylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion"
|
||||
]
|
||||
|
||||
fluxSMPServers :: [NewUserServer 'PSMP]
|
||||
fluxSMPServers = map (presetServer' True) $ L.toList fluxSMPServers_
|
||||
|
||||
-- Please note: if any servers are removed from this list, they MUST be added to allPresetServers.
|
||||
-- Otherwise previously created short links won't work.
|
||||
--
|
||||
-- !!! Also, if any servers need to be added, shortLinkPresetServers will need to be be split to two,
|
||||
-- so that option used for restoring links is updated earlier, for backward/forward compatibility.
|
||||
fluxSMPServers_ :: NonEmpty SMPServer
|
||||
fluxSMPServers_ =
|
||||
[ "smp://xQW_ufMkGE20UrTlBl8QqceG1tbuylXhr9VOLPyRJmw=@smp1.simplexonflux.com,qb4yoanyl4p7o33yrknv4rs6qo7ugeb2tu2zo66sbebezs4cpyosarid.onion",
|
||||
"smp://LDnWZVlAUInmjmdpQQoIo6FUinRXGe0q3zi5okXDE4s=@smp2.simplexonflux.com,yiqtuh3q4x7hgovkomafsod52wvfjucdljqbbipg5sdssnklgongxbqd.onion",
|
||||
"smp://1jne379u7IDJSxAvXbWb_JgoE7iabcslX0LBF22Rej0=@smp3.simplexonflux.com,a5lm4k7ufei66cdck6fy63r4lmkqy3dekmmb7jkfdm5ivi6kfaojshad.onion",
|
||||
"smp://xmAmqj75I9mWrUihLUlI0ZuNLXlIwFIlHRq5Pb6cHAU=@smp4.simplexonflux.com,qpcz2axyy66u26hfdd2e23uohcf3y6c36mn7dcuilcgnwjasnrvnxjqd.onion",
|
||||
"smp://rWvBYyTamuRCBYb_KAn-nsejg879ndhiTg5Sq3k0xWA=@smp5.simplexonflux.com,4ao347qwiuluyd45xunmii4skjigzuuox53hpdsgbwxqafd4yrticead.onion",
|
||||
"smp://PN7-uqLBToqlf1NxHEaiL35lV2vBpXq8Nj8BW11bU48=@smp6.simplexonflux.com,hury6ot3ymebbr2535mlp7gcxzrjpc6oujhtfxcfh2m4fal4xw5fq6qd.onion"
|
||||
]
|
||||
|
||||
fluxXFTPServers :: [NewUserServer 'PXFTP]
|
||||
fluxXFTPServers =
|
||||
map
|
||||
(presetServer True)
|
||||
[ "xftp://92Sctlc09vHl_nAqF2min88zKyjdYJ9mgxRCJns5K2U=@xftp1.simplexonflux.com,apl3pumq3emwqtrztykyyoomdx4dg6ysql5zek2bi3rgznz7ai3odkid.onion",
|
||||
"xftp://YBXy4f5zU1CEhnbbCzVWTNVNsaETcAGmYqGNxHntiE8=@xftp2.simplexonflux.com,c5jjecisncnngysah3cz2mppediutfelco4asx65mi75d44njvua3xid.onion",
|
||||
"xftp://ARQO74ZSvv2OrulRF3CdgwPz_AMy27r0phtLSq5b664=@xftp3.simplexonflux.com,dc4mohiubvbnsdfqqn7xhlhpqs5u4tjzp7xpz6v6corwvzvqjtaqqiqd.onion",
|
||||
"xftp://ub2jmAa9U0uQCy90O-fSUNaYCj6sdhl49Jh3VpNXP58=@xftp4.simplexonflux.com,4qq5pzier3i4yhpuhcrhfbl6j25udc4czoyascrj4yswhodhfwev3nyd.onion",
|
||||
"xftp://Rh19D5e4Eez37DEE9hAlXDB3gZa1BdFYJTPgJWPO9OI=@xftp5.simplexonflux.com,q7itltdn32hjmgcqwhow4tay5ijetng3ur32bolssw32fvc5jrwvozad.onion",
|
||||
"xftp://0AznwoyfX8Od9T_acp1QeeKtxUi676IBIiQjXVwbdyU=@xftp6.simplexonflux.com,upvzf23ou6nrmaf3qgnhd6cn3d74tvivlmz3p7wdfwq6fhthjrjiiqid.onion"
|
||||
]
|
|
@ -1,4 +1,5 @@
|
|||
{-# LANGUAGE CPP #-}
|
||||
{-# LANGUAGE DataKinds #-}
|
||||
{-# LANGUAGE DuplicateRecordFields #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
|
@ -12,6 +13,7 @@ module Simplex.Chat.Store.Connections
|
|||
( getChatLockEntity,
|
||||
getConnectionEntity,
|
||||
getConnectionEntityByConnReq,
|
||||
getConnectionEntityViaShortLink,
|
||||
getContactConnEntityByConnReqHash,
|
||||
getConnectionsToSubscribe,
|
||||
unsetConnectionToSubscribe,
|
||||
|
@ -33,7 +35,7 @@ import Simplex.Chat.Store.Groups
|
|||
import Simplex.Chat.Store.Profiles
|
||||
import Simplex.Chat.Store.Shared
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Messaging.Agent.Protocol (ConnId)
|
||||
import Simplex.Messaging.Agent.Protocol (ConnId, ConnShortLink, ConnectionMode (..))
|
||||
import Simplex.Messaging.Agent.Store.AgentStore (firstRow, firstRow', maybeFirstRow)
|
||||
import Simplex.Messaging.Agent.Store.DB (BoolInt (..))
|
||||
import qualified Simplex.Messaging.Agent.Store.DB as DB
|
||||
|
@ -204,6 +206,26 @@ getConnectionEntityByConnReq db vr user@User {userId} (cReqSchema1, cReqSchema2)
|
|||
DB.query db "SELECT agent_conn_id FROM connections WHERE user_id = ? AND conn_req_inv IN (?,?) LIMIT 1" (userId, cReqSchema1, cReqSchema2)
|
||||
maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getConnectionEntity db vr user) connId_
|
||||
|
||||
getConnectionEntityViaShortLink :: DB.Connection -> VersionRangeChat -> User -> ConnShortLink 'CMInvitation -> IO (Maybe (ConnReqInvitation, ConnectionEntity))
|
||||
getConnectionEntityViaShortLink db vr user@User {userId} shortLink = fmap eitherToMaybe $ runExceptT $ do
|
||||
(cReq, connId) <- ExceptT getConnReqConnId
|
||||
(cReq,) <$> getConnectionEntity db vr user connId
|
||||
where
|
||||
getConnReqConnId =
|
||||
firstRow' toConnReqConnId (SEInternalError "connection not found") $
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT conn_req_inv, agent_conn_id
|
||||
FROM connections
|
||||
WHERE user_id = ? AND short_link_inv = ? LIMIT 1
|
||||
|]
|
||||
(userId, shortLink)
|
||||
-- cReq is Maybe - it is removed when connection is established
|
||||
toConnReqConnId = \case
|
||||
(Just cReq, connId) -> Right (cReq, connId)
|
||||
_ -> Left $ SEInternalError "no connection request"
|
||||
|
||||
-- search connection for connection plan:
|
||||
-- multiple connections can have same via_contact_uri_hash if request was repeated;
|
||||
-- this function searches for latest connection with contact so that "known contact" plan would be chosen;
|
||||
|
|
|
@ -100,7 +100,7 @@ import Simplex.Chat.Store.Shared
|
|||
import Simplex.Chat.Types
|
||||
import Simplex.Chat.Types.Preferences
|
||||
import Simplex.Chat.Types.UITheme
|
||||
import Simplex.Messaging.Agent.Protocol (ConnId, InvitationId, UserId)
|
||||
import Simplex.Messaging.Agent.Protocol (ConnId, CreatedConnLink (..), InvitationId, UserId)
|
||||
import Simplex.Messaging.Agent.Store.AgentStore (firstRow, maybeFirstRow)
|
||||
import Simplex.Messaging.Agent.Store.DB (Binary (..), BoolInt (..))
|
||||
import qualified Simplex.Messaging.Agent.Store.DB as DB
|
||||
|
@ -122,7 +122,7 @@ getPendingContactConnection db userId connId = do
|
|||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at
|
||||
SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, custom_user_profile_id, conn_req_inv, short_link_inv, local_alias, created_at, updated_at
|
||||
FROM connections
|
||||
WHERE user_id = ?
|
||||
AND connection_id = ?
|
||||
|
@ -148,14 +148,14 @@ deletePendingContactConnection db userId connId =
|
|||
|]
|
||||
(userId, connId, ConnContact)
|
||||
|
||||
createAddressContactConnection :: DB.Connection -> VersionRangeChat -> User -> Contact -> ConnId -> ConnReqUriHash -> XContactId -> Maybe Profile -> SubscriptionMode -> VersionChat -> PQSupport -> ExceptT StoreError IO (Int64, Contact)
|
||||
createAddressContactConnection db vr user@User {userId} Contact {contactId} acId cReqHash xContactId incognitoProfile subMode chatV pqSup = do
|
||||
PendingContactConnection {pccConnId} <- liftIO $ createConnReqConnection db userId acId cReqHash xContactId incognitoProfile Nothing subMode chatV pqSup
|
||||
createAddressContactConnection :: DB.Connection -> VersionRangeChat -> User -> Contact -> ConnId -> ConnReqUriHash -> Maybe ShortLinkContact -> XContactId -> Maybe Profile -> SubscriptionMode -> VersionChat -> PQSupport -> ExceptT StoreError IO (Int64, Contact)
|
||||
createAddressContactConnection db vr user@User {userId} Contact {contactId} acId cReqHash sLnk xContactId incognitoProfile subMode chatV pqSup = do
|
||||
PendingContactConnection {pccConnId} <- liftIO $ createConnReqConnection db userId acId cReqHash sLnk xContactId incognitoProfile Nothing subMode chatV pqSup
|
||||
liftIO $ DB.execute db "UPDATE connections SET contact_id = ? WHERE connection_id = ?" (contactId, pccConnId)
|
||||
(pccConnId,) <$> getContact db vr user contactId
|
||||
|
||||
createConnReqConnection :: DB.Connection -> UserId -> ConnId -> ConnReqUriHash -> XContactId -> Maybe Profile -> Maybe GroupLinkId -> SubscriptionMode -> VersionChat -> PQSupport -> IO PendingContactConnection
|
||||
createConnReqConnection db userId acId cReqHash xContactId incognitoProfile groupLinkId subMode chatV pqSup = do
|
||||
createConnReqConnection :: DB.Connection -> UserId -> ConnId -> ConnReqUriHash -> Maybe ShortLinkContact -> XContactId -> Maybe Profile -> Maybe GroupLinkId -> SubscriptionMode -> VersionChat -> PQSupport -> IO PendingContactConnection
|
||||
createConnReqConnection db userId acId cReqHash sLnk xContactId incognitoProfile groupLinkId subMode chatV pqSup = do
|
||||
createdAt <- getCurrentTime
|
||||
customUserProfileId <- mapM (createIncognitoProfile_ db userId createdAt) incognitoProfile
|
||||
let pccConnStatus = ConnJoined
|
||||
|
@ -164,16 +164,16 @@ createConnReqConnection db userId acId cReqHash xContactId incognitoProfile grou
|
|||
[sql|
|
||||
INSERT INTO connections (
|
||||
user_id, agent_conn_id, conn_status, conn_type, contact_conn_initiated,
|
||||
via_contact_uri_hash, xcontact_id, custom_user_profile_id, via_group_link, group_link_id,
|
||||
via_contact_uri_hash, via_short_link_contact, xcontact_id, custom_user_profile_id, via_group_link, group_link_id,
|
||||
created_at, updated_at, to_subscribe, conn_chat_version, pq_support, pq_encryption
|
||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
|]
|
||||
( (userId, acId, pccConnStatus, ConnContact, BI True, cReqHash, xContactId)
|
||||
( (userId, acId, pccConnStatus, ConnContact, BI True, cReqHash, sLnk, xContactId)
|
||||
:. (customUserProfileId, BI (isJust groupLinkId), groupLinkId)
|
||||
:. (createdAt, createdAt, BI (subMode == SMOnlyCreate), chatV, pqSup, pqSup)
|
||||
)
|
||||
pccConnId <- insertedRowId db
|
||||
pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = True, viaUserContactLink = Nothing, groupLinkId, customUserProfileId, connReqInv = Nothing, localAlias = "", createdAt, updatedAt = createdAt}
|
||||
pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = True, viaUserContactLink = Nothing, groupLinkId, customUserProfileId, connLinkInv = Nothing, localAlias = "", createdAt, updatedAt = createdAt}
|
||||
|
||||
getConnReqContactXContactId :: DB.Connection -> VersionRangeChat -> User -> ConnReqUriHash -> IO (Maybe Contact, Maybe XContactId)
|
||||
getConnReqContactXContactId db vr user@User {userId} cReqHash = do
|
||||
|
@ -214,8 +214,8 @@ getContactByConnReqHash db vr user@User {userId} cReqHash = do
|
|||
(userId, cReqHash, CSActive)
|
||||
mapM (addDirectChatTags db) ct_
|
||||
|
||||
createDirectConnection :: DB.Connection -> User -> ConnId -> ConnReqInvitation -> ConnStatus -> Maybe Profile -> SubscriptionMode -> VersionChat -> PQSupport -> IO PendingContactConnection
|
||||
createDirectConnection db User {userId} acId cReq pccConnStatus incognitoProfile subMode chatV pqSup = do
|
||||
createDirectConnection :: DB.Connection -> User -> ConnId -> CreatedLinkInvitation -> ConnStatus -> Maybe Profile -> SubscriptionMode -> VersionChat -> PQSupport -> IO PendingContactConnection
|
||||
createDirectConnection db User {userId} acId ccLink@(CCLink cReq shortLinkInv) pccConnStatus incognitoProfile subMode chatV pqSup = do
|
||||
createdAt <- getCurrentTime
|
||||
customUserProfileId <- mapM (createIncognitoProfile_ db userId createdAt) incognitoProfile
|
||||
let contactConnInitiated = pccConnStatus == ConnNew
|
||||
|
@ -223,15 +223,15 @@ createDirectConnection db User {userId} acId cReq pccConnStatus incognitoProfile
|
|||
db
|
||||
[sql|
|
||||
INSERT INTO connections
|
||||
(user_id, agent_conn_id, conn_req_inv, conn_status, conn_type, contact_conn_initiated, custom_user_profile_id,
|
||||
(user_id, agent_conn_id, conn_req_inv, short_link_inv, conn_status, conn_type, contact_conn_initiated, custom_user_profile_id,
|
||||
created_at, updated_at, to_subscribe, conn_chat_version, pq_support, pq_encryption)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
|]
|
||||
( (userId, acId, cReq, pccConnStatus, ConnContact, BI contactConnInitiated, customUserProfileId)
|
||||
( (userId, acId, cReq, shortLinkInv, pccConnStatus, ConnContact, BI contactConnInitiated, customUserProfileId)
|
||||
:. (createdAt, createdAt, BI (subMode == SMOnlyCreate), chatV, pqSup, pqSup)
|
||||
)
|
||||
pccConnId <- insertedRowId db
|
||||
pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = False, viaUserContactLink = Nothing, groupLinkId = Nothing, customUserProfileId, connReqInv = Just cReq, localAlias = "", createdAt, updatedAt = createdAt}
|
||||
pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = False, viaUserContactLink = Nothing, groupLinkId = Nothing, customUserProfileId, connLinkInv = Just ccLink, localAlias = "", createdAt, updatedAt = createdAt}
|
||||
|
||||
createIncognitoProfile :: DB.Connection -> User -> Profile -> IO Int64
|
||||
createIncognitoProfile db User {userId} p = do
|
||||
|
@ -904,7 +904,7 @@ getPendingContactConnections db User {userId} = do
|
|||
<$> DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at
|
||||
SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, custom_user_profile_id, conn_req_inv, short_link_inv, local_alias, created_at, updated_at
|
||||
FROM connections
|
||||
WHERE user_id = ?
|
||||
AND conn_type = ?
|
||||
|
@ -989,7 +989,7 @@ updateConnectionStatus_ :: DB.Connection -> Int64 -> ConnStatus -> IO ()
|
|||
updateConnectionStatus_ db connId connStatus = do
|
||||
currentTs <- getCurrentTime
|
||||
if connStatus == ConnReady
|
||||
then DB.execute db "UPDATE connections SET conn_status = ?, updated_at = ?, conn_req_inv = NULL WHERE connection_id = ?" (connStatus, currentTs, connId)
|
||||
then DB.execute db "UPDATE connections SET conn_status = ?, updated_at = ?, conn_req_inv = NULL, short_link_inv = NULL WHERE connection_id = ?" (connStatus, currentTs, connId)
|
||||
else DB.execute db "UPDATE connections SET conn_status = ?, updated_at = ? WHERE connection_id = ?" (connStatus, currentTs, connId)
|
||||
|
||||
updateContactSettings :: DB.Connection -> User -> Int64 -> ChatSettings -> IO ()
|
||||
|
|
|
@ -39,6 +39,7 @@ module Simplex.Chat.Store.Groups
|
|||
getGroup,
|
||||
getGroupInfo,
|
||||
getGroupInfoByUserContactLinkConnReq,
|
||||
getGroupInfoViaUserShortLink,
|
||||
getGroupInfoByGroupLinkHash,
|
||||
updateGroupProfile,
|
||||
updateGroupPreferences,
|
||||
|
@ -157,14 +158,14 @@ import Simplex.Chat.Types
|
|||
import Simplex.Chat.Types.Preferences
|
||||
import Simplex.Chat.Types.Shared
|
||||
import Simplex.Chat.Types.UITheme
|
||||
import Simplex.Messaging.Agent.Protocol (ConnId, UserId)
|
||||
import Simplex.Messaging.Agent.Protocol (ConnId, CreatedConnLink (..), UserId)
|
||||
import Simplex.Messaging.Agent.Store.AgentStore (firstRow, fromOnlyBI, maybeFirstRow)
|
||||
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.Protocol (SubscriptionMode (..))
|
||||
import Simplex.Messaging.Util (eitherToMaybe, ($>>=), (<$$>))
|
||||
import Simplex.Messaging.Util (eitherToMaybe, firstRow', ($>>=), (<$$>))
|
||||
import Simplex.Messaging.Version
|
||||
import UnliftIO.STM
|
||||
#if defined(dbPostgres)
|
||||
|
@ -175,21 +176,21 @@ import Database.SQLite.Simple (Only (..), Query, (:.) (..))
|
|||
import Database.SQLite.Simple.QQ (sql)
|
||||
#endif
|
||||
|
||||
type MaybeGroupMemberRow = (Maybe Int64, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe ConnReqContact, Maybe LocalAlias, Maybe Preferences) :. (Maybe UTCTime, Maybe UTCTime)
|
||||
type MaybeGroupMemberRow = (Maybe Int64, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe LocalAlias, Maybe Preferences) :. (Maybe UTCTime, Maybe UTCTime)
|
||||
|
||||
toMaybeGroupMember :: Int64 -> MaybeGroupMemberRow -> Maybe GroupMember
|
||||
toMaybeGroupMember userContactId ((Just groupMemberId, Just groupId, Just memberId, Just minVer, Just maxVer, Just memberRole, Just memberCategory, Just memberStatus, Just showMessages, memberBlocked) :. (invitedById, invitedByGroupMemberId, Just localDisplayName, memberContactId, Just memberContactProfileId, Just profileId, Just displayName, Just fullName, image, contactLink, Just localAlias, contactPreferences) :. (Just createdAt, Just updatedAt)) =
|
||||
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))
|
||||
toMaybeGroupMember _ _ = Nothing
|
||||
|
||||
createGroupLink :: DB.Connection -> User -> GroupInfo -> ConnId -> ConnReqContact -> GroupLinkId -> GroupMemberRole -> SubscriptionMode -> ExceptT StoreError IO ()
|
||||
createGroupLink db User {userId} groupInfo@GroupInfo {groupId, localDisplayName} agentConnId cReq groupLinkId memberRole subMode =
|
||||
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 =
|
||||
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, group_link_member_role, auto_accept, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)"
|
||||
(userId, groupId, groupLinkId, "group_link_" <> localDisplayName, cReq, 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, group_link_member_role, auto_accept, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?)"
|
||||
(userId, groupId, groupLinkId, "group_link_" <> localDisplayName, cReq, shortLink, 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
|
||||
|
||||
|
@ -250,12 +251,12 @@ 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, ConnReqContact, GroupMemberRole)
|
||||
getGroupLink :: DB.Connection -> User -> GroupInfo -> ExceptT StoreError IO (Int64, CreatedLinkContact, GroupMemberRole)
|
||||
getGroupLink db User {userId} gInfo@GroupInfo {groupId} =
|
||||
ExceptT . firstRow groupLink (SEGroupLinkNotFound gInfo) $
|
||||
DB.query db "SELECT user_contact_link_id, conn_req_contact, group_link_member_role FROM user_contact_links WHERE user_id = ? AND group_id = ? LIMIT 1" (userId, groupId)
|
||||
DB.query db "SELECT user_contact_link_id, conn_req_contact, short_link_contact, group_link_member_role FROM user_contact_links WHERE user_id = ? AND group_id = ? LIMIT 1" (userId, groupId)
|
||||
where
|
||||
groupLink (linkId, cReq, mRole_) = (linkId, cReq, fromMaybe GRMember mRole_)
|
||||
groupLink (linkId, cReq, shortLink, mRole_) = (linkId, CCLink cReq shortLink, fromMaybe GRMember mRole_)
|
||||
|
||||
getGroupLinkId :: DB.Connection -> User -> GroupInfo -> IO (Maybe GroupLinkId)
|
||||
getGroupLinkId db User {userId} GroupInfo {groupId} =
|
||||
|
@ -1683,8 +1684,9 @@ getGroupInfo db vr User {userId, userContactId} groupId = ExceptT $ do
|
|||
|
||||
getGroupInfoByUserContactLinkConnReq :: DB.Connection -> VersionRangeChat -> User -> (ConnReqContact, ConnReqContact) -> IO (Maybe GroupInfo)
|
||||
getGroupInfoByUserContactLinkConnReq db vr user@User {userId} (cReqSchema1, cReqSchema2) = do
|
||||
-- fmap join is to support group_id = NULL if non-group contact request is sent to this function (e.g., if client data is appended).
|
||||
groupId_ <-
|
||||
maybeFirstRow fromOnly $
|
||||
fmap join . maybeFirstRow fromOnly $
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
|
@ -1695,6 +1697,26 @@ getGroupInfoByUserContactLinkConnReq db vr user@User {userId} (cReqSchema1, cReq
|
|||
(userId, cReqSchema1, cReqSchema2)
|
||||
maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getGroupInfo db vr user) groupId_
|
||||
|
||||
getGroupInfoViaUserShortLink :: DB.Connection -> VersionRangeChat -> User -> ShortLinkContact -> IO (Maybe (ConnReqContact, GroupInfo))
|
||||
getGroupInfoViaUserShortLink db vr user@User {userId} shortLink = fmap eitherToMaybe $ runExceptT $ do
|
||||
(cReq, groupId) <- ExceptT getConnReqGroup
|
||||
(cReq,) <$> getGroupInfo db vr user groupId
|
||||
where
|
||||
getConnReqGroup =
|
||||
firstRow' toConnReqGroupId (SEInternalError "group link not found") $
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT conn_req_contact, group_id
|
||||
FROM user_contact_links
|
||||
WHERE user_id = ? AND short_link_contact = ?
|
||||
|]
|
||||
(userId, shortLink)
|
||||
toConnReqGroupId = \case
|
||||
-- cReq is "not null", group_id is nullable
|
||||
(cReq, Just groupId) -> Right (cReq, groupId)
|
||||
_ -> Left $ SEInternalError "no conn req or group ID"
|
||||
|
||||
getGroupInfoByGroupLinkHash :: DB.Connection -> VersionRangeChat -> User -> (ConnReqUriHash, ConnReqUriHash) -> IO (Maybe GroupInfo)
|
||||
getGroupInfoByGroupLinkHash db vr user@User {userId, userContactId} (groupLinkHash1, groupLinkHash2) = do
|
||||
groupId_ <-
|
||||
|
|
|
@ -162,7 +162,7 @@ import Simplex.Chat.Store.NoteFolders
|
|||
import Simplex.Chat.Store.Shared
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Chat.Types.Shared
|
||||
import Simplex.Messaging.Agent.Protocol (AgentMsgId, ConnId, MsgMeta (..), UserId)
|
||||
import Simplex.Messaging.Agent.Protocol (AgentMsgId, ConnId, ConnShortLink, ConnectionMode (..), MsgMeta (..), UserId)
|
||||
import Simplex.Messaging.Agent.Store.AgentStore (firstRow, firstRow', maybeFirstRow)
|
||||
import Simplex.Messaging.Agent.Store.DB (BoolInt (..))
|
||||
import qualified Simplex.Messaging.Agent.Store.DB as DB
|
||||
|
@ -966,7 +966,7 @@ getContactConnectionChatPreviews_ db User {userId} pagination clq = case clq of
|
|||
[sql|
|
||||
SELECT
|
||||
connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id,
|
||||
custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at
|
||||
custom_user_profile_id, conn_req_inv, short_link_inv, local_alias, created_at, updated_at
|
||||
FROM connections
|
||||
WHERE user_id = ?
|
||||
AND conn_type = ?
|
||||
|
@ -982,7 +982,7 @@ getContactConnectionChatPreviews_ db User {userId} pagination clq = case clq of
|
|||
PTLast count -> DB.query db (query <> " ORDER BY updated_at DESC LIMIT ?") (params search :. Only count)
|
||||
PTAfter ts count -> DB.query db (query <> " AND updated_at > ? ORDER BY updated_at ASC LIMIT ?") (params search :. (ts, count))
|
||||
PTBefore ts count -> DB.query db (query <> " AND updated_at < ? ORDER BY updated_at DESC LIMIT ?") (params search :. (ts, count))
|
||||
toPreview :: (Int64, ConnId, ConnStatus, Maybe ByteString, Maybe Int64, Maybe GroupLinkId, Maybe Int64, Maybe ConnReqInvitation, LocalAlias, UTCTime, UTCTime) -> AChatPreviewData
|
||||
toPreview :: (Int64, ConnId, ConnStatus, Maybe ByteString, Maybe Int64, Maybe GroupLinkId, Maybe Int64, Maybe ConnReqInvitation, Maybe (ConnShortLink 'CMInvitation), LocalAlias, UTCTime, UTCTime) -> AChatPreviewData
|
||||
toPreview connRow =
|
||||
let conn@PendingContactConnection {updatedAt} = toPendingContactConnection connRow
|
||||
aChat = AChat SCTContactConnection $ Chat (ContactConnection conn) [] emptyChatStats
|
||||
|
|
|
@ -5,11 +5,13 @@ module Simplex.Chat.Store.Postgres.Migrations (migrations) where
|
|||
import Data.List (sortOn)
|
||||
import Data.Text (Text)
|
||||
import Simplex.Chat.Store.Postgres.Migrations.M20241220_initial
|
||||
import Simplex.Chat.Store.Postgres.Migrations.M20250402_short_links
|
||||
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
|
||||
|
||||
schemaMigrations :: [(String, Text, Maybe Text)]
|
||||
schemaMigrations =
|
||||
[ ("20241220_initial", m20241220_initial, Nothing)
|
||||
[ ("20241220_initial", m20241220_initial, Nothing),
|
||||
("20250402_short_links", m20250402_short_links, Just down_m20250402_short_links)
|
||||
]
|
||||
|
||||
-- | The list of migrations in ascending order by date
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module Simplex.Chat.Store.Postgres.Migrations.M20250402_short_links where
|
||||
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import Text.RawString.QQ (r)
|
||||
|
||||
m20250402_short_links :: Text
|
||||
m20250402_short_links =
|
||||
T.pack
|
||||
[r|
|
||||
ALTER TABLE user_contact_links ADD COLUMN short_link_contact BYTEA;
|
||||
ALTER TABLE connections ADD COLUMN short_link_inv BYTEA;
|
||||
|]
|
||||
|
||||
down_m20250402_short_links :: Text
|
||||
down_m20250402_short_links =
|
||||
T.pack
|
||||
[r|
|
||||
ALTER TABLE user_contact_links DROP COLUMN short_link_contact;
|
||||
ALTER TABLE connections DROP COLUMN short_link_inv;
|
||||
|]
|
|
@ -50,6 +50,7 @@ module Simplex.Chat.Store.Profiles
|
|||
getUserContactLinkById,
|
||||
getGroupLinkInfo,
|
||||
getUserContactLinkByConnReq,
|
||||
getUserContactLinkViaShortLink,
|
||||
getContactWithoutConnViaAddress,
|
||||
updateUserAddressAutoAccept,
|
||||
getProtocolServers,
|
||||
|
@ -100,7 +101,7 @@ import Simplex.Chat.Types.Preferences
|
|||
import Simplex.Chat.Types.Shared
|
||||
import Simplex.Chat.Types.UITheme
|
||||
import Simplex.Messaging.Agent.Env.SQLite (ServerRoles (..))
|
||||
import Simplex.Messaging.Agent.Protocol (ACorrId, ConnId, UserId)
|
||||
import Simplex.Messaging.Agent.Protocol (ACorrId, ConnId, ConnectionLink (..), CreatedConnLink (..), UserId)
|
||||
import Simplex.Messaging.Agent.Store.AgentStore (firstRow, maybeFirstRow)
|
||||
import Simplex.Messaging.Agent.Store.DB (BoolInt (..))
|
||||
import qualified Simplex.Messaging.Agent.Store.DB as DB
|
||||
|
@ -326,11 +327,13 @@ setUserProfileContactLink db user@User {userId, profile = p@LocalProfile {profil
|
|||
SET contact_link = ?, updated_at = ?
|
||||
WHERE user_id = ? AND contact_profile_id = ?
|
||||
|]
|
||||
(connReqContact_, ts, userId, profileId)
|
||||
pure (user :: User) {profile = p {contactLink = connReqContact_}}
|
||||
(contactLink, ts, userId, profileId)
|
||||
pure (user :: User) {profile = p {contactLink}}
|
||||
where
|
||||
connReqContact_ = case ucl_ of
|
||||
Just UserContactLink {connReqContact} -> Just connReqContact
|
||||
-- TODO [short links] this should be replaced with short links once they are supported by all clients.
|
||||
-- Or, maybe, we want to allow both, when both are optional.
|
||||
contactLink = case ucl_ of
|
||||
Just UserContactLink {connLinkContact = CCLink cReq _} -> Just $ CLFull cReq
|
||||
_ -> Nothing
|
||||
|
||||
-- only used in tests
|
||||
|
@ -346,17 +349,17 @@ getUserContactProfiles db User {userId} =
|
|||
|]
|
||||
(Only userId)
|
||||
where
|
||||
toContactProfile :: (ContactName, Text, Maybe ImageData, Maybe ConnReqContact, Maybe Preferences) -> Profile
|
||||
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 -> ConnReqContact -> SubscriptionMode -> ExceptT StoreError IO ()
|
||||
createUserContactLink db User {userId} agentConnId cReq subMode =
|
||||
createUserContactLink :: DB.Connection -> User -> ConnId -> CreatedLinkContact -> SubscriptionMode -> ExceptT StoreError IO ()
|
||||
createUserContactLink db User {userId} agentConnId (CCLink cReq shortLink) subMode =
|
||||
checkConstraint SEDuplicateContactLink . liftIO $ do
|
||||
currentTs <- getCurrentTime
|
||||
DB.execute
|
||||
db
|
||||
"INSERT INTO user_contact_links (user_id, conn_req_contact, created_at, updated_at) VALUES (?,?,?,?)"
|
||||
(userId, cReq, currentTs, currentTs)
|
||||
"INSERT INTO user_contact_links (user_id, conn_req_contact, short_link_contact, created_at, updated_at) VALUES (?,?,?,?,?)"
|
||||
(userId, cReq, shortLink, currentTs, currentTs)
|
||||
userContactLinkId <- insertedRowId db
|
||||
void $ createConnection_ db userId ConnUserContact (Just userContactLinkId) agentConnId ConnNew initialChatVersion chatInitialVRange Nothing Nothing Nothing 0 currentTs subMode CR.PQSupportOff
|
||||
|
||||
|
@ -450,7 +453,7 @@ data UserMsgReceiptSettings = UserMsgReceiptSettings
|
|||
deriving (Show)
|
||||
|
||||
data UserContactLink = UserContactLink
|
||||
{ connReqContact :: ConnReqContact,
|
||||
{ connLinkContact :: CreatedLinkContact,
|
||||
autoAccept :: Maybe AutoAccept
|
||||
}
|
||||
deriving (Show)
|
||||
|
@ -472,22 +475,15 @@ $(J.deriveJSON defaultJSON ''AutoAccept)
|
|||
|
||||
$(J.deriveJSON defaultJSON ''UserContactLink)
|
||||
|
||||
toUserContactLink :: (ConnReqContact, BoolInt, BoolInt, BoolInt, Maybe MsgContent) -> UserContactLink
|
||||
toUserContactLink (connReq, BI autoAccept, BI businessAddress, BI acceptIncognito, autoReply) =
|
||||
UserContactLink connReq $
|
||||
toUserContactLink :: (ConnReqContact, Maybe ShortLinkContact, BoolInt, BoolInt, BoolInt, Maybe MsgContent) -> UserContactLink
|
||||
toUserContactLink (connReq, shortLink, BI autoAccept, BI businessAddress, BI acceptIncognito, autoReply) =
|
||||
UserContactLink (CCLink connReq shortLink) $
|
||||
if autoAccept then Just AutoAccept {businessAddress, acceptIncognito, autoReply} else Nothing
|
||||
|
||||
getUserAddress :: DB.Connection -> User -> ExceptT StoreError IO UserContactLink
|
||||
getUserAddress db User {userId} =
|
||||
ExceptT . firstRow toUserContactLink SEUserContactLinkNotFound $
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT conn_req_contact, 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
|
||||
|]
|
||||
(Only userId)
|
||||
DB.query db (userContactLinkQuery <> " WHERE user_id = ? AND local_display_name = '' AND group_id IS NULL") (Only userId)
|
||||
|
||||
getUserContactLinkById :: DB.Connection -> UserId -> Int64 -> ExceptT StoreError IO (UserContactLink, Maybe GroupLinkInfo)
|
||||
getUserContactLinkById db userId userContactLinkId =
|
||||
|
@ -495,7 +491,7 @@ getUserContactLinkById db userId userContactLinkId =
|
|||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT conn_req_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, 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 = ?
|
||||
|]
|
||||
|
@ -521,14 +517,19 @@ getGroupLinkInfo db userId groupId =
|
|||
getUserContactLinkByConnReq :: DB.Connection -> User -> (ConnReqContact, ConnReqContact) -> IO (Maybe UserContactLink)
|
||||
getUserContactLinkByConnReq db User {userId} (cReqSchema1, cReqSchema2) =
|
||||
maybeFirstRow toUserContactLink $
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT conn_req_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content
|
||||
FROM user_contact_links
|
||||
WHERE user_id = ? AND conn_req_contact IN (?,?)
|
||||
|]
|
||||
(userId, cReqSchema1, cReqSchema2)
|
||||
DB.query db (userContactLinkQuery <> " WHERE user_id = ? AND conn_req_contact IN (?,?)") (userId, cReqSchema1, cReqSchema2)
|
||||
|
||||
getUserContactLinkViaShortLink :: DB.Connection -> User -> ShortLinkContact -> IO (Maybe UserContactLink)
|
||||
getUserContactLinkViaShortLink db User {userId} shortLink =
|
||||
maybeFirstRow toUserContactLink $
|
||||
DB.query db (userContactLinkQuery <> " WHERE user_id = ? AND short_link_contact = ?") (userId, shortLink)
|
||||
|
||||
userContactLinkQuery :: Query
|
||||
userContactLinkQuery =
|
||||
[sql|
|
||||
SELECT conn_req_contact, short_link_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content
|
||||
FROM user_contact_links
|
||||
|]
|
||||
|
||||
getContactWithoutConnViaAddress :: DB.Connection -> VersionRangeChat -> User -> (ConnReqContact, ConnReqContact) -> IO (Maybe Contact)
|
||||
getContactWithoutConnViaAddress db vr user@User {userId} (cReqSchema1, cReqSchema2) = do
|
||||
|
|
|
@ -128,6 +128,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20250122_chat_items_include_in_hist
|
|||
import Simplex.Chat.Store.SQLite.Migrations.M20250126_mentions
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20250129_delete_unused_contacts
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20250130_indexes
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20250402_short_links
|
||||
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
|
||||
|
||||
schemaMigrations :: [(String, Query, Maybe Query)]
|
||||
|
@ -255,7 +256,8 @@ schemaMigrations =
|
|||
("20250122_chat_items_include_in_history", m20250122_chat_items_include_in_history, Just down_m20250122_chat_items_include_in_history),
|
||||
("20250126_mentions", m20250126_mentions, Just down_m20250126_mentions),
|
||||
("20250129_delete_unused_contacts", m20250129_delete_unused_contacts, Just down_m20250129_delete_unused_contacts),
|
||||
("20250130_indexes", m20250130_indexes, Just down_m20250130_indexes)
|
||||
("20250130_indexes", m20250130_indexes, Just down_m20250130_indexes),
|
||||
("20250402_short_links", m20250402_short_links, Just down_m20250402_short_links)
|
||||
]
|
||||
|
||||
-- | The list of migrations in ascending order by date
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module Simplex.Chat.Store.SQLite.Migrations.M20250402_short_links where
|
||||
|
||||
import Database.SQLite.Simple (Query)
|
||||
import Database.SQLite.Simple.QQ (sql)
|
||||
|
||||
m20250402_short_links :: Query
|
||||
m20250402_short_links =
|
||||
[sql|
|
||||
ALTER TABLE user_contact_links ADD COLUMN short_link_contact BLOB;
|
||||
ALTER TABLE connections ADD COLUMN short_link_inv BLOB;
|
||||
ALTER TABLE connections ADD COLUMN via_short_link_contact BLOB;
|
||||
|
||||
|]
|
||||
|
||||
down_m20250402_short_links :: Query
|
||||
down_m20250402_short_links =
|
||||
[sql|
|
||||
ALTER TABLE user_contact_links DROP COLUMN short_link_contact;
|
||||
ALTER TABLE connections DROP COLUMN short_link_inv;
|
||||
ALTER TABLE connections DROP COLUMN via_short_link_contact;
|
||||
|]
|
|
@ -443,6 +443,22 @@ Query:
|
|||
Plan:
|
||||
SEARCH connections USING PRIMARY KEY (conn_id=?)
|
||||
|
||||
Query:
|
||||
SELECT link_id, snd_private_key
|
||||
FROM inv_short_links
|
||||
WHERE host = ? AND port = ? AND snd_id = ?
|
||||
|
||||
Plan:
|
||||
SEARCH inv_short_links USING INDEX idx_inv_short_links_link_id (host=? AND port=?)
|
||||
|
||||
Query:
|
||||
SELECT link_key, snd_private_key, snd_id
|
||||
FROM inv_short_links
|
||||
WHERE host = ? AND port = ? AND link_id = ?
|
||||
|
||||
Plan:
|
||||
SEARCH inv_short_links USING INDEX idx_inv_short_links_link_id (host=? AND port=? AND link_id=?)
|
||||
|
||||
Query:
|
||||
SELECT s.internal_id, m.msg_type, s.internal_hash, s.rcpt_internal_id, s.rcpt_status
|
||||
FROM snd_messages s
|
||||
|
@ -466,6 +482,19 @@ Query:
|
|||
|
||||
Plan:
|
||||
|
||||
Query:
|
||||
INSERT INTO inv_short_links
|
||||
(host, port, server_key_hash, link_id, link_key, snd_private_key, snd_id)
|
||||
VALUES (?,?,?,?,?,?,?)
|
||||
ON CONFLICT (host, port, link_id)
|
||||
DO UPDATE SET
|
||||
server_key_hash = EXCLUDED.server_key_hash,
|
||||
link_key = EXCLUDED.link_key,
|
||||
snd_private_key = EXCLUDED.snd_private_key,
|
||||
snd_id = EXCLUDED.snd_id
|
||||
|
||||
Plan:
|
||||
|
||||
Query:
|
||||
INSERT INTO messages
|
||||
(conn_id, internal_id, internal_ts, internal_rcv_id, internal_snd_id, msg_type, msg_flags, msg_body, pq_encryption)
|
||||
|
@ -524,7 +553,10 @@ SEARCH messages USING COVERING INDEX idx_messages_conn_id_internal_rcv_id (conn_
|
|||
|
||||
Query:
|
||||
INSERT INTO rcv_queues
|
||||
(host, port, rcv_id, conn_id, rcv_private_key, rcv_dh_secret, e2e_priv_key, e2e_dh_secret, snd_id, snd_secure, status, rcv_queue_id, rcv_primary, replace_rcv_queue_id, smp_client_version, server_key_hash) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);
|
||||
( host, port, rcv_id, conn_id, rcv_private_key, rcv_dh_secret, e2e_priv_key, e2e_dh_secret,
|
||||
snd_id, queue_mode, status, rcv_queue_id, rcv_primary, replace_rcv_queue_id, smp_client_version, server_key_hash,
|
||||
link_id, link_key, link_priv_sig_key, link_enc_fixed_data
|
||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);
|
||||
|
||||
Plan:
|
||||
|
||||
|
@ -546,14 +578,14 @@ SEARCH messages USING COVERING INDEX idx_messages_conn_id_internal_snd_id (conn_
|
|||
|
||||
Query:
|
||||
INSERT INTO snd_queues
|
||||
(host, port, snd_id, snd_secure, conn_id, snd_public_key, snd_private_key, e2e_pub_key, e2e_dh_secret,
|
||||
(host, port, snd_id, queue_mode, conn_id, snd_public_key, snd_private_key, e2e_pub_key, e2e_dh_secret,
|
||||
status, snd_queue_id, snd_primary, replace_snd_queue_id, smp_client_version, server_key_hash)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
ON CONFLICT (host, port, snd_id) DO UPDATE SET
|
||||
host=EXCLUDED.host,
|
||||
port=EXCLUDED.port,
|
||||
snd_id=EXCLUDED.snd_id,
|
||||
snd_secure=EXCLUDED.snd_secure,
|
||||
queue_mode=EXCLUDED.queue_mode,
|
||||
conn_id=EXCLUDED.conn_id,
|
||||
snd_public_key=EXCLUDED.snd_public_key,
|
||||
snd_private_key=EXCLUDED.snd_private_key,
|
||||
|
@ -631,6 +663,14 @@ Query:
|
|||
Plan:
|
||||
SEARCH connections USING PRIMARY KEY (conn_id=?)
|
||||
|
||||
Query:
|
||||
UPDATE inv_short_links
|
||||
SET snd_id = ?
|
||||
WHERE host = ? AND port = ? AND link_id = ?
|
||||
|
||||
Plan:
|
||||
SEARCH inv_short_links USING INDEX idx_inv_short_links_link_id (host=? AND port=? AND link_id=?)
|
||||
|
||||
Query:
|
||||
UPDATE ratchets
|
||||
SET x3dh_priv_key_1 = ?, x3dh_priv_key_2 = ?, pq_priv_kem = ?
|
||||
|
@ -691,7 +731,7 @@ SEARCH snd_queues USING PRIMARY KEY (host=? AND port=? AND snd_id=?)
|
|||
|
||||
Query:
|
||||
SELECT
|
||||
c.user_id, COALESCE(q.server_key_hash, s.key_hash), q.conn_id, q.host, q.port, q.snd_id, q.snd_secure,
|
||||
c.user_id, COALESCE(q.server_key_hash, s.key_hash), q.conn_id, q.host, q.port, q.snd_id, q.queue_mode,
|
||||
q.snd_public_key, q.snd_private_key, q.e2e_pub_key, q.e2e_dh_secret, q.status,
|
||||
q.snd_queue_id, q.snd_primary, q.replace_snd_queue_id, q.switch_status, q.smp_client_version
|
||||
FROM snd_queues q
|
||||
|
@ -705,9 +745,10 @@ SEARCH s USING PRIMARY KEY (host=? AND port=?)
|
|||
|
||||
Query:
|
||||
SELECT c.user_id, COALESCE(q.server_key_hash, s.key_hash), q.conn_id, q.host, q.port, q.rcv_id, q.rcv_private_key, q.rcv_dh_secret,
|
||||
q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.snd_secure, q.status,
|
||||
q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.queue_mode, q.status,
|
||||
q.rcv_queue_id, q.rcv_primary, q.replace_rcv_queue_id, q.switch_status, q.smp_client_version, q.delete_errors,
|
||||
q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret
|
||||
q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret,
|
||||
q.link_id, q.link_key, q.link_priv_sig_key, q.link_enc_fixed_data
|
||||
FROM rcv_queues q
|
||||
JOIN servers s ON q.host = s.host AND q.port = s.port
|
||||
JOIN connections c ON q.conn_id = c.conn_id
|
||||
|
@ -719,9 +760,10 @@ SEARCH s USING PRIMARY KEY (host=? AND port=?)
|
|||
|
||||
Query:
|
||||
SELECT c.user_id, COALESCE(q.server_key_hash, s.key_hash), q.conn_id, q.host, q.port, q.rcv_id, q.rcv_private_key, q.rcv_dh_secret,
|
||||
q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.snd_secure, q.status,
|
||||
q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.queue_mode, q.status,
|
||||
q.rcv_queue_id, q.rcv_primary, q.replace_rcv_queue_id, q.switch_status, q.smp_client_version, q.delete_errors,
|
||||
q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret
|
||||
q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret,
|
||||
q.link_id, q.link_key, q.link_priv_sig_key, q.link_enc_fixed_data
|
||||
FROM rcv_queues q
|
||||
JOIN servers s ON q.host = s.host AND q.port = s.port
|
||||
JOIN connections c ON q.conn_id = c.conn_id
|
||||
|
@ -733,9 +775,10 @@ SEARCH c USING PRIMARY KEY (conn_id=?)
|
|||
|
||||
Query:
|
||||
SELECT c.user_id, COALESCE(q.server_key_hash, s.key_hash), q.conn_id, q.host, q.port, q.rcv_id, q.rcv_private_key, q.rcv_dh_secret,
|
||||
q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.snd_secure, q.status,
|
||||
q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.queue_mode, q.status,
|
||||
q.rcv_queue_id, q.rcv_primary, q.replace_rcv_queue_id, q.switch_status, q.smp_client_version, q.delete_errors,
|
||||
q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret
|
||||
q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret,
|
||||
q.link_id, q.link_key, q.link_priv_sig_key, q.link_enc_fixed_data
|
||||
FROM rcv_queues q
|
||||
JOIN servers s ON q.host = s.host AND q.port = s.port
|
||||
JOIN connections c ON q.conn_id = c.conn_id
|
||||
|
@ -747,9 +790,10 @@ SEARCH c USING PRIMARY KEY (conn_id=?)
|
|||
|
||||
Query:
|
||||
SELECT c.user_id, COALESCE(q.server_key_hash, s.key_hash), q.conn_id, q.host, q.port, q.rcv_id, q.rcv_private_key, q.rcv_dh_secret,
|
||||
q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.snd_secure, q.status,
|
||||
q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.queue_mode, q.status,
|
||||
q.rcv_queue_id, q.rcv_primary, q.replace_rcv_queue_id, q.switch_status, q.smp_client_version, q.delete_errors,
|
||||
q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret
|
||||
q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret,
|
||||
q.link_id, q.link_key, q.link_priv_sig_key, q.link_enc_fixed_data
|
||||
FROM rcv_queues q
|
||||
JOIN servers s ON q.host = s.host AND q.port = s.port
|
||||
JOIN connections c ON q.conn_id = c.conn_id
|
||||
|
@ -761,9 +805,10 @@ SEARCH s USING PRIMARY KEY (host=? AND port=?)
|
|||
|
||||
Query:
|
||||
SELECT c.user_id, COALESCE(q.server_key_hash, s.key_hash), q.conn_id, q.host, q.port, q.rcv_id, q.rcv_private_key, q.rcv_dh_secret,
|
||||
q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.snd_secure, q.status,
|
||||
q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.queue_mode, q.status,
|
||||
q.rcv_queue_id, q.rcv_primary, q.replace_rcv_queue_id, q.switch_status, q.smp_client_version, q.delete_errors,
|
||||
q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret
|
||||
q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret,
|
||||
q.link_id, q.link_key, q.link_priv_sig_key, q.link_enc_fixed_data
|
||||
FROM rcv_queues q
|
||||
JOIN servers s ON q.host = s.host AND q.port = s.port
|
||||
JOIN connections c ON q.conn_id = c.conn_id
|
||||
|
@ -799,6 +844,10 @@ Query: DELETE FROM deleted_snd_chunk_replicas WHERE deleted_snd_chunk_replica_id
|
|||
Plan:
|
||||
SEARCH deleted_snd_chunk_replicas USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: DELETE FROM inv_short_links WHERE host = ? AND port = ? AND link_id = ?
|
||||
Plan:
|
||||
SEARCH inv_short_links USING INDEX idx_inv_short_links_link_id (host=? AND port=? AND link_id=?)
|
||||
|
||||
Query: DELETE FROM messages WHERE conn_id = ? AND internal_id = ?;
|
||||
Plan:
|
||||
SEARCH messages USING PRIMARY KEY (conn_id=? AND internal_id=?)
|
||||
|
|
|
@ -497,6 +497,14 @@ Plan:
|
|||
SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=?)
|
||||
USE TEMP B-TREE FOR ORDER BY
|
||||
|
||||
Query:
|
||||
SELECT conn_req_contact, group_id
|
||||
FROM user_contact_links
|
||||
WHERE user_id = ? AND short_link_contact = ?
|
||||
|
||||
Plan:
|
||||
SEARCH user_contact_links USING INDEX sqlite_autoindex_user_contact_links_1 (user_id=?)
|
||||
|
||||
Query:
|
||||
SELECT conn_req_contact, group_id
|
||||
FROM user_contact_links
|
||||
|
@ -505,6 +513,14 @@ Query:
|
|||
Plan:
|
||||
SEARCH user_contact_links USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
SELECT conn_req_inv, agent_conn_id
|
||||
FROM connections
|
||||
WHERE user_id = ? AND short_link_inv = ? LIMIT 1
|
||||
|
||||
Plan:
|
||||
SEARCH connections USING INDEX idx_connections_updated_at (user_id=?)
|
||||
|
||||
Query:
|
||||
SELECT connection_id, agent_conn_id, conn_level, via_contact, via_user_contact_link, via_group_link, group_link_id, custom_user_profile_id,
|
||||
conn_status, conn_type, contact_conn_initiated, local_alias, contact_id, group_member_id, snd_file_id, rcv_file_id, user_contact_link_id,
|
||||
|
@ -1347,7 +1363,7 @@ SCAN cc
|
|||
Query:
|
||||
SELECT
|
||||
connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id,
|
||||
custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at
|
||||
custom_user_profile_id, conn_req_inv, short_link_inv, local_alias, created_at, updated_at
|
||||
FROM connections
|
||||
WHERE user_id = ?
|
||||
AND conn_type = ?
|
||||
|
@ -1364,7 +1380,7 @@ SEARCH connections USING INDEX idx_connections_updated_at (user_id=? AND updated
|
|||
Query:
|
||||
SELECT
|
||||
connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id,
|
||||
custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at
|
||||
custom_user_profile_id, conn_req_inv, short_link_inv, local_alias, created_at, updated_at
|
||||
FROM connections
|
||||
WHERE user_id = ?
|
||||
AND conn_type = ?
|
||||
|
@ -1381,7 +1397,7 @@ SEARCH connections USING INDEX idx_connections_updated_at (user_id=? AND updated
|
|||
Query:
|
||||
SELECT
|
||||
connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id,
|
||||
custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at
|
||||
custom_user_profile_id, conn_req_inv, short_link_inv, local_alias, created_at, updated_at
|
||||
FROM connections
|
||||
WHERE user_id = ?
|
||||
AND conn_type = ?
|
||||
|
@ -2935,23 +2951,7 @@ Plan:
|
|||
SEARCH commands USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
SELECT conn_req_contact, 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, 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, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content, group_id, group_link_member_role
|
||||
SELECT conn_req_contact, short_link_contact, 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 = ?
|
||||
|
||||
|
@ -2970,7 +2970,7 @@ Plan:
|
|||
SEARCH connections USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at
|
||||
SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, custom_user_profile_id, conn_req_inv, short_link_inv, local_alias, created_at, updated_at
|
||||
FROM connections
|
||||
WHERE user_id = ?
|
||||
AND conn_type = ?
|
||||
|
@ -2980,7 +2980,7 @@ Plan:
|
|||
SEARCH connections USING INDEX idx_connections_updated_at (user_id=?)
|
||||
|
||||
Query:
|
||||
SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at
|
||||
SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, custom_user_profile_id, conn_req_inv, short_link_inv, local_alias, created_at, updated_at
|
||||
FROM connections
|
||||
WHERE user_id = ?
|
||||
AND connection_id = ?
|
||||
|
@ -3944,9 +3944,9 @@ Plan:
|
|||
|
||||
Query:
|
||||
INSERT INTO connections
|
||||
(user_id, agent_conn_id, conn_req_inv, conn_status, conn_type, contact_conn_initiated, custom_user_profile_id,
|
||||
(user_id, agent_conn_id, conn_req_inv, short_link_inv, conn_status, conn_type, contact_conn_initiated, custom_user_profile_id,
|
||||
created_at, updated_at, to_subscribe, conn_chat_version, pq_support, pq_encryption)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
|
||||
Plan:
|
||||
|
||||
|
@ -3962,9 +3962,9 @@ Plan:
|
|||
Query:
|
||||
INSERT INTO connections (
|
||||
user_id, agent_conn_id, conn_status, conn_type, contact_conn_initiated,
|
||||
via_contact_uri_hash, xcontact_id, custom_user_profile_id, via_group_link, group_link_id,
|
||||
via_contact_uri_hash, via_short_link_contact, xcontact_id, custom_user_profile_id, via_group_link, group_link_id,
|
||||
created_at, updated_at, to_subscribe, conn_chat_version, pq_support, pq_encryption
|
||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
|
||||
Plan:
|
||||
|
||||
|
@ -4650,6 +4650,27 @@ SEARCH c USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
|
|||
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
|
||||
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
|
||||
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
|
||||
FROM user_contact_links
|
||||
WHERE user_id = ? AND short_link_contact = ?
|
||||
Plan:
|
||||
SEARCH user_contact_links USING INDEX sqlite_autoindex_user_contact_links_1 (user_id=?)
|
||||
|
||||
Query:
|
||||
SELECT f.file_id, f.ci_file_status, f.file_path
|
||||
FROM chat_items i
|
||||
|
@ -5409,10 +5430,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, created_at, updated_at) VALUES (?,?,?,?)
|
||||
Query: INSERT INTO user_contact_links (user_id, conn_req_contact, short_link_contact, 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, 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, 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,?,?,?,?,?)
|
||||
|
@ -5637,7 +5658,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, 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, 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=?)
|
||||
|
||||
|
@ -5685,7 +5706,7 @@ Query: UPDATE connections SET conn_status = ?, updated_at = ? WHERE connection_i
|
|||
Plan:
|
||||
SEARCH connections USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: UPDATE connections SET conn_status = ?, updated_at = ?, conn_req_inv = NULL WHERE connection_id = ?
|
||||
Query: UPDATE connections SET conn_status = ?, updated_at = ?, conn_req_inv = NULL, short_link_inv = NULL WHERE connection_id = ?
|
||||
Plan:
|
||||
SEARCH connections USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
|
|
|
@ -297,6 +297,8 @@ CREATE TABLE connections(
|
|||
pq_snd_enabled INTEGER,
|
||||
pq_rcv_enabled INTEGER,
|
||||
quota_err_counter INTEGER NOT NULL DEFAULT 0,
|
||||
short_link_inv BLOB,
|
||||
via_short_link_contact BLOB,
|
||||
FOREIGN KEY(snd_file_id, connection_id)
|
||||
REFERENCES snd_files(file_id, connection_id)
|
||||
ON DELETE CASCADE
|
||||
|
@ -316,6 +318,7 @@ CREATE TABLE user_contact_links(
|
|||
group_link_id BLOB,
|
||||
group_link_member_role TEXT NULL,
|
||||
business_address INTEGER DEFAULT 0,
|
||||
short_link_contact BLOB,
|
||||
UNIQUE(user_id, local_display_name)
|
||||
);
|
||||
CREATE TABLE contact_requests(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{-# LANGUAGE CPP #-}
|
||||
{-# LANGUAGE DataKinds #-}
|
||||
{-# LANGUAGE DeriveAnyClass #-}
|
||||
{-# LANGUAGE DuplicateRecordFields #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
|
@ -35,7 +36,7 @@ import Simplex.Chat.Types
|
|||
import Simplex.Chat.Types.Preferences
|
||||
import Simplex.Chat.Types.Shared
|
||||
import Simplex.Chat.Types.UITheme
|
||||
import Simplex.Messaging.Agent.Protocol (ConnId, UserId)
|
||||
import Simplex.Messaging.Agent.Protocol (ConnId, ConnShortLink, ConnectionMode (..), CreatedConnLink (..), UserId)
|
||||
import Simplex.Messaging.Agent.Store.AgentStore (firstRow, maybeFirstRow)
|
||||
import Simplex.Messaging.Agent.Store.DB (BoolInt (..))
|
||||
import qualified Simplex.Messaging.Agent.Store.DB as DB
|
||||
|
@ -416,7 +417,7 @@ deleteUnusedIncognitoProfileById_ db User {userId} profileId =
|
|||
|]
|
||||
(userId, profileId, userId, profileId, userId, profileId)
|
||||
|
||||
type ContactRow' = (ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, BoolInt, ContactStatus) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) :. (Maybe GroupMemberId, BoolInt, Maybe UIThemeEntityOverrides, BoolInt, Maybe CustomData, Maybe Int64)
|
||||
type ContactRow' = (ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, LocalAlias, BoolInt, ContactStatus) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) :. (Maybe GroupMemberId, BoolInt, Maybe UIThemeEntityOverrides, BoolInt, Maybe CustomData, Maybe Int64)
|
||||
|
||||
type ContactRow = Only ContactId :. ContactRow'
|
||||
|
||||
|
@ -441,10 +442,10 @@ getProfileById db userId profileId =
|
|||
|]
|
||||
(userId, profileId)
|
||||
where
|
||||
toProfile :: (ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences) -> LocalProfile
|
||||
toProfile :: (ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, LocalAlias, Maybe Preferences) -> LocalProfile
|
||||
toProfile (displayName, fullName, image, contactLink, localAlias, preferences) = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias}
|
||||
|
||||
type ContactRequestRow = (Int64, ContactName, AgentInvId, Maybe ContactId, Int64, AgentConnId, Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact) :. (Maybe XContactId, PQSupport, Maybe Preferences, UTCTime, UTCTime, VersionChat, VersionChat)
|
||||
type ContactRequestRow = (Int64, ContactName, AgentInvId, Maybe ContactId, Int64, AgentConnId, Int64, ContactName, Text, Maybe ImageData, Maybe ConnLinkContact) :. (Maybe XContactId, PQSupport, Maybe Preferences, UTCTime, UTCTime, VersionChat, VersionChat)
|
||||
|
||||
toContactRequest :: ContactRequestRow -> UserContactRequest
|
||||
toContactRequest ((contactRequestId, localDisplayName, agentInvitationId, contactId_, userContactLinkId, agentContactConnId, profileId, displayName, fullName, image, contactLink) :. (xContactId, pqSupport, preferences, createdAt, updatedAt, minVer, maxVer)) = do
|
||||
|
@ -462,7 +463,7 @@ userQuery =
|
|||
JOIN contact_profiles ucp ON ucp.contact_profile_id = uct.contact_profile_id
|
||||
|]
|
||||
|
||||
toUser :: (UserId, UserId, ContactId, ProfileId, BoolInt, Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, Maybe Preferences) :. (BoolInt, BoolInt, BoolInt, Maybe B64UrlByteString, Maybe B64UrlByteString, Maybe UTCTime, Maybe UIThemeEntityOverrides) -> User
|
||||
toUser :: (UserId, UserId, ContactId, ProfileId, BoolInt, Int64, ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, Maybe Preferences) :. (BoolInt, BoolInt, BoolInt, Maybe B64UrlByteString, Maybe B64UrlByteString, Maybe UTCTime, Maybe UIThemeEntityOverrides) -> User
|
||||
toUser ((userId, auId, userContactId, profileId, BI activeUser, activeOrder, displayName, fullName, image, contactLink, userPreferences) :. (BI showNtfs, BI sendRcptsContacts, BI sendRcptsSmallGroups, viewPwdHash_, viewPwdSalt_, userMemberProfileUpdatedAt, uiThemes)) =
|
||||
User {userId, agentUserId = AgentUserId auId, userContactId, localDisplayName = displayName, profile, activeUser, activeOrder, fullPreferences, showNtfs, sendRcptsContacts, sendRcptsSmallGroups, viewPwdHash, userMemberProfileUpdatedAt, uiThemes}
|
||||
where
|
||||
|
@ -470,9 +471,10 @@ toUser ((userId, auId, userContactId, profileId, BI activeUser, activeOrder, dis
|
|||
fullPreferences = mergePreferences Nothing userPreferences
|
||||
viewPwdHash = UserPwdHash <$> viewPwdHash_ <*> viewPwdSalt_
|
||||
|
||||
toPendingContactConnection :: (Int64, ConnId, ConnStatus, Maybe ByteString, Maybe Int64, Maybe GroupLinkId, Maybe Int64, Maybe ConnReqInvitation, LocalAlias, UTCTime, UTCTime) -> PendingContactConnection
|
||||
toPendingContactConnection (pccConnId, acId, pccConnStatus, connReqHash, viaUserContactLink, groupLinkId, customUserProfileId, connReqInv, localAlias, createdAt, updatedAt) =
|
||||
PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = isJust connReqHash, viaUserContactLink, groupLinkId, customUserProfileId, connReqInv, localAlias, createdAt, updatedAt}
|
||||
toPendingContactConnection :: (Int64, ConnId, ConnStatus, Maybe ByteString, Maybe Int64, Maybe GroupLinkId, Maybe Int64, Maybe ConnReqInvitation, Maybe (ConnShortLink 'CMInvitation), LocalAlias, UTCTime, UTCTime) -> PendingContactConnection
|
||||
toPendingContactConnection (pccConnId, acId, pccConnStatus, connReqHash, viaUserContactLink, groupLinkId, customUserProfileId, connReqInv, shortLinkInv, localAlias, createdAt, updatedAt) =
|
||||
let connLinkInv = (`CCLink` shortLinkInv) <$> connReqInv
|
||||
in PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = isJust connReqHash, viaUserContactLink, groupLinkId, customUserProfileId, connLinkInv, localAlias, createdAt, updatedAt}
|
||||
|
||||
getConnReqInv :: DB.Connection -> Int64 -> ExceptT StoreError IO ConnReqInvitation
|
||||
getConnReqInv db connId =
|
||||
|
@ -579,7 +581,7 @@ type BusinessChatInfoRow = (Maybe BusinessChatType, Maybe MemberId, Maybe Member
|
|||
|
||||
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Text, Maybe Text, Maybe ImageData, Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. BusinessChatInfoRow :. (Maybe UIThemeEntityOverrides, Maybe CustomData, Maybe Int64) :. GroupMemberRow
|
||||
|
||||
type GroupMemberRow = (Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences) :. (UTCTime, UTCTime)
|
||||
type GroupMemberRow = (Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, LocalAlias, Maybe Preferences) :. (UTCTime, UTCTime)
|
||||
|
||||
toGroupInfo :: VersionRangeChat -> Int64 -> [ChatTagId] -> GroupInfoRow -> GroupInfo
|
||||
toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, localAlias, description, image, enableNtfs_, sendRcpts, BI favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. businessRow :. (uiThemes, customData, chatItemTTL) :. userMemberRow) =
|
||||
|
|
|
@ -9,12 +9,13 @@ module Simplex.Chat.Terminal where
|
|||
|
||||
import Control.Monad
|
||||
import qualified Data.List.NonEmpty as L
|
||||
import Simplex.Chat (defaultChatConfig, operatorSimpleXChat)
|
||||
import Simplex.Chat (defaultChatConfig)
|
||||
import Simplex.Chat.Controller
|
||||
import Simplex.Chat.Core
|
||||
import Simplex.Chat.Help (chatWelcome)
|
||||
import Simplex.Chat.Library.Commands (_defaultNtfServers)
|
||||
import Simplex.Chat.Operators
|
||||
import Simplex.Chat.Operators.Presets (operatorSimpleXChat)
|
||||
import Simplex.Chat.Options
|
||||
import Simplex.Chat.Terminal.Input
|
||||
import Simplex.Chat.Terminal.Output
|
||||
|
|
|
@ -51,7 +51,7 @@ import Simplex.Chat.Types.UITheme
|
|||
import Simplex.Chat.Types.Util
|
||||
import Simplex.FileTransfer.Description (FileDigest)
|
||||
import Simplex.FileTransfer.Types (RcvFileId, SndFileId)
|
||||
import Simplex.Messaging.Agent.Protocol (ACorrId, AEventTag (..), AEvtTag (..), ConnId, ConnectionMode (..), ConnectionRequestUri, InvitationId, SAEntity (..), UserId)
|
||||
import Simplex.Messaging.Agent.Protocol (ACorrId, AEventTag (..), AEvtTag (..), ConnId, ConnShortLink, ConnectionLink, ConnectionMode (..), ConnectionRequestUri, CreatedConnLink, InvitationId, SAEntity (..), UserId)
|
||||
import Simplex.Messaging.Agent.Store.DB (Binary (..), blobFieldDecoder, fromTextField_)
|
||||
import Simplex.Messaging.Crypto.File (CryptoFileArgs (..))
|
||||
import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport, pattern PQEncOff)
|
||||
|
@ -220,6 +220,8 @@ contactConnId c = aConnId <$> contactConn c
|
|||
|
||||
type IncognitoEnabled = Bool
|
||||
|
||||
type CreateShortLink = Bool
|
||||
|
||||
contactConnIncognito :: Contact -> IncognitoEnabled
|
||||
contactConnIncognito = maybe False connIncognito . contactConn
|
||||
|
||||
|
@ -559,7 +561,7 @@ data Profile = Profile
|
|||
{ displayName :: ContactName,
|
||||
fullName :: Text,
|
||||
image :: Maybe ImageData,
|
||||
contactLink :: Maybe ConnReqContact,
|
||||
contactLink :: Maybe ConnLinkContact,
|
||||
preferences :: Maybe Preferences
|
||||
-- fields that should not be read into this data type to prevent sending them as part of profile to contacts:
|
||||
-- - contact_profile_id
|
||||
|
@ -592,7 +594,7 @@ data LocalProfile = LocalProfile
|
|||
displayName :: ContactName,
|
||||
fullName :: Text,
|
||||
image :: Maybe ImageData,
|
||||
contactLink :: Maybe ConnReqContact,
|
||||
contactLink :: Maybe ConnLinkContact,
|
||||
preferences :: Maybe Preferences,
|
||||
localAlias :: LocalAlias
|
||||
}
|
||||
|
@ -1407,6 +1409,14 @@ type ConnReqInvitation = ConnectionRequestUri 'CMInvitation
|
|||
|
||||
type ConnReqContact = ConnectionRequestUri 'CMContact
|
||||
|
||||
type CreatedLinkInvitation = CreatedConnLink 'CMInvitation
|
||||
|
||||
type CreatedLinkContact = CreatedConnLink 'CMContact
|
||||
|
||||
type ConnLinkContact = ConnectionLink 'CMContact
|
||||
|
||||
type ShortLinkContact = ConnShortLink 'CMContact
|
||||
|
||||
data Connection = Connection
|
||||
{ connId :: Int64,
|
||||
agentConnId :: AgentConnId,
|
||||
|
@ -1484,7 +1494,7 @@ data PendingContactConnection = PendingContactConnection
|
|||
viaUserContactLink :: Maybe Int64,
|
||||
groupLinkId :: Maybe GroupLinkId,
|
||||
customUserProfileId :: Maybe Int64,
|
||||
connReqInv :: Maybe ConnReqInvitation,
|
||||
connLinkInv :: Maybe CreatedLinkInvitation,
|
||||
localAlias :: Text,
|
||||
createdAt :: UTCTime,
|
||||
updatedAt :: UTCTime
|
||||
|
|
|
@ -90,7 +90,7 @@ serializeChatResponse :: (Maybe RemoteHostId, Maybe User) -> CurrentTime -> Time
|
|||
serializeChatResponse user_ ts tz remoteHost_ = unlines . map unStyle . responseToView user_ defaultChatConfig False ts tz remoteHost_
|
||||
|
||||
responseToView :: (Maybe RemoteHostId, Maybe User) -> ChatConfig -> Bool -> CurrentTime -> TimeZone -> Maybe RemoteHostId -> ChatResponse -> [StyledString]
|
||||
responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showReceipts, testView} liveItems ts tz outputRH = \case
|
||||
responseToView hu@(currentRH, user_) cfg@ChatConfig {logLevel, showReactions, showReceipts, testView} liveItems ts tz outputRH = \case
|
||||
CRActiveUser User {profile, uiThemes} -> viewUserProfile (fromLocalProfile profile) <> viewUITheme uiThemes
|
||||
CRUsersList users -> viewUsersList users
|
||||
CRChatStarted -> ["chat started"]
|
||||
|
@ -108,7 +108,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
|||
CRUserServersValidation {} -> []
|
||||
CRUsageConditions current _ accepted_ -> viewUsageConditions current accepted_
|
||||
CRChatItemTTL u ttl -> ttyUser u $ viewChatItemTTL ttl
|
||||
CRNetworkConfig cfg -> viewNetworkConfig cfg
|
||||
CRNetworkConfig netCfg -> viewNetworkConfig netCfg
|
||||
CRContactInfo u ct cStats customUserProfile -> ttyUser u $ viewContactInfo ct cStats customUserProfile
|
||||
CRGroupInfo u g s -> ttyUser u $ viewGroupInfo g s
|
||||
CRGroupMemberInfo u g m cStats -> ttyUser u $ viewGroupMemberInfo g m cStats
|
||||
|
@ -181,7 +181,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
|||
HSDatabase -> databaseHelpInfo
|
||||
CRWelcome user -> chatWelcome user
|
||||
CRContactsList u cs -> ttyUser u $ viewContactsList cs
|
||||
CRUserContactLink u UserContactLink {connReqContact, autoAccept} -> ttyUser u $ connReqContact_ "Your chat address:" connReqContact <> autoAcceptStatus_ autoAccept
|
||||
CRUserContactLink u UserContactLink {connLinkContact, autoAccept} -> ttyUser u $ connReqContact_ "Your chat address:" connLinkContact <> autoAcceptStatus_ autoAccept
|
||||
CRUserContactLinkUpdated u UserContactLink {autoAccept} -> ttyUser u $ autoAcceptStatus_ autoAccept
|
||||
CRContactRequestRejected u UserContactRequest {localDisplayName = c} -> ttyUser u [ttyContact c <> ": contact request rejected"]
|
||||
CRGroupCreated u g -> ttyUser u $ viewGroupCreated g testView
|
||||
|
@ -200,10 +200,10 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
|||
CRUserProfileNoChange u -> ttyUser u ["user profile did not change"]
|
||||
CRUserPrivacy u u' -> ttyUserPrefix u $ viewUserPrivacy u u'
|
||||
CRVersionInfo info _ _ -> viewVersionInfo logLevel info
|
||||
CRInvitation u cReq _ -> ttyUser u $ viewConnReqInvitation cReq
|
||||
CRInvitation u ccLink _ -> ttyUser u $ viewConnReqInvitation ccLink
|
||||
CRConnectionIncognitoUpdated u c -> ttyUser u $ viewConnectionIncognitoUpdated c
|
||||
CRConnectionUserChanged u c c' nu -> ttyUser u $ viewConnectionUserChanged u c nu c'
|
||||
CRConnectionPlan u connectionPlan -> ttyUser u $ viewConnectionPlan connectionPlan
|
||||
CRConnectionPlan u _ connectionPlan -> ttyUser u $ viewConnectionPlan cfg connectionPlan
|
||||
CRSentConfirmation u _ -> ttyUser u ["confirmation sent!"]
|
||||
CRSentInvitation u _ customUserProfile -> ttyUser u $ viewSentInvitation customUserProfile testView
|
||||
CRSentInvitationToContact u _c customUserProfile -> ttyUser u $ viewSentInvitation customUserProfile testView
|
||||
|
@ -215,7 +215,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
|||
CRContactAlreadyExists u c -> ttyUser u [ttyFullContact c <> ": contact already exists"]
|
||||
CRContactRequestAlreadyAccepted u c -> ttyUser u [ttyFullContact c <> ": sent you a duplicate contact request, but you are already connected, no action needed"]
|
||||
CRBusinessRequestAlreadyAccepted u g -> ttyUser u [ttyFullGroup g <> ": sent you a duplicate connection request, but you are already connected, no action needed"]
|
||||
CRUserContactLinkCreated u cReq -> ttyUser u $ connReqContact_ "Your new chat address is created!" cReq
|
||||
CRUserContactLinkCreated u ccLink -> ttyUser u $ connReqContact_ "Your new chat address is created!" ccLink
|
||||
CRUserContactLinkDeleted u -> ttyUser u viewUserContactLinkDeleted
|
||||
CRUserAcceptedGroupSent u _g _ -> ttyUser u [] -- [ttyGroup' g <> ": joining the group..."]
|
||||
CRGroupLinkConnecting u g _ -> ttyUser u [ttyGroup' g <> ": joining the group..."]
|
||||
|
@ -314,8 +314,8 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
|||
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 cReq mRole -> ttyUser u $ groupLink_ "Group link is created!" g cReq mRole
|
||||
CRGroupLink u g cReq mRole -> ttyUser u $ groupLink_ "Group link:" g cReq mRole
|
||||
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
|
||||
CRGroupLinkDeleted u g -> ttyUser u $ viewGroupLinkDeleted g
|
||||
CRAcceptingGroupJoinRequestMember _ g m -> [ttyFullMember m <> ": accepting request to join group " <> ttyGroup' g <> "..."]
|
||||
CRNoMemberContactCreating u g m -> ttyUser u ["member " <> ttyGroup' g <> " " <> ttyMember m <> " does not have direct connection, creating"]
|
||||
|
@ -911,14 +911,17 @@ viewInvalidConnReq =
|
|||
plain updateStr
|
||||
]
|
||||
|
||||
viewConnReqInvitation :: ConnReqInvitation -> [StyledString]
|
||||
viewConnReqInvitation cReq =
|
||||
viewConnReqInvitation :: CreatedLinkInvitation -> [StyledString]
|
||||
viewConnReqInvitation (CCLink cReq shortLink) =
|
||||
[ "pass this invitation link to your contact (via another channel): ",
|
||||
"",
|
||||
(plain . strEncode) (simplexChatInvitation cReq),
|
||||
plain $ maybe cReqStr strEncode shortLink,
|
||||
"",
|
||||
"and ask them to connect: " <> highlight' "/c <invitation_link_above>"
|
||||
]
|
||||
<> ["The invitation link for old clients: " <> plain cReqStr | isJust shortLink]
|
||||
where
|
||||
cReqStr = strEncode $ simplexChatInvitation cReq
|
||||
|
||||
simplexChatInvitation :: ConnReqInvitation -> ConnReqInvitation
|
||||
simplexChatInvitation (CRInvitationUri crData e2e) = CRInvitationUri crData {crScheme = simplexChat} e2e
|
||||
|
@ -973,21 +976,29 @@ viewForwardPlan count itemIds = maybe [forwardCount] $ \fc -> [confirmation fc,
|
|||
| otherwise = plain $ show len <> " message(s) out of " <> show count <> " can be forwarded"
|
||||
len = length itemIds
|
||||
|
||||
connReqContact_ :: StyledString -> ConnReqContact -> [StyledString]
|
||||
connReqContact_ intro cReq =
|
||||
connReqContact_ :: StyledString -> CreatedLinkContact -> [StyledString]
|
||||
connReqContact_ intro (CCLink cReq shortLink) =
|
||||
[ intro,
|
||||
"",
|
||||
(plain . strEncode) (simplexChatContact cReq),
|
||||
plain $ maybe cReqStr strEncode shortLink,
|
||||
"",
|
||||
"Anybody can send you contact requests with: " <> highlight' "/c <contact_link_above>",
|
||||
"to show it again: " <> highlight' "/sa",
|
||||
"to share with your contacts: " <> highlight' "/profile_address on",
|
||||
"to delete it: " <> highlight' "/da" <> " (accepted contacts will remain connected)"
|
||||
]
|
||||
<> ["The contact link for old clients: " <> plain cReqStr | isJust shortLink]
|
||||
where
|
||||
cReqStr = strEncode $ simplexChatContact cReq
|
||||
|
||||
simplexChatContact :: ConnReqContact -> ConnReqContact
|
||||
simplexChatContact (CRContactUri crData) = CRContactUri crData {crScheme = simplexChat}
|
||||
|
||||
simplexChatContact' :: ConnLinkContact -> ConnLinkContact
|
||||
simplexChatContact' = \case
|
||||
CLFull (CRContactUri crData) -> CLFull $ CRContactUri crData {crScheme = simplexChat}
|
||||
l@(CLShort _) -> l
|
||||
|
||||
autoAcceptStatus_ :: Maybe AutoAccept -> [StyledString]
|
||||
autoAcceptStatus_ = \case
|
||||
Just AutoAccept {businessAddress, acceptIncognito, autoReply} ->
|
||||
|
@ -1000,16 +1011,19 @@ autoAcceptStatus_ = \case
|
|||
| otherwise = ""
|
||||
_ -> ["auto_accept off"]
|
||||
|
||||
groupLink_ :: StyledString -> GroupInfo -> ConnReqContact -> GroupMemberRole -> [StyledString]
|
||||
groupLink_ intro g cReq mRole =
|
||||
groupLink_ :: StyledString -> GroupInfo -> CreatedLinkContact -> GroupMemberRole -> [StyledString]
|
||||
groupLink_ intro g (CCLink cReq shortLink) mRole =
|
||||
[ intro,
|
||||
"",
|
||||
(plain . strEncode) (simplexChatContact cReq),
|
||||
plain $ maybe cReqStr strEncode shortLink,
|
||||
"",
|
||||
"Anybody can connect to you and join group as " <> showRole mRole <> " 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)"
|
||||
]
|
||||
<> ["The group link for old clients: " <> plain cReqStr | isJust shortLink]
|
||||
where
|
||||
cReqStr = strEncode $ simplexChatContact cReq
|
||||
|
||||
viewGroupLinkDeleted :: GroupInfo -> [StyledString]
|
||||
viewGroupLinkDeleted g =
|
||||
|
@ -1405,7 +1419,7 @@ viewContactInfo :: Contact -> Maybe ConnectionStats -> Maybe Profile -> [StyledS
|
|||
viewContactInfo ct@Contact {contactId, profile = LocalProfile {localAlias, contactLink}, activeConn, uiThemes, customData} stats incognitoProfile =
|
||||
["contact ID: " <> sShow contactId]
|
||||
<> maybe [] viewConnectionStats stats
|
||||
<> maybe [] (\l -> ["contact address: " <> (plain . strEncode) (simplexChatContact l)]) contactLink
|
||||
<> maybe [] (\l -> ["contact address: " <> (plain . strEncode) (simplexChatContact' l)]) contactLink
|
||||
<> maybe
|
||||
["you've shared main profile with this contact"]
|
||||
(\p -> ["you've shared incognito profile with this contact: " <> incognitoProfile' p])
|
||||
|
@ -1437,7 +1451,7 @@ viewGroupMemberInfo GroupInfo {groupId} m@GroupMember {groupMemberId, memberProf
|
|||
"member ID: " <> sShow groupMemberId
|
||||
]
|
||||
<> maybe ["member not connected"] viewConnectionStats stats
|
||||
<> maybe [] (\l -> ["contact address: " <> (plain . strEncode) (simplexChatContact l)]) contactLink
|
||||
<> maybe [] (\l -> ["contact address: " <> (plain . strEncode) (simplexChatContact' l)]) contactLink
|
||||
<> ["alias: " <> plain localAlias | localAlias /= ""]
|
||||
<> [viewConnectionVerified (memberSecurityCode m) | isJust stats]
|
||||
<> maybe [] (\ac -> [viewPeerChatVRange (peerChatVRange ac)]) activeConn
|
||||
|
@ -1663,21 +1677,24 @@ viewConnectionIncognitoUpdated PendingContactConnection {pccConnId, customUserPr
|
|||
| otherwise = ["connection " <> sShow pccConnId <> " changed to non incognito"]
|
||||
|
||||
viewConnectionUserChanged :: User -> PendingContactConnection -> User -> PendingContactConnection -> [StyledString]
|
||||
viewConnectionUserChanged User {localDisplayName = n} PendingContactConnection {pccConnId, connReqInv} User {localDisplayName = n'} PendingContactConnection {connReqInv = connReqInv'} =
|
||||
case (connReqInv, connReqInv') of
|
||||
(Just cReqInv, Just cReqInv')
|
||||
| cReqInv /= cReqInv' -> [userChangedStr <> ", new link:"] <> newLink cReqInv'
|
||||
viewConnectionUserChanged User {localDisplayName = n} PendingContactConnection {pccConnId, connLinkInv} User {localDisplayName = n'} PendingContactConnection {connLinkInv = connLinkInv'} =
|
||||
case (connLinkInv, connLinkInv') of
|
||||
(Just ccLink, Just ccLink')
|
||||
| ccLink /= ccLink' -> [userChangedStr <> ", new link:"] <> newLink ccLink'
|
||||
_ -> [userChangedStr]
|
||||
where
|
||||
userChangedStr = "connection " <> sShow pccConnId <> " changed from user " <> plain n <> " to user " <> plain n'
|
||||
newLink cReqInv =
|
||||
newLink (CCLink cReq shortLink) =
|
||||
[ "",
|
||||
(plain . strEncode) (simplexChatInvitation cReqInv),
|
||||
plain $ maybe cReqStr strEncode shortLink,
|
||||
""
|
||||
]
|
||||
<> ["The invitation link for old clients: " <> plain cReqStr | isJust shortLink]
|
||||
where
|
||||
cReqStr = strEncode $ simplexChatInvitation cReq
|
||||
|
||||
viewConnectionPlan :: ConnectionPlan -> [StyledString]
|
||||
viewConnectionPlan = \case
|
||||
viewConnectionPlan :: ChatConfig -> ConnectionPlan -> [StyledString]
|
||||
viewConnectionPlan ChatConfig {logLevel, testView} = \case
|
||||
CPInvitationLink ilp -> case ilp of
|
||||
ILPOk -> [invLink "ok to connect"]
|
||||
ILPOwnLink -> [invLink "own link"]
|
||||
|
@ -1716,6 +1733,7 @@ viewConnectionPlan = \case
|
|||
grpOrBiz GroupInfo {businessChat} = case businessChat of
|
||||
Just _ -> "business"
|
||||
Nothing -> "group"
|
||||
CPError e -> viewChatError False logLevel testView e
|
||||
|
||||
viewContactUpdated :: Contact -> Contact -> [StyledString]
|
||||
viewContactUpdated
|
||||
|
@ -2138,8 +2156,8 @@ viewChatError isCmd logLevel testView = \case
|
|||
CEChatNotStarted -> ["error: chat not started"]
|
||||
CEChatNotStopped -> ["error: chat not stopped"]
|
||||
CEChatStoreChanged -> ["error: chat store changed, please restart chat"]
|
||||
CEConnectionPlan connectionPlan -> viewConnectionPlan connectionPlan
|
||||
CEInvalidConnReq -> viewInvalidConnReq
|
||||
CEUnsupportedConnReq -> [ "", "Connection link is not supported by the your app version, please ugrade it.", plain updateStr]
|
||||
CEInvalidChatMessage Connection {connId} msgMeta_ msg e ->
|
||||
[ plain $
|
||||
("chat message error: " <> e <> " (" <> T.unpack (T.take 120 msg) <> ")")
|
||||
|
|
|
@ -186,6 +186,7 @@ testCfg =
|
|||
defaultChatConfig
|
||||
{ agentConfig = testAgentCfg,
|
||||
showReceipts = False,
|
||||
shortLinkPresetServers = ["smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=@localhost:7001"],
|
||||
testView = True,
|
||||
tbqSize = 16
|
||||
}
|
||||
|
@ -290,8 +291,8 @@ startTestChat_ TestParams {printOutput} db cfg opts@ChatOpts {maintenance} user
|
|||
ct <- newChatTerminal t opts
|
||||
cc <- newChatController db (Just user) cfg opts False
|
||||
void $ execChatCommand' (SetTempFolder "tests/tmp/tmp") `runReaderT` cc
|
||||
chatAsync <- async . runSimplexChat opts user cc $ \_u cc' -> runChatTerminal ct cc' opts
|
||||
atomically . unless maintenance $ readTVar (agentAsync cc) >>= \a -> when (isNothing a) retry
|
||||
chatAsync <- async $ runSimplexChat opts user cc $ \_u cc' -> runChatTerminal ct cc' opts
|
||||
unless maintenance $ atomically $ readTVar (agentAsync cc) >>= \a -> when (isNothing a) retry
|
||||
termQ <- newTQueueIO
|
||||
termAsync <- async $ readTerminalOutput t termQ
|
||||
pure TestCC {chatController = cc, virtualTerminal = t, chatAsync, termAsync, termQ, printOutput}
|
||||
|
@ -393,14 +394,14 @@ withTmpFiles =
|
|||
|
||||
testChatN :: HasCallStack => ChatConfig -> ChatOpts -> [Profile] -> (HasCallStack => [TestCC] -> IO ()) -> TestParams -> IO ()
|
||||
testChatN cfg opts ps test params =
|
||||
bracket (getTestCCs (zip ps [1 ..]) []) entTests test
|
||||
bracket (getTestCCs $ zip ps [1 ..]) endTests test
|
||||
where
|
||||
getTestCCs :: [(Profile, Int)] -> [TestCC] -> IO [TestCC]
|
||||
getTestCCs [] tcs = pure tcs
|
||||
getTestCCs ((p, db) : envs') tcs = (:) <$> createTestChat params cfg opts (show db) p <*> getTestCCs envs' tcs
|
||||
entTests tcs = do
|
||||
concurrentlyN_ $ map (<// 100000) tcs
|
||||
concurrentlyN_ $ map (stopTestChat params) tcs
|
||||
getTestCCs :: [(Profile, Int)] -> IO [TestCC]
|
||||
getTestCCs [] = pure []
|
||||
getTestCCs ((p, db) : envs') = (:) <$> createTestChat params cfg opts (show db) p <*> getTestCCs envs'
|
||||
endTests tcs = do
|
||||
mapConcurrently_ (<// 100000) tcs
|
||||
mapConcurrently_ (stopTestChat params) tcs
|
||||
|
||||
(<//) :: HasCallStack => TestCC -> Int -> Expectation
|
||||
(<//) cc t = timeout t (getTermLine cc) `shouldReturn` Nothing
|
||||
|
@ -481,7 +482,7 @@ smpServerCfg =
|
|||
msgQueueQuota = 16,
|
||||
maxJournalMsgCount = 24,
|
||||
maxJournalStateLines = 4,
|
||||
queueIdBytes = 12,
|
||||
queueIdBytes = 24,
|
||||
msgIdBytes = 6,
|
||||
serverStoreCfg = ASSCfg SQSMemory SMSMemory $ SSCMemory Nothing,
|
||||
storeNtfsFile = Nothing,
|
||||
|
|
|
@ -1113,6 +1113,8 @@ testSendMultiManyBatches =
|
|||
DB.query db "SELECT count(1) FROM chat_items WHERE chat_item_id > ?" (Only msgIdBob) :: IO [[Int]]
|
||||
bobItemsCount `shouldBe` [[300]]
|
||||
|
||||
threadDelay 1000000
|
||||
|
||||
testGetSetSMPServers :: HasCallStack => TestParams -> IO ()
|
||||
testGetSetSMPServers =
|
||||
testChat aliceProfile $
|
||||
|
|
|
@ -101,6 +101,11 @@ chatProfileTests = do
|
|||
it "files & media" testGroupPrefsFilesForRole
|
||||
it "SimpleX links" testGroupPrefsSimplexLinksForRole
|
||||
it "set user, contact and group UI theme" testSetUITheme
|
||||
describe "short links" $ do
|
||||
it "should connect via one-time inviation" testShortLinkInvitation
|
||||
it "should plan and connect via one-time inviation" testPlanShortLinkInvitation
|
||||
it "should connect via contact address" testShortLinkContactAddress
|
||||
it "should join group" testShortLinkJoinGroup
|
||||
|
||||
testUpdateProfile :: HasCallStack => TestParams -> IO ()
|
||||
testUpdateProfile =
|
||||
|
@ -2583,3 +2588,162 @@ testSetUITheme =
|
|||
groupInfo a = do
|
||||
a <## "group ID: 1"
|
||||
a <## "current members: 1"
|
||||
|
||||
testShortLinkInvitation :: HasCallStack => TestParams -> IO ()
|
||||
testShortLinkInvitation =
|
||||
testChat2 aliceProfile bobProfile $ \alice bob -> do
|
||||
alice ##> "/c short"
|
||||
inv <- getShortInvitation alice
|
||||
bob ##> ("/c " <> inv)
|
||||
bob <## "confirmation sent!"
|
||||
concurrently_
|
||||
(alice <## "bob (Bob): contact is connected")
|
||||
(bob <## "alice (Alice): contact is connected")
|
||||
alice #> "@bob hi"
|
||||
bob <# "alice> hi"
|
||||
bob #> "@alice hey"
|
||||
alice <# "bob> hey"
|
||||
|
||||
testPlanShortLinkInvitation :: HasCallStack => TestParams -> IO ()
|
||||
testPlanShortLinkInvitation =
|
||||
testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do
|
||||
alice ##> "/c short"
|
||||
inv <- getShortInvitation alice
|
||||
alice ##> ("/_connect plan 1 " <> inv)
|
||||
alice <## "invitation link: own link"
|
||||
alice ##> ("/_connect plan 1 " <> slSimplexScheme inv)
|
||||
alice <## "invitation link: own link"
|
||||
bob ##> ("/_connect plan 1 " <> inv)
|
||||
bob <## "invitation link: ok to connect"
|
||||
-- nobody else can connect
|
||||
cath ##> ("/_connect plan 1 " <> inv)
|
||||
cath <##. "error: connection authorization failed"
|
||||
cath ##> ("/c " <> inv)
|
||||
cath <##. "error: connection authorization failed"
|
||||
-- bob can retry "plan"
|
||||
bob ##> ("/_connect plan 1 " <> inv)
|
||||
bob <## "invitation link: ok to connect"
|
||||
-- with simplex: scheme too
|
||||
bob ##> ("/_connect plan 1 " <> slSimplexScheme inv)
|
||||
bob <## "invitation link: ok to connect"
|
||||
bob ##> ("/c " <> inv)
|
||||
bob <## "confirmation sent!"
|
||||
concurrently_
|
||||
(alice <## "bob (Bob): contact is connected")
|
||||
(bob <## "alice (Alice): contact is connected")
|
||||
alice #> "@bob hi"
|
||||
bob <# "alice> hi"
|
||||
bob #> "@alice hey"
|
||||
alice <# "bob> hey"
|
||||
bob ##> ("/_connect plan 1 " <> inv)
|
||||
bob <##. "error: connection authorization failed"
|
||||
alice ##> ("/_connect plan 1 " <> inv)
|
||||
alice <##. "error: connection authorization failed" -- short_link_inv and conn_req_inv are removed after connection
|
||||
|
||||
slSimplexScheme :: String -> String
|
||||
slSimplexScheme sl = T.unpack $ T.replace "https://localhost/" "simplex:/" (T.pack sl) <> "?h=localhost"
|
||||
|
||||
testShortLinkContactAddress :: HasCallStack => TestParams -> IO ()
|
||||
testShortLinkContactAddress =
|
||||
testChat4 aliceProfile bobProfile cathProfile danProfile $ \alice bob cath dan -> do
|
||||
alice ##> "/ad short"
|
||||
(shortLink, fullLink) <- getShortContactLink alice True
|
||||
alice ##> ("/_connect plan 1 " <> shortLink)
|
||||
alice <## "contact address: own address"
|
||||
alice ##> ("/_connect plan 1 " <> slSimplexScheme shortLink)
|
||||
alice <## "contact address: own address"
|
||||
alice ##> ("/_connect plan 1 " <> fullLink)
|
||||
alice <## "contact address: own address"
|
||||
(alice, bob) `connectVia` shortLink
|
||||
bob ##> ("/_connect plan 1 " <> slSimplexScheme shortLink)
|
||||
bob <## "contact address: known contact alice"
|
||||
bob <## "use @alice <message> to send messages"
|
||||
(alice, cath) `connectVia` slSimplexScheme shortLink
|
||||
cath ##> ("/_connect plan 1 " <> shortLink)
|
||||
cath <## "contact address: known contact alice"
|
||||
cath <## "use @alice <message> to send messages"
|
||||
(alice, dan) `connectVia` fullLink
|
||||
where
|
||||
(alice, cc) `connectVia` cLink = do
|
||||
name <- userName cc
|
||||
sName <- showName cc
|
||||
cc ##> ("/_connect plan 1 " <> cLink)
|
||||
cc <## "contact address: ok to connect"
|
||||
cc ##> ("/c " <> cLink)
|
||||
alice <#? cc
|
||||
alice ##> ("/ac " <> name)
|
||||
alice <## (sName <> ": accepting contact request, you can send messages to contact")
|
||||
concurrently_
|
||||
(cc <## "alice (Alice): contact is connected")
|
||||
(alice <## (sName <> ": contact is connected"))
|
||||
cc ##> ("/_connect plan 1 " <> cLink)
|
||||
cc <## "contact address: known contact alice"
|
||||
cc <## "use @alice <message> to send messages"
|
||||
|
||||
testShortLinkJoinGroup :: HasCallStack => TestParams -> IO ()
|
||||
testShortLinkJoinGroup =
|
||||
testChat4 aliceProfile bobProfile cathProfile danProfile $ \alice bob cath dan -> do
|
||||
threadDelay 100000
|
||||
alice ##> "/ad short" -- create the address to test that it can co-exist with group link
|
||||
_ <- getShortContactLink alice True
|
||||
alice ##> "/g team"
|
||||
alice <## "group #team is created"
|
||||
alice <## "to add members use /a team <name> or /create link #team"
|
||||
alice ##> "/create link #team short"
|
||||
(shortLink, fullLink) <- getShortGroupLink alice "team" GRMember True
|
||||
alice ##> ("/_connect plan 1 " <> shortLink)
|
||||
alice <## "group link: own link for group #team"
|
||||
alice ##> ("/_connect plan 1 " <> slSimplexScheme shortLink)
|
||||
alice <## "group link: own link for group #team"
|
||||
alice ##> ("/_connect plan 1 " <> fullLink)
|
||||
alice <## "group link: own link for group #team"
|
||||
joinGroup alice bob shortLink
|
||||
bob ##> ("/_connect plan 1 " <> shortLink)
|
||||
bob <## "group link: known group #team"
|
||||
bob <## "use #team <message> to send messages"
|
||||
bob ##> ("/_connect plan 1 " <> slSimplexScheme shortLink)
|
||||
bob <## "group link: known group #team"
|
||||
bob <## "use #team <message> to send messages"
|
||||
joinGroup alice cath $ slSimplexScheme shortLink
|
||||
concurrentlyN_
|
||||
[ do
|
||||
bob <## "#team: alice added cath (Catherine) to the group (connecting...)"
|
||||
bob <## "#team: new member cath is connected",
|
||||
cath <## "#team: member bob (Bob) is connected"
|
||||
]
|
||||
cath ##> ("/_connect plan 1 " <> slSimplexScheme shortLink)
|
||||
cath <## "group link: known group #team"
|
||||
cath <## "use #team <message> to send messages"
|
||||
cath ##> ("/_connect plan 1 " <> shortLink)
|
||||
cath <## "group link: known group #team"
|
||||
cath <## "use #team <message> to send messages"
|
||||
joinGroup alice dan fullLink
|
||||
concurrentlyN_
|
||||
[ do
|
||||
bob <## "#team: alice added dan (Daniel) to the group (connecting...)"
|
||||
bob <## "#team: new member dan is connected",
|
||||
do
|
||||
cath <## "#team: alice added dan (Daniel) to the group (connecting...)"
|
||||
cath <## "#team: new member dan is connected",
|
||||
do
|
||||
dan <## "#team: member bob (Bob) is connected"
|
||||
dan <## "#team: member cath (Catherine) is connected"
|
||||
]
|
||||
dan ##> ("/_connect plan 1 " <> fullLink)
|
||||
dan <## "group link: known group #team"
|
||||
dan <## "use #team <message> to send messages"
|
||||
where
|
||||
joinGroup alice cc link = do
|
||||
name <- userName cc
|
||||
sName <- showName cc
|
||||
cc ##> ("/_connect plan 1 " <> link)
|
||||
cc <## "group link: ok to connect"
|
||||
cc ##> ("/c " <> link)
|
||||
cc <## "connection request sent!"
|
||||
alice <## (sName <> ": accepting request to join group #team...")
|
||||
concurrentlyN_
|
||||
[ alice <## ("#team: " <> name <> " joined the group"),
|
||||
do
|
||||
cc <## "#team: joining the group..."
|
||||
cc <## "#team: you joined the group"
|
||||
]
|
||||
|
|
|
@ -11,7 +11,7 @@ module ChatTests.Utils where
|
|||
import ChatClient
|
||||
import ChatTests.DBUtils
|
||||
import Control.Concurrent (threadDelay)
|
||||
import Control.Concurrent.Async (concurrently_)
|
||||
import Control.Concurrent.Async (concurrently_, mapConcurrently_)
|
||||
import Control.Concurrent.STM
|
||||
import Control.Monad (unless, when)
|
||||
import Control.Monad.Except (runExceptT)
|
||||
|
@ -424,7 +424,7 @@ getInAnyOrder f cc ls = do
|
|||
cc <# line = (dropTime <$> getTermLine cc) `shouldReturn` line
|
||||
|
||||
(*<#) :: HasCallStack => [TestCC] -> String -> Expectation
|
||||
ccs *<# line = concurrentlyN_ $ map (<# line) ccs
|
||||
ccs *<# line = mapConcurrently_ (<# line) ccs
|
||||
|
||||
(?<#) :: HasCallStack => TestCC -> String -> Expectation
|
||||
cc ?<# line = (dropTime <$> getTermLine cc) `shouldReturn` "i " <> line
|
||||
|
@ -502,14 +502,27 @@ dropPartialReceipt_ msg = case splitAt 2 msg of
|
|||
_ -> Nothing
|
||||
|
||||
getInvitation :: HasCallStack => TestCC -> IO String
|
||||
getInvitation cc = do
|
||||
getInvitation = getInvitation_ False
|
||||
|
||||
getShortInvitation :: HasCallStack => TestCC -> IO String
|
||||
getShortInvitation = getInvitation_ True
|
||||
|
||||
getInvitation_ :: HasCallStack => Bool -> TestCC -> IO String
|
||||
getInvitation_ short cc = do
|
||||
cc <## "pass this invitation link to your contact (via another channel):"
|
||||
cc <## ""
|
||||
inv <- getTermLine cc
|
||||
cc <## ""
|
||||
cc <## "and ask them to connect: /c <invitation_link_above>"
|
||||
when short $ cc <##. "The invitation link for old clients: https://simplex.chat/invitation#"
|
||||
pure inv
|
||||
|
||||
getShortContactLink :: HasCallStack => TestCC -> Bool -> IO (String, String)
|
||||
getShortContactLink cc created = do
|
||||
shortLink <- getContactLink cc created
|
||||
fullLink <- dropLinePrefix "The contact link for old clients: " =<< getTermLine cc
|
||||
pure (shortLink, fullLink)
|
||||
|
||||
getContactLink :: HasCallStack => TestCC -> Bool -> IO String
|
||||
getContactLink cc created = do
|
||||
cc <## if created then "Your new chat address is created!" else "Your chat address:"
|
||||
|
@ -522,6 +535,17 @@ getContactLink cc created = do
|
|||
cc <## "to delete it: /da (accepted contacts will remain connected)"
|
||||
pure link
|
||||
|
||||
dropLinePrefix :: String -> String -> IO String
|
||||
dropLinePrefix line s
|
||||
| line `isPrefixOf` s = pure $ drop (length line) s
|
||||
| otherwise = error $ "expected to start from: " <> line <> ", got: " <> s
|
||||
|
||||
getShortGroupLink :: HasCallStack => TestCC -> String -> GroupMemberRole -> Bool -> IO (String, String)
|
||||
getShortGroupLink cc gName mRole created = do
|
||||
shortLink <- getGroupLink cc gName mRole created
|
||||
fullLink <- dropLinePrefix "The group link for old clients: " =<< getTermLine cc
|
||||
pure (shortLink, fullLink)
|
||||
|
||||
getGroupLink :: HasCallStack => TestCC -> String -> GroupMemberRole -> Bool -> IO String
|
||||
getGroupLink cc gName mRole created = do
|
||||
cc <## if created then "Group link is created!" else "Group link:"
|
||||
|
|
|
@ -19,6 +19,7 @@ import qualified Data.List.NonEmpty as L
|
|||
import Simplex.Chat
|
||||
import Simplex.Chat.Controller (ChatConfig (..), PresetServers (..))
|
||||
import Simplex.Chat.Operators
|
||||
import Simplex.Chat.Operators.Presets
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.FileTransfer.Client.Presets (defaultXFTPServers)
|
||||
import Simplex.Messaging.Agent.Env.SQLite (ServerRoles (..), allRoles)
|
||||
|
|
|
@ -35,7 +35,7 @@ queue =
|
|||
{ smpServer = srv,
|
||||
senderId = EntityId "\223\142z\251",
|
||||
dhPublicKey = "MCowBQYDK2VuAyEAjiswwI3O/NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o=",
|
||||
sndSecure = False
|
||||
queueMode = Nothing
|
||||
}
|
||||
|
||||
connReqData :: ConnReqUriData
|
||||
|
@ -201,7 +201,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
|
|||
"{\"v\":\"1\",\"event\":\"x.msg.deleted\",\"params\":{}}"
|
||||
#==# XMsgDeleted
|
||||
it "x.file" $
|
||||
"{\"v\":\"1\",\"event\":\"x.file\",\"params\":{\"file\":{\"fileConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}"
|
||||
"{\"v\":\"1\",\"event\":\"x.file\",\"params\":{\"file\":{\"fileConnReq\":\"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\",\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}"
|
||||
#==# XFile FileInvitation {fileName = "photo.jpg", fileSize = 12345, fileDigest = Nothing, fileConnReq = Just testConnReq, fileInline = Nothing, fileDescr = Nothing}
|
||||
it "x.file without file invitation" $
|
||||
"{\"v\":\"1\",\"event\":\"x.file\",\"params\":{\"file\":{\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}"
|
||||
|
@ -210,7 +210,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
|
|||
"{\"v\":\"1\",\"event\":\"x.file.acpt\",\"params\":{\"fileName\":\"photo.jpg\"}}"
|
||||
#==# XFileAcpt "photo.jpg"
|
||||
it "x.file.acpt.inv" $
|
||||
"{\"v\":\"1\",\"event\":\"x.file.acpt.inv\",\"params\":{\"msgId\":\"AQIDBA==\",\"fileName\":\"photo.jpg\",\"fileConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}"
|
||||
"{\"v\":\"1\",\"event\":\"x.file.acpt.inv\",\"params\":{\"msgId\":\"AQIDBA==\",\"fileName\":\"photo.jpg\",\"fileConnReq\":\"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\"}}"
|
||||
#==# XFileAcptInv (SharedMsgId "\1\2\3\4") (Just testConnReq) "photo.jpg"
|
||||
it "x.file.acpt.inv" $
|
||||
"{\"v\":\"1\",\"event\":\"x.file.acpt.inv\",\"params\":{\"msgId\":\"AQIDBA==\",\"fileName\":\"photo.jpg\"}}"
|
||||
|
@ -237,10 +237,10 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
|
|||
"{\"v\":\"1\",\"event\":\"x.contact\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}"
|
||||
==# XContact testProfile Nothing
|
||||
it "x.grp.inv" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}}}}"
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}}}}"
|
||||
#==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, business = Nothing, groupLinkId = Nothing, groupSize = Nothing}
|
||||
it "x.grp.inv with group link id" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}, \"groupLinkId\":\"AQIDBA==\"}}}"
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}, \"groupLinkId\":\"AQIDBA==\"}}}"
|
||||
#==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, business = Nothing, groupLinkId = Just $ GroupLinkId "\1\2\3\4", groupSize = Nothing}
|
||||
it "x.grp.acpt without incognito profile" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.acpt\",\"params\":{\"memberId\":\"AQIDBA==\"}}"
|
||||
|
@ -261,16 +261,16 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
|
|||
"{\"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\"}}}}}}"
|
||||
#==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} (Just MemberRestrictions {restriction = MRSBlocked})
|
||||
it "x.grp.mem.inv" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}"
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"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\"}}}"
|
||||
#==# XGrpMemInv (MemberId "\1\2\3\4") IntroInvitation {groupConnReq = testConnReq, directConnReq = Just testConnReq}
|
||||
it "x.grp.mem.inv w/t directConnReq" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}"
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"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\"}}}"
|
||||
#==# XGrpMemInv (MemberId "\1\2\3\4") IntroInvitation {groupConnReq = testConnReq, directConnReq = Nothing}
|
||||
it "x.grp.mem.fwd" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-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-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-14\",\"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-14\",\"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\"}}}}}"
|
||||
|
@ -291,10 +291,10 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
|
|||
"{\"v\":\"1\",\"event\":\"x.grp.del\",\"params\":{}}"
|
||||
==# XGrpDel
|
||||
it "x.grp.direct.inv" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.direct.inv\",\"params\":{\"connReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\", \"content\":{\"text\":\"hello\",\"type\":\"text\"}}}"
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.direct.inv\",\"params\":{\"connReq\":\"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\", \"content\":{\"text\":\"hello\",\"type\":\"text\"}}}"
|
||||
#==# XGrpDirectInv testConnReq (Just $ MCText "hello")
|
||||
it "x.grp.direct.inv without content" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.direct.inv\",\"params\":{\"connReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}"
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.direct.inv\",\"params\":{\"connReq\":\"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\"}}"
|
||||
#==# XGrpDirectInv testConnReq Nothing
|
||||
-- it "x.grp.msg.forward"
|
||||
-- $ "{\"v\":\"1\",\"event\":\"x.grp.msg.forward\",\"params\":{\"msgForward\":{\"memberId\":\"AQIDBA==\",\"msg\":\"{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}\",\"msgTs\":\"1970-01-01T00:00:01.000000001Z\"}}}"
|
||||
|
|
|
@ -17,6 +17,30 @@
|
|||
},
|
||||
{
|
||||
"/": "/invitation"
|
||||
},
|
||||
{
|
||||
"/": "/a/*"
|
||||
},
|
||||
{
|
||||
"/": "/a"
|
||||
},
|
||||
{
|
||||
"/": "/c/*"
|
||||
},
|
||||
{
|
||||
"/": "/c"
|
||||
},
|
||||
{
|
||||
"/": "/g/*"
|
||||
},
|
||||
{
|
||||
"/": "/g"
|
||||
},
|
||||
{
|
||||
"/": "/i/*"
|
||||
},
|
||||
{
|
||||
"/": "/i"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue