From bb0482104c234c08bc4d62b520d514e7c0c11812 Mon Sep 17 00:00:00 2001 From: JRoberts <8711996+jr-simplex@users.noreply.github.com> Date: Thu, 5 Jan 2023 20:38:31 +0400 Subject: [PATCH] core, ios, android: add UserId to api commands (#1696) --- .../java/chat/simplex/app/model/SimpleXAPI.kt | 158 ++++++++++++------ apps/ios/Shared/Model/SimpleXAPI.swift | 63 +++++-- .../ios/SimpleX NSE/NotificationService.swift | 6 +- apps/ios/SimpleXChat/APITypes.swift | 84 +++++----- apps/ios/SimpleXChat/ChatTypes.swift | 2 +- apps/simplex-bot-advanced/Main.hs | 4 +- apps/simplex-chat/Server.hs | 4 +- src/Simplex/Chat.hs | 107 +++++++++--- src/Simplex/Chat/Controller.hs | 67 +++++--- src/Simplex/Chat/View.hs | 1 + tests/ChatTests.hs | 34 ++-- 11 files changed, 341 insertions(+), 189 deletions(-) diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt index e9864edfa1..cb92dc2f74 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt @@ -33,8 +33,6 @@ import kotlinx.coroutines.* import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.serialization.* -import kotlinx.serialization.builtins.ListSerializer -import kotlinx.serialization.builtins.nullable import kotlinx.serialization.json.* import java.util.Date @@ -405,7 +403,11 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a } suspend fun apiGetChats(): List { - val r = sendCmd(CC.ApiGetChats()) + val userId = chatModel.currentUser.value?.userId ?: run { + Log.e(TAG, "apiGetChats: no current user") + return emptyList() + } + val r = sendCmd(CC.ApiGetChats(userId)) if (r is CR.ApiChats) return r.chats Log.e(TAG, "failed getting the list of chats: ${r.responseType} ${r.details}") AlertManager.shared.showAlertMsg(generalGetString(R.string.failed_to_parse_chats_title), generalGetString(R.string.contact_developers)) @@ -449,14 +451,22 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a } private suspend fun getUserSMPServers(): Pair, List>? { - val r = sendCmd(CC.GetUserSMPServers()) + val userId = chatModel.currentUser.value?.userId ?: run { + Log.e(TAG, "getUserSMPServers: no current user") + return null + } + val r = sendCmd(CC.APIGetUserSMPServers(userId)) if (r is CR.UserSMPServers) return r.smpServers to r.presetSMPServers Log.e(TAG, "getUserSMPServers bad response: ${r.responseType} ${r.details}") return null } suspend fun setUserSMPServers(smpServers: List): Boolean { - val r = sendCmd(CC.SetUserSMPServers(smpServers)) + val userId = chatModel.currentUser.value?.userId ?: run { + Log.e(TAG, "setUserSMPServers: no current user") + return false + } + val r = sendCmd(CC.APISetUserSMPServers(userId, smpServers)) return when (r) { is CR.CmdOk -> true else -> { @@ -482,13 +492,15 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a } suspend fun getChatItemTTL(): ChatItemTTL { - val r = sendCmd(CC.APIGetChatItemTTL()) + val userId = chatModel.currentUser.value?.userId ?: run { throw Exception("getChatItemTTL: no current user") } + val r = sendCmd(CC.APIGetChatItemTTL(userId)) if (r is CR.ChatItemTTL) return ChatItemTTL.fromSeconds(r.chatItemTTL) throw Exception("failed to get chat item TTL: ${r.responseType} ${r.details}") } suspend fun setChatItemTTL(chatItemTTL: ChatItemTTL) { - val r = sendCmd(CC.APISetChatItemTTL(chatItemTTL.seconds)) + val userId = chatModel.currentUser.value?.userId ?: run { throw Exception("setChatItemTTL: no current user") } + val r = sendCmd(CC.APISetChatItemTTL(userId, chatItemTTL.seconds)) if (r is CR.CmdOk) return throw Exception("failed to set chat item TTL: ${r.responseType} ${r.details}") } @@ -587,7 +599,11 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a suspend fun apiAddContact(): String? { - val r = sendCmd(CC.AddContact()) + val userId = chatModel.currentUser.value?.userId ?: run { + Log.e(TAG, "apiAddContact: no current user") + return null + } + val r = sendCmd(CC.APIAddContact(userId)) return when (r) { is CR.Invitation -> r.connReqInvitation else -> { @@ -600,7 +616,11 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a } suspend fun apiConnect(connReq: String): Boolean { - val r = sendCmd(CC.Connect(connReq)) + val userId = chatModel.currentUser.value?.userId ?: run { + Log.e(TAG, "apiConnect: no current user") + return false + } + val r = sendCmd(CC.APIConnect(userId, connReq)) when { r is CR.SentConfirmation || r is CR.SentInvitation -> return true r is CR.ContactAlreadyExists -> { @@ -663,14 +683,22 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a } suspend fun apiListContacts(): List? { - val r = sendCmd(CC.ListContacts()) + val userId = chatModel.currentUser.value?.userId ?: run { + Log.e(TAG, "apiListContacts: no current user") + return null + } + val r = sendCmd(CC.ApiListContacts(userId)) if (r is CR.ContactsList) return r.contacts Log.e(TAG, "apiListContacts bad response: ${r.responseType} ${r.details}") return null } suspend fun apiUpdateProfile(profile: Profile): Profile? { - val r = sendCmd(CC.ApiUpdateProfile(profile)) + val userId = chatModel.currentUser.value?.userId ?: run { + Log.e(TAG, "apiUpdateProfile: no current user") + return null + } + val r = sendCmd(CC.ApiUpdateProfile(userId, profile)) if (r is CR.UserProfileNoChange) return profile if (r is CR.UserProfileUpdated) return r.toProfile Log.e(TAG, "apiUpdateProfile bad response: ${r.responseType} ${r.details}") @@ -706,7 +734,11 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a } suspend fun apiCreateUserAddress(): String? { - val r = sendCmd(CC.CreateMyAddress()) + val userId = chatModel.currentUser.value?.userId ?: run { + Log.e(TAG, "apiCreateUserAddress: no current user") + return null + } + val r = sendCmd(CC.ApiCreateMyAddress(userId)) return when (r) { is CR.UserContactLinkCreated -> r.connReqContact else -> { @@ -719,14 +751,22 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a } suspend fun apiDeleteUserAddress(): Boolean { - val r = sendCmd(CC.DeleteMyAddress()) + val userId = chatModel.currentUser.value?.userId ?: run { + Log.e(TAG, "apiDeleteUserAddress: no current user") + return false + } + val r = sendCmd(CC.ApiDeleteMyAddress(userId)) if (r is CR.UserContactLinkDeleted) return true Log.e(TAG, "apiDeleteUserAddress bad response: ${r.responseType} ${r.details}") return false } private suspend fun apiGetUserAddress(): UserContactLinkRec? { - val r = sendCmd(CC.ShowMyAddress()) + val userId = chatModel.currentUser.value?.userId ?: run { + Log.e(TAG, "apiGetUserAddress: no current user") + return null + } + val r = sendCmd(CC.ApiShowMyAddress(userId)) if (r is CR.UserContactLink) return r.contactLink if (r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore && r.chatError.storeError is StoreError.UserContactLinkNotFound) { @@ -737,7 +777,11 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a } suspend fun userAddressAutoAccept(autoAccept: AutoAccept?): UserContactLinkRec? { - val r = sendCmd(CC.AddressAutoAccept(autoAccept)) + val userId = chatModel.currentUser.value?.userId ?: run { + Log.e(TAG, "userAddressAutoAccept: no current user") + return null + } + val r = sendCmd(CC.ApiAddressAutoAccept(userId, autoAccept)) if (r is CR.UserContactLinkUpdated) return r.contactLink if (r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore && r.chatError.storeError is StoreError.UserContactLinkNotFound) { @@ -856,7 +900,11 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a } suspend fun apiNewGroup(p: GroupProfile): GroupInfo? { - val r = sendCmd(CC.NewGroup(p)) + val userId = chatModel.currentUser.value?.userId ?: run { + Log.e(TAG, "apiNewGroup: no current user") + return null + } + val r = sendCmd(CC.ApiNewGroup(userId, p)) if (r is CR.GroupCreated) return r.groupInfo Log.e(TAG, "apiNewGroup bad response: ${r.responseType} ${r.details}") return null @@ -1549,12 +1597,12 @@ sealed class CC { class ApiImportArchive(val config: ArchiveConfig): CC() class ApiDeleteStorage: CC() class ApiStorageEncryption(val config: DBEncryptionConfig): CC() - class ApiGetChats: CC() + class ApiGetChats(val userId: Long): CC() class ApiGetChat(val type: ChatType, val id: Long, val pagination: ChatPagination, val search: String = ""): CC() class ApiSendMessage(val type: ChatType, val id: Long, val file: String?, val quotedItemId: Long?, val mc: MsgContent, val live: Boolean): CC() class ApiUpdateChatItem(val type: ChatType, val id: Long, val itemId: Long, val mc: MsgContent, val live: Boolean): CC() class ApiDeleteChatItem(val type: ChatType, val id: Long, val itemId: Long, val mode: CIDeleteMode): CC() - class NewGroup(val groupProfile: GroupProfile): CC() + class ApiNewGroup(val userId: Long, val groupProfile: GroupProfile): CC() class ApiAddMember(val groupId: Long, val contactId: Long, val memberRole: GroupMemberRole): CC() class ApiJoinGroup(val groupId: Long): CC() class ApiMemberRole(val groupId: Long, val memberId: Long, val memberRole: GroupMemberRole): CC() @@ -1565,11 +1613,11 @@ sealed class CC { class APICreateGroupLink(val groupId: Long): CC() class APIDeleteGroupLink(val groupId: Long): CC() class APIGetGroupLink(val groupId: Long): CC() - class GetUserSMPServers: CC() - class SetUserSMPServers(val smpServers: List): CC() + class APIGetUserSMPServers(val userId: Long): CC() + class APISetUserSMPServers(val userId: Long, val smpServers: List): CC() class TestSMPServer(val smpServer: String): CC() - class APISetChatItemTTL(val seconds: Long?): CC() - class APIGetChatItemTTL: CC() + class APISetChatItemTTL(val userId: Long, val seconds: Long?): CC() + class APIGetChatItemTTL(val userId: Long): CC() class APISetNetworkConfig(val networkConfig: NetCfg): CC() class APIGetNetworkConfig: CC() class APISetChatSettings(val type: ChatType, val id: Long, val chatSettings: ChatSettings): CC() @@ -1581,20 +1629,20 @@ 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 AddContact: CC() - class Connect(val connReq: String): CC() + class APIAddContact(val userId: Long): CC() + class APIConnect(val userId: Long, val connReq: String): CC() class ApiDeleteChat(val type: ChatType, val id: Long): CC() class ApiClearChat(val type: ChatType, val id: Long): CC() - class ListContacts: CC() - class ApiUpdateProfile(val profile: Profile): CC() + class ApiListContacts(val userId: Long): CC() + class ApiUpdateProfile(val userId: Long, val profile: Profile): CC() class ApiSetContactPrefs(val contactId: Long, val prefs: ChatPreferences): CC() class ApiParseMarkdown(val text: String): CC() class ApiSetContactAlias(val contactId: Long, val localAlias: String): CC() class ApiSetConnectionAlias(val connId: Long, val localAlias: String): CC() - class CreateMyAddress: CC() - class DeleteMyAddress: CC() - class ShowMyAddress: CC() - class AddressAutoAccept(val autoAccept: AutoAccept?): CC() + class ApiCreateMyAddress(val userId: Long): CC() + class ApiDeleteMyAddress(val userId: Long): CC() + class ApiShowMyAddress(val userId: Long): CC() + class ApiAddressAutoAccept(val userId: Long, val autoAccept: AutoAccept?): CC() class ApiSendCallInvitation(val contact: Contact, val callType: CallType): CC() class ApiRejectCall(val contact: Contact): CC() class ApiSendCallOffer(val contact: Contact, val callOffer: WebRTCCallOffer): CC() @@ -1620,12 +1668,12 @@ sealed class CC { is ApiImportArchive -> "/_db import ${json.encodeToString(config)}" is ApiDeleteStorage -> "/_db delete" is ApiStorageEncryption -> "/_db encryption ${json.encodeToString(config)}" - is ApiGetChats -> "/_get chats pcc=on" + is ApiGetChats -> "/_get $userId chats pcc=on" is ApiGetChat -> "/_get chat ${chatRef(type, id)} ${pagination.cmdString}" + (if (search == "") "" else " search=$search") is ApiSendMessage -> "/_send ${chatRef(type, id)} live=${onOff(live)} json ${json.encodeToString(ComposedMessage(file, quotedItemId, mc))}" is ApiUpdateChatItem -> "/_update item ${chatRef(type, id)} $itemId live=${onOff(live)} ${mc.cmdString}" is ApiDeleteChatItem -> "/_delete item ${chatRef(type, id)} $itemId ${mode.deleteMode}" - is NewGroup -> "/_group ${json.encodeToString(groupProfile)}" + is ApiNewGroup -> "/_group $userId ${json.encodeToString(groupProfile)}" is ApiAddMember -> "/_add #$groupId $contactId ${memberRole.memberRole}" is ApiJoinGroup -> "/_join #$groupId" is ApiMemberRole -> "/_member role #$groupId $memberId ${memberRole.memberRole}" @@ -1636,11 +1684,11 @@ sealed class CC { is APICreateGroupLink -> "/_create link #$groupId" is APIDeleteGroupLink -> "/_delete link #$groupId" is APIGetGroupLink -> "/_get link #$groupId" - is GetUserSMPServers -> "/smp" - is SetUserSMPServers -> "/_smp ${smpServersStr(smpServers)}" + is APIGetUserSMPServers -> "/_smp $userId" + is APISetUserSMPServers -> "/_smp $userId ${smpServersStr(smpServers)}" is TestSMPServer -> "/smp test $smpServer" - is APISetChatItemTTL -> "/_ttl ${chatItemTTLStr(seconds)}" - is APIGetChatItemTTL -> "/ttl" + is APISetChatItemTTL -> "/_ttl $userId ${chatItemTTLStr(seconds)}" + is APIGetChatItemTTL -> "/_ttl $userId" is APISetNetworkConfig -> "/_network ${json.encodeToString(networkConfig)}" is APIGetNetworkConfig -> "/network" is APISetChatSettings -> "/_settings ${chatRef(type, id)} ${json.encodeToString(chatSettings)}" @@ -1652,20 +1700,20 @@ 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 AddContact -> "/connect" - is Connect -> "/connect $connReq" + is APIAddContact -> "/_connect $userId" + is APIConnect -> "/_connect $userId $connReq" is ApiDeleteChat -> "/_delete ${chatRef(type, id)}" is ApiClearChat -> "/_clear chat ${chatRef(type, id)}" - is ListContacts -> "/contacts" - is ApiUpdateProfile -> "/_profile ${json.encodeToString(profile)}" + is ApiListContacts -> "/_contacts $userId" + is ApiUpdateProfile -> "/_profile $userId ${json.encodeToString(profile)}" is ApiSetContactPrefs -> "/_set prefs @$contactId ${json.encodeToString(prefs)}" is ApiParseMarkdown -> "/_parse $text" is ApiSetContactAlias -> "/_set alias @$contactId ${localAlias.trim()}" is ApiSetConnectionAlias -> "/_set alias :$connId ${localAlias.trim()}" - is CreateMyAddress -> "/address" - is DeleteMyAddress -> "/delete_address" - is ShowMyAddress -> "/show_address" - is AddressAutoAccept -> "/auto_accept ${AutoAccept.cmdString(autoAccept)}" + is ApiCreateMyAddress -> "/_address $userId" + is ApiDeleteMyAddress -> "/_delete_address $userId" + is ApiShowMyAddress -> "/_show_address $userId" + is ApiAddressAutoAccept -> "/_auto_accept $userId ${AutoAccept.cmdString(autoAccept)}" is ApiAcceptContact -> "/_accept $contactReqId" is ApiRejectContact -> "/_reject $contactReqId" is ApiSendCallInvitation -> "/_call invite @${contact.apiId} ${json.encodeToString(callType)}" @@ -1697,7 +1745,7 @@ sealed class CC { is ApiSendMessage -> "apiSendMessage" is ApiUpdateChatItem -> "apiUpdateChatItem" is ApiDeleteChatItem -> "apiDeleteChatItem" - is NewGroup -> "newGroup" + is ApiNewGroup -> "apiNewGroup" is ApiAddMember -> "apiAddMember" is ApiJoinGroup -> "apiJoinGroup" is ApiMemberRole -> "apiMemberRole" @@ -1708,8 +1756,8 @@ sealed class CC { is APICreateGroupLink -> "apiCreateGroupLink" is APIDeleteGroupLink -> "apiDeleteGroupLink" is APIGetGroupLink -> "apiGetGroupLink" - is GetUserSMPServers -> "getUserSMPServers" - is SetUserSMPServers -> "setUserSMPServers" + is APIGetUserSMPServers -> "apiGetUserSMPServers" + is APISetUserSMPServers -> "apiSetUserSMPServers" is TestSMPServer -> "testSMPServer" is APISetChatItemTTL -> "apiSetChatItemTTL" is APIGetChatItemTTL -> "apiGetChatItemTTL" @@ -1724,20 +1772,20 @@ sealed class CC { is APIGetGroupMemberCode -> "apiGetGroupMemberCode" is APIVerifyContact -> "apiVerifyContact" is APIVerifyGroupMember -> "apiVerifyGroupMember" - is AddContact -> "addContact" - is Connect -> "connect" + is APIAddContact -> "apiAddContact" + is APIConnect -> "apiConnect" is ApiDeleteChat -> "apiDeleteChat" is ApiClearChat -> "apiClearChat" - is ListContacts -> "listContacts" - is ApiUpdateProfile -> "updateProfile" + is ApiListContacts -> "apiListContacts" + is ApiUpdateProfile -> "apiUpdateProfile" is ApiSetContactPrefs -> "apiSetContactPrefs" is ApiParseMarkdown -> "apiParseMarkdown" is ApiSetContactAlias -> "apiSetContactAlias" is ApiSetConnectionAlias -> "apiSetConnectionAlias" - is CreateMyAddress -> "createMyAddress" - is DeleteMyAddress -> "deleteMyAddress" - is ShowMyAddress -> "showMyAddress" - is AddressAutoAccept -> "addressAutoAccept" + is ApiCreateMyAddress -> "apiCreateMyAddress" + is ApiDeleteMyAddress -> "apiDeleteMyAddress" + is ApiShowMyAddress -> "apiShowMyAddress" + is ApiAddressAutoAccept -> "apiAddressAutoAccept" is ApiAcceptContact -> "apiAcceptContact" is ApiRejectContact -> "apiRejectContact" is ApiSendCallInvitation -> "apiSendCallInvitation" diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 8cf06ba065..382237ecad 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -189,7 +189,8 @@ func apiStorageEncryption(currentKey: String = "", newKey: String = "") async th } func apiGetChats() throws -> [ChatData] { - let r = chatSendCmdSync(.apiGetChats) + guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("apiGetChats: no current user") } + let r = chatSendCmdSync(.apiGetChats(userId: userId)) if case let .apiChats(chats) = r { return chats } throw r } @@ -310,13 +311,15 @@ func apiDeleteToken(token: DeviceToken) async throws { } func getUserSMPServers() throws -> ([ServerCfg], [String]) { - let r = chatSendCmdSync(.getUserSMPServers) + guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("getUserSMPServers: no current user") } + let r = chatSendCmdSync(.apiGetUserSMPServers(userId: userId)) if case let .userSMPServers(smpServers, presetServers) = r { return (smpServers, presetServers) } throw r } func setUserSMPServers(smpServers: [ServerCfg]) async throws { - try await sendCommandOkResp(.setUserSMPServers(smpServers: smpServers)) + guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("setUserSMPServers: no current user") } + try await sendCommandOkResp(.apiSetUserSMPServers(userId: userId, smpServers: smpServers)) } func testSMPServer(smpServer: String) async throws -> Result<(), SMPTestFailure> { @@ -331,13 +334,15 @@ func testSMPServer(smpServer: String) async throws -> Result<(), SMPTestFailure> } func getChatItemTTL() throws -> ChatItemTTL { - let r = chatSendCmdSync(.apiGetChatItemTTL) + guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("getChatItemTTL: no current user") } + let r = chatSendCmdSync(.apiGetChatItemTTL(userId: userId)) if case let .chatItemTTL(chatItemTTL) = r { return ChatItemTTL(chatItemTTL) } throw r } func setChatItemTTL(_ chatItemTTL: ChatItemTTL) async throws { - try await sendCommandOkResp(.apiSetChatItemTTL(seconds: chatItemTTL.seconds)) + guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("setChatItemTTL: no current user") } + try await sendCommandOkResp(.apiSetChatItemTTL(userId: userId, seconds: chatItemTTL.seconds)) } func getNetworkConfig() async throws -> NetCfg? { @@ -403,14 +408,22 @@ func apiVerifyGroupMember(_ groupId: Int64, _ groupMemberId: Int64, connectionCo } func apiAddContact() async -> String? { - let r = await chatSendCmd(.addContact, bgTask: false) + guard let userId = ChatModel.shared.currentUser?.userId else { + logger.error("apiAddContact: no current user") + return nil + } + let r = await chatSendCmd(.apiAddContact(userId: userId), bgTask: false) if case let .invitation(connReqInvitation) = r { return connReqInvitation } connectionErrorAlert(r) return nil } func apiConnect(connReq: String) async -> ConnReqType? { - let r = await chatSendCmd(.connect(connReq: connReq)) + guard let userId = ChatModel.shared.currentUser?.userId else { + logger.error("apiConnect: no current user") + return nil + } + let r = await chatSendCmd(.apiConnect(userId: userId, connReq: connReq)) let am = AlertManager.shared switch r { case .sentConfirmation: return .invitation @@ -499,13 +512,15 @@ func clearChat(_ chat: Chat) async { } func apiListContacts() throws -> [Contact] { - let r = chatSendCmdSync(.listContacts) + guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("apiListContacts: no current user") } + let r = chatSendCmdSync(.apiListContacts(userId: userId)) if case let .contactsList(contacts) = r { return contacts } throw r } func apiUpdateProfile(profile: Profile) async throws -> Profile? { - let r = await chatSendCmd(.apiUpdateProfile(profile: profile)) + guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("apiUpdateProfile: no current user") } + let r = await chatSendCmd(.apiUpdateProfile(userId: userId, profile: profile)) switch r { case .userProfileNoChange: return nil case let .userProfileUpdated(_, toProfile): return toProfile @@ -532,19 +547,22 @@ func apiSetConnectionAlias(connId: Int64, localAlias: String) async throws -> Pe } func apiCreateUserAddress() async throws -> String { - let r = await chatSendCmd(.createMyAddress) + guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("apiCreateUserAddress: no current user") } + let r = await chatSendCmd(.apiCreateMyAddress(userId: userId)) if case let .userContactLinkCreated(connReq) = r { return connReq } throw r } func apiDeleteUserAddress() async throws { - let r = await chatSendCmd(.deleteMyAddress) + guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("apiDeleteUserAddress: no current user") } + let r = await chatSendCmd(.apiDeleteMyAddress(userId: userId)) if case .userContactLinkDeleted = r { return } throw r } func apiGetUserAddress() throws -> UserContactLink? { - let r = chatSendCmdSync(.showMyAddress) + guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("apiGetUserAddress: no current user") } + let r = chatSendCmdSync(.apiShowMyAddress(userId: userId)) switch r { case let .userContactLink(contactLink): return contactLink case .chatCmdError(chatError: .errorStore(storeError: .userContactLinkNotFound)): return nil @@ -553,7 +571,8 @@ func apiGetUserAddress() throws -> UserContactLink? { } func userAddressAutoAccept(_ autoAccept: AutoAccept?) async throws -> UserContactLink? { - let r = await chatSendCmd(.addressAutoAccept(autoAccept: autoAccept)) + guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("userAddressAutoAccept: no current user") } + let r = await chatSendCmd(.apiAddressAutoAccept(userId: userId, autoAccept: autoAccept)) switch r { case let .userContactLinkUpdated(contactLink): return contactLink case .chatCmdError(chatError: .errorStore(storeError: .userContactLinkNotFound)): return nil @@ -691,7 +710,8 @@ func apiEndCall(_ contact: Contact) async throws { } func apiGetCallInvitations() throws -> [RcvCallInvitation] { - let r = chatSendCmdSync(.apiGetCallInvitations) + guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("apiGetCallInvitations: no current user") } + let r = chatSendCmdSync(.apiGetCallInvitations(userId: userId)) if case let .callInvitations(invs) = r { return invs } throw r } @@ -748,7 +768,8 @@ private func sendCommandOkResp(_ cmd: ChatCommand) async throws { } func apiNewGroup(_ p: GroupProfile) throws -> GroupInfo { - let r = chatSendCmdSync(.newGroup(groupProfile: p)) + guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("apiNewGroup: no current user") } + let r = chatSendCmdSync(.apiNewGroup(userId: userId, groupProfile: p)) if case let .groupCreated(groupInfo) = r { return groupInfo } throw r } @@ -1209,3 +1230,15 @@ private struct UserResponse: Decodable { var user: User? var error: String? } + +struct RuntimeError: Error { + let message: String + + init(_ message: String) { + self.message = message + } + + public var localizedDescription: String { + return message + } +} diff --git a/apps/ios/SimpleX NSE/NotificationService.swift b/apps/ios/SimpleX NSE/NotificationService.swift index 7d6c6c485e..779b61ad85 100644 --- a/apps/ios/SimpleX NSE/NotificationService.swift +++ b/apps/ios/SimpleX NSE/NotificationService.swift @@ -275,7 +275,11 @@ func apiSetIncognito(incognito: Bool) throws { } func apiGetNtfMessage(nonce: String, encNtfInfo: String) -> NtfMessages? { - let r = sendSimpleXCmd(.apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo)) + guard let user = apiGetActiveUser() else { + logger.debug("no active user") + return nil + } + let r = sendSimpleXCmd(.apiGetNtfMessage(userId: user.userId, nonce: nonce, encNtfInfo: encNtfInfo)) if case let .ntfMessages(connEntity, msgTs, ntfMessages) = r { return NtfMessages(connEntity: connEntity, msgTs: msgTs, ntfMessages: ntfMessages) } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 27f26a7a98..ad44234e77 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -25,7 +25,7 @@ public enum ChatCommand { case apiImportArchive(config: ArchiveConfig) case apiDeleteStorage case apiStorageEncryption(config: DBEncryptionConfig) - case apiGetChats + case apiGetChats(userId: Int64) case apiGetChat(type: ChatType, id: Int64, pagination: ChatPagination, search: String) case apiSendMessage(type: ChatType, id: Int64, file: String?, quotedItemId: Int64?, msg: MsgContent, live: Bool) case apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent, live: Bool) @@ -34,8 +34,8 @@ public enum ChatCommand { case apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode) case apiVerifyToken(token: DeviceToken, nonce: String, code: String) case apiDeleteToken(token: DeviceToken) - case apiGetNtfMessage(nonce: String, encNtfInfo: String) - case newGroup(groupProfile: GroupProfile) + case apiGetNtfMessage(userId: Int64, nonce: String, encNtfInfo: String) + case apiNewGroup(userId: Int64, groupProfile: GroupProfile) case apiAddMember(groupId: Int64, contactId: Int64, memberRole: GroupMemberRole) case apiJoinGroup(groupId: Int64) case apiMemberRole(groupId: Int64, memberId: Int64, memberRole: GroupMemberRole) @@ -46,11 +46,11 @@ public enum ChatCommand { case apiCreateGroupLink(groupId: Int64) case apiDeleteGroupLink(groupId: Int64) case apiGetGroupLink(groupId: Int64) - case getUserSMPServers - case setUserSMPServers(smpServers: [ServerCfg]) + case apiGetUserSMPServers(userId: Int64) + case apiSetUserSMPServers(userId: Int64, smpServers: [ServerCfg]) case testSMPServer(smpServer: String) - case apiSetChatItemTTL(seconds: Int64?) - case apiGetChatItemTTL + case apiSetChatItemTTL(userId: Int64, seconds: Int64?) + case apiGetChatItemTTL(userId: Int64) case apiSetNetworkConfig(networkConfig: NetCfg) case apiGetNetworkConfig case apiSetChatSettings(type: ChatType, id: Int64, chatSettings: ChatSettings) @@ -62,19 +62,19 @@ public enum ChatCommand { case apiGetGroupMemberCode(groupId: Int64, groupMemberId: Int64) case apiVerifyContact(contactId: Int64, connectionCode: String?) case apiVerifyGroupMember(groupId: Int64, groupMemberId: Int64, connectionCode: String?) - case addContact - case connect(connReq: String) + case apiAddContact(userId: Int64) + case apiConnect(userId: Int64, connReq: String) case apiDeleteChat(type: ChatType, id: Int64) case apiClearChat(type: ChatType, id: Int64) - case listContacts - case apiUpdateProfile(profile: Profile) + case apiListContacts(userId: Int64) + case apiUpdateProfile(userId: Int64, profile: Profile) case apiSetContactPrefs(contactId: Int64, preferences: Preferences) case apiSetContactAlias(contactId: Int64, localAlias: String) case apiSetConnectionAlias(connId: Int64, localAlias: String) - case createMyAddress - case deleteMyAddress - case showMyAddress - case addressAutoAccept(autoAccept: AutoAccept?) + case apiCreateMyAddress(userId: Int64) + case apiDeleteMyAddress(userId: Int64) + case apiShowMyAddress(userId: Int64) + case apiAddressAutoAccept(userId: Int64, autoAccept: AutoAccept?) case apiAcceptContact(contactReqId: Int64) case apiRejectContact(contactReqId: Int64) // WebRTC calls @@ -84,7 +84,7 @@ public enum ChatCommand { case apiSendCallAnswer(contact: Contact, answer: WebRTCSession) case apiSendCallExtraInfo(contact: Contact, extraInfo: WebRTCExtraInfo) case apiEndCall(contact: Contact) - case apiGetCallInvitations + case apiGetCallInvitations(userId: Int64) case apiCallStatus(contact: Contact, callStatus: WebRTCCallStatus) case apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) @@ -106,7 +106,7 @@ public enum ChatCommand { case let .apiImportArchive(cfg): return "/_db import \(encodeJSON(cfg))" case .apiDeleteStorage: return "/_db delete" case let .apiStorageEncryption(cfg): return "/_db encryption \(encodeJSON(cfg))" - case .apiGetChats: return "/_get chats pcc=on" + case let .apiGetChats(userId): return "/_get chats \(userId) pcc=on" case let .apiGetChat(type, id, pagination, search): return "/_get chat \(ref(type, id)) \(pagination.cmdString)" + (search == "" ? "" : " search=\(search)") case let .apiSendMessage(type, id, file, quotedItemId, mc, live): @@ -118,8 +118,8 @@ public enum ChatCommand { case let .apiRegisterToken(token, notificationMode): return "/_ntf register \(token.cmdString) \(notificationMode.rawValue)" case let .apiVerifyToken(token, nonce, code): return "/_ntf verify \(token.cmdString) \(nonce) \(code)" case let .apiDeleteToken(token): return "/_ntf delete \(token.cmdString)" - case let .apiGetNtfMessage(nonce, encNtfInfo): return "/_ntf message \(nonce) \(encNtfInfo)" - case let .newGroup(groupProfile): return "/_group \(encodeJSON(groupProfile))" + case let .apiGetNtfMessage(userId, nonce, encNtfInfo): return "/_ntf message \(userId) \(nonce) \(encNtfInfo)" + case let .apiNewGroup(userId, groupProfile): return "/_group \(userId) \(encodeJSON(groupProfile))" case let .apiAddMember(groupId, contactId, memberRole): return "/_add #\(groupId) \(contactId) \(memberRole)" case let .apiJoinGroup(groupId): return "/_join #\(groupId)" case let .apiMemberRole(groupId, memberId, memberRole): return "/_member role #\(groupId) \(memberId) \(memberRole.rawValue)" @@ -130,11 +130,11 @@ public enum ChatCommand { case let .apiCreateGroupLink(groupId): return "/_create link #\(groupId)" case let .apiDeleteGroupLink(groupId): return "/_delete link #\(groupId)" case let .apiGetGroupLink(groupId): return "/_get link #\(groupId)" - case .getUserSMPServers: return "/smp" - case let .setUserSMPServers(smpServers): return "/_smp \(smpServersStr(smpServers: smpServers))" + case let .apiGetUserSMPServers(userId): return "/_smp \(userId)" + case let .apiSetUserSMPServers(userId, smpServers): return "/_smp \(userId) \(smpServersStr(smpServers: smpServers))" case let .testSMPServer(smpServer): return "/smp test \(smpServer)" - case let .apiSetChatItemTTL(seconds): return "/_ttl \(chatItemTTLStr(seconds: seconds))" - case .apiGetChatItemTTL: return "/ttl" + case let .apiSetChatItemTTL(userId, seconds): return "/_ttl \(userId) \(chatItemTTLStr(seconds: seconds))" + case let .apiGetChatItemTTL(userId): return "/_ttl \(userId)" case let .apiSetNetworkConfig(networkConfig): return "/_network \(encodeJSON(networkConfig))" case .apiGetNetworkConfig: return "/network" case let .apiSetChatSettings(type, id, chatSettings): return "/_settings \(ref(type, id)) \(encodeJSON(chatSettings))" @@ -148,19 +148,19 @@ 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 .addContact: return "/connect" - case let .connect(connReq): return "/connect \(connReq)" + case let .apiAddContact(userId): return "/_connect \(userId)" + case let .apiConnect(userId, connReq): return "/_connect \(userId) \(connReq)" case let .apiDeleteChat(type, id): return "/_delete \(ref(type, id))" case let .apiClearChat(type, id): return "/_clear chat \(ref(type, id))" - case .listContacts: return "/contacts" - case let .apiUpdateProfile(profile): return "/_profile \(encodeJSON(profile))" + case let .apiListContacts(userId): return "/_contacts \(userId)" + case let .apiUpdateProfile(userId, profile): return "/_profile \(userId) \(encodeJSON(profile))" case let .apiSetContactPrefs(contactId, preferences): return "/_set prefs @\(contactId) \(encodeJSON(preferences))" case let .apiSetContactAlias(contactId, localAlias): return "/_set alias @\(contactId) \(localAlias.trimmingCharacters(in: .whitespaces))" case let .apiSetConnectionAlias(connId, localAlias): return "/_set alias :\(connId) \(localAlias.trimmingCharacters(in: .whitespaces))" - case .createMyAddress: return "/address" - case .deleteMyAddress: return "/delete_address" - case .showMyAddress: return "/show_address" - case let .addressAutoAccept(autoAccept): return "/auto_accept \(AutoAccept.cmdString(autoAccept))" + case let .apiCreateMyAddress(userId): return "/_address \(userId)" + case let .apiDeleteMyAddress(userId): return "/_delete_address \(userId)" + case let .apiShowMyAddress(userId): return "/_show_address \(userId)" + case let .apiAddressAutoAccept(userId, autoAccept): return "/_auto_accept \(userId) \(AutoAccept.cmdString(autoAccept))" case let .apiAcceptContact(contactReqId): return "/_accept \(contactReqId)" case let .apiRejectContact(contactReqId): return "/_reject \(contactReqId)" case let .apiSendCallInvitation(contact, callType): return "/_call invite @\(contact.apiId) \(encodeJSON(callType))" @@ -169,7 +169,7 @@ public enum ChatCommand { case let .apiSendCallAnswer(contact, answer): return "/_call answer @\(contact.apiId) \(encodeJSON(answer))" case let .apiSendCallExtraInfo(contact, extraInfo): return "/_call extra @\(contact.apiId) \(encodeJSON(extraInfo))" case let .apiEndCall(contact): return "/_call end @\(contact.apiId)" - case .apiGetCallInvitations: return "/_call get" + case let .apiGetCallInvitations(userId): return "/_call get \(userId)" case let .apiCallStatus(contact, callStatus): return "/_call status @\(contact.apiId) \(callStatus.rawValue)" case let .apiChatRead(type, id, itemRange: (from, to)): return "/_read chat \(ref(type, id)) from=\(from) to=\(to)" case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id)) \(onOff(unreadChat))" @@ -204,7 +204,7 @@ public enum ChatCommand { case .apiVerifyToken: return "apiVerifyToken" case .apiDeleteToken: return "apiDeleteToken" case .apiGetNtfMessage: return "apiGetNtfMessage" - case .newGroup: return "newGroup" + case .apiNewGroup: return "apiNewGroup" case .apiAddMember: return "apiAddMember" case .apiJoinGroup: return "apiJoinGroup" case .apiMemberRole: return "apiMemberRole" @@ -215,8 +215,8 @@ public enum ChatCommand { case .apiCreateGroupLink: return "apiCreateGroupLink" case .apiDeleteGroupLink: return "apiDeleteGroupLink" case .apiGetGroupLink: return "apiGetGroupLink" - case .getUserSMPServers: return "getUserSMPServers" - case .setUserSMPServers: return "setUserSMPServers" + case .apiGetUserSMPServers: return "apiGetUserSMPServers" + case .apiSetUserSMPServers: return "apiSetUserSMPServers" case .testSMPServer: return "testSMPServer" case .apiSetChatItemTTL: return "apiSetChatItemTTL" case .apiGetChatItemTTL: return "apiGetChatItemTTL" @@ -231,19 +231,19 @@ public enum ChatCommand { case .apiGetGroupMemberCode: return "apiGetGroupMemberCode" case .apiVerifyContact: return "apiVerifyContact" case .apiVerifyGroupMember: return "apiVerifyGroupMember" - case .addContact: return "addContact" - case .connect: return "connect" + case .apiAddContact: return "apiAddContact" + case .apiConnect: return "apiConnect" case .apiDeleteChat: return "apiDeleteChat" case .apiClearChat: return "apiClearChat" - case .listContacts: return "listContacts" + case .apiListContacts: return "apiListContacts" case .apiUpdateProfile: return "apiUpdateProfile" case .apiSetContactPrefs: return "apiSetContactPrefs" case .apiSetContactAlias: return "apiSetContactAlias" case .apiSetConnectionAlias: return "apiSetConnectionAlias" - case .createMyAddress: return "createMyAddress" - case .deleteMyAddress: return "deleteMyAddress" - case .showMyAddress: return "showMyAddress" - case .addressAutoAccept: return "addressAutoAccept" + case .apiCreateMyAddress: return "apiCreateMyAddress" + case .apiDeleteMyAddress: return "apiDeleteMyAddress" + case .apiShowMyAddress: return "apiShowMyAddress" + case .apiAddressAutoAccept: return "apiAddressAutoAccept" case .apiAcceptContact: return "apiAcceptContact" case .apiRejectContact: return "apiRejectContact" case .apiSendCallInvitation: return "apiSendCallInvitation" diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index f7f53005fe..1e918cfcc9 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -10,7 +10,7 @@ import Foundation import SwiftUI public struct User: Decodable, NamedChat { - var userId: Int64 + public var userId: Int64 var userContactId: Int64 var localDisplayName: ContactName public var profile: LocalProfile diff --git a/apps/simplex-bot-advanced/Main.hs b/apps/simplex-bot-advanced/Main.hs index f5acebe96a..db370cdfa0 100644 --- a/apps/simplex-bot-advanced/Main.hs +++ b/apps/simplex-bot-advanced/Main.hs @@ -39,10 +39,10 @@ mySquaringBot _user cc = do race_ (forever $ void getLine) . forever $ do (_, resp) <- atomically . readTBQueue $ outputQ cc case resp of - CRContactConnected contact _ -> do + CRContactConnected _ contact _ -> do contactConnected contact void . sendMsg contact $ "Hello! I am a simple squaring bot - if you send me a number, I will calculate its square" - CRNewChatItem (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content}) -> do + CRNewChatItem _ (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content}) -> do let msg = T.unpack $ ciContentToText content number_ = readMaybe msg :: Maybe Integer void . sendMsg contact $ case number_ of diff --git a/apps/simplex-chat/Server.hs b/apps/simplex-chat/Server.hs index 481e03b01e..d59adc04e7 100644 --- a/apps/simplex-chat/Server.hs +++ b/apps/simplex-chat/Server.hs @@ -95,11 +95,11 @@ runChatServer ChatServerConfig {chatPort, clientQSize} cc = do Left e -> sendError (Just corrId) e Nothing -> sendError Nothing "invalid request" where - sendError corrId e = atomically $ writeTBQueue sndQ ChatSrvResponse {corrId, resp = chatCmdError e} + sendError corrId e = atomically $ writeTBQueue sndQ ChatSrvResponse {corrId, resp = chatCmdError Nothing e} processCommand (corrId, cmd) = runReaderT (runExceptT $ processChatCommand cmd) cc >>= \case Right resp -> response resp - Left e -> response $ CRChatCmdError e + Left e -> response $ CRChatCmdError Nothing e where response resp = pure ChatSrvResponse {corrId = Just corrId, resp} clientDisconnected _ = pure () diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index b36c634f8a..4784964df7 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -308,7 +308,8 @@ processChatCommand = \case APIStorageEncryption cfg -> withStoreChanged $ sqlCipherExport cfg ExecChatStoreSQL query -> CRSQLResult <$> withStore' (`execSQL` query) ExecAgentStoreSQL query -> CRSQLResult <$> withAgent (`execAgentStoreSQL` query) - APIGetChats withPCC -> withUser' $ \user -> do + APIGetChats cmdUserId withPCC -> withUser' $ \user -> do + checkCorrectCmdUser cmdUserId user chats <- withStore' $ \db -> getChatPreviews db user withPCC pure $ CRApiChats user chats APIGetChat (ChatRef cType cId) pagination search -> withUser $ \user -> case cType of @@ -716,7 +717,8 @@ processChatCommand = \case (SndMessage {msgId}, _) <- sendDirectContactMessage ct (XCallEnd callId) updateCallItemStatus userId ct call WCSDisconnected $ Just msgId pure Nothing - APIGetCallInvitations -> withUser $ \user -> do + APIGetCallInvitations cmdUserId -> withUser $ \user -> do + checkCorrectCmdUser cmdUserId user calls <- asks currentCalls >>= readTVarIO let invs = mapMaybe callInvitation $ M.elems calls rcvCallInvitations <- mapM (rcvCallInvitation user) invs @@ -731,7 +733,9 @@ processChatCommand = \case APICallStatus contactId receivedStatus -> withCurrentCall contactId $ \userId ct call -> updateCallItemStatus userId ct call receivedStatus Nothing $> Just call - APIUpdateProfile profile -> withUser (`updateProfile` profile) + APIUpdateProfile cmdUserId profile -> withUser $ \user -> do + checkCorrectCmdUser cmdUserId user + updateProfile user profile APISetContactPrefs contactId prefs' -> withUser $ \user -> do ct <- withStore $ \db -> getContact db user contactId updateContactPrefs user ct prefs' @@ -752,26 +756,34 @@ processChatCommand = \case pure $ CRNtfTokenStatus tokenStatus APIVerifyToken token nonce code -> withUser $ \_ -> withAgent (\a -> verifyNtfToken a token nonce code) $> CRCmdOk Nothing APIDeleteToken token -> withUser $ \_ -> withAgent (`deleteNtfToken` token) $> CRCmdOk Nothing - APIGetNtfMessage nonce encNtfInfo -> withUser $ \user -> do + APIGetNtfMessage cmdUserId nonce encNtfInfo -> withUser $ \user -> do + checkCorrectCmdUser cmdUserId user (NotificationInfo {ntfConnId, ntfMsgMeta}, msgs) <- withAgent $ \a -> getNotificationMessage a nonce encNtfInfo let ntfMessages = map (\SMP.SMPMsgMeta {msgTs, msgFlags} -> NtfMsgInfo {msgTs = systemToUTCTime msgTs, msgFlags}) msgs msgTs' = systemToUTCTime . (SMP.msgTs :: SMP.NMsgMeta -> SystemTime) <$> ntfMsgMeta connEntity <- withStore (\db -> Just <$> getConnectionEntity db user (AgentConnId ntfConnId)) `catchError` \_ -> pure Nothing pure CRNtfMessages {user, connEntity, msgTs = msgTs', ntfMessages} - GetUserSMPServers -> withUser $ \user -> do + APIGetUserSMPServers cmdUserId -> withUser $ \user -> do + checkCorrectCmdUser cmdUserId user ChatConfig {defaultServers = InitialAgentServers {smp = defaultSMPServers}} <- asks config smpServers <- withStore' (`getSMPServers` user) let smpServers' = fromMaybe (L.map toServerCfg defaultSMPServers) $ nonEmpty smpServers pure $ CRUserSMPServers user smpServers' defaultSMPServers where toServerCfg server = ServerCfg {server, preset = True, tested = Nothing, enabled = True} - SetUserSMPServers (SMPServersConfig smpServers) -> withUser $ \user -> withChatLock "setUserSMPServers" $ do + GetUserSMPServers -> withUser $ \User {userId} -> + processChatCommand $ APIGetUserSMPServers userId + APISetUserSMPServers cmdUserId (SMPServersConfig smpServers) -> withUser $ \user -> withChatLock "setUserSMPServers" $ do + checkCorrectCmdUser cmdUserId user withStore $ \db -> overwriteSMPServers db user smpServers cfg <- asks config withAgent $ \a -> setSMPServers a $ activeAgentServers cfg smpServers pure $ CRCmdOk (Just user) + SetUserSMPServers smpServersConfig -> withUser $ \User {userId} -> + processChatCommand $ APISetUserSMPServers userId smpServersConfig TestSMPServer smpServer -> CRSmpTestResult <$> withAgent (`testSMPServerConnection` smpServer) - APISetChatItemTTL newTTL_ -> withUser' $ \user -> + APISetChatItemTTL cmdUserId newTTL_ -> withUser' $ \user -> do + checkCorrectCmdUser cmdUserId user checkStoreNotChanged $ withChatLock "setChatItemTTL" $ do case newTTL_ of @@ -786,9 +798,14 @@ processChatCommand = \case withStore' $ \db -> setChatItemTTL db user newTTL_ whenM chatStarted $ setExpireCIs True pure $ CRCmdOk (Just user) - APIGetChatItemTTL -> withUser $ \user -> do + SetChatItemTTL newTTL_ -> withUser' $ \User {userId} -> do + processChatCommand $ APISetChatItemTTL userId newTTL_ + APIGetChatItemTTL cmdUserId -> withUser $ \user -> do + checkCorrectCmdUser cmdUserId user ttl <- withStore' (`getChatItemTTL` user) pure $ CRChatItemTTL user ttl + GetChatItemTTL -> withUser' $ \User {userId} -> do + processChatCommand $ APIGetChatItemTTL userId APISetNetworkConfig cfg -> withUser' $ \_ -> withAgent (`setNetworkConfig` cfg) $> CRCmdOk Nothing APIGetNetworkConfig -> withUser' $ \_ -> do networkConfig <- withAgent getNetworkConfig @@ -878,7 +895,8 @@ processChatCommand = \case VerifyGroupMember gName mName code -> withMemberName gName mName $ \gId mId -> APIVerifyGroupMember gId mId code ChatHelp section -> pure $ CRChatHelp section Welcome -> withUser $ pure . CRWelcome - AddContact -> withUser $ \user@User {userId} -> withChatLock "addContact" . procCmd $ do + APIAddContact cmdUserId -> withUser $ \user@User {userId} -> withChatLock "addContact" . procCmd $ do + checkCorrectCmdUser cmdUserId user -- [incognito] generate profile for connection incognito <- readTVarIO =<< asks incognitoMode incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing @@ -886,7 +904,10 @@ processChatCommand = \case conn <- withStore' $ \db -> createDirectConnection db userId connId cReq ConnNew incognitoProfile toView $ CRNewContactConnection user conn pure $ CRInvitation user cReq - Connect (Just (ACR SCMInvitation cReq)) -> withUser $ \user@User {userId} -> withChatLock "connect" . procCmd $ do + AddContact -> withUser $ \User {userId} -> + processChatCommand $ APIAddContact userId + APIConnect cmdUserId (Just (ACR SCMInvitation cReq)) -> withUser $ \user@User {userId} -> withChatLock "connect" . procCmd $ do + checkCorrectCmdUser cmdUserId user -- [incognito] generate profile to send incognito <- readTVarIO =<< asks incognitoMode incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing @@ -895,34 +916,52 @@ processChatCommand = \case conn <- withStore' $ \db -> createDirectConnection db userId connId cReq ConnJoined $ incognitoProfile $> profileToSend toView $ CRNewContactConnection user conn pure $ CRSentConfirmation user - Connect (Just (ACR SCMContact cReq)) -> withUser $ \user -> + APIConnect cmdUserId (Just (ACR SCMContact cReq)) -> withUser $ \user -> do + checkCorrectCmdUser cmdUserId user -- [incognito] generate profile to send connectViaContact user cReq - Connect Nothing -> throwChatError CEInvalidConnReq + APIConnect _ Nothing -> throwChatError CEInvalidConnReq + Connect cReqUri -> withUser $ \User {userId} -> + processChatCommand $ APIConnect userId cReqUri ConnectSimplex -> withUser $ \user -> -- [incognito] generate profile to send connectViaContact user adminContactReq DeleteContact cName -> withContactName cName $ APIDeleteChat . ChatRef CTDirect ClearContact cName -> withContactName cName $ APIClearChat . ChatRef CTDirect - ListContacts -> withUser $ \user -> do + APIListContacts cmdUserId -> withUser $ \user -> do + checkCorrectCmdUser cmdUserId user contacts <- withStore' (`getUserContacts` user) pure $ CRContactsList user contacts - CreateMyAddress -> withUser $ \user@User {userId} -> withChatLock "createMyAddress" . procCmd $ do + ListContacts -> withUser $ \User {userId} -> + processChatCommand $ APIListContacts userId + APICreateMyAddress cmdUserId -> withUser $ \user@User {userId} -> withChatLock "createMyAddress" . procCmd $ do + checkCorrectCmdUser cmdUserId user (connId, cReq) <- withAgent $ \a -> createConnection a True SCMContact Nothing withStore $ \db -> createUserContactLink db userId connId cReq pure $ CRUserContactLinkCreated user cReq - DeleteMyAddress -> withUser $ \user -> withChatLock "deleteMyAddress" $ do + CreateMyAddress -> withUser $ \User {userId} -> + processChatCommand $ APICreateMyAddress userId + APIDeleteMyAddress cmdUserId -> withUser $ \user -> withChatLock "deleteMyAddress" $ do + checkCorrectCmdUser cmdUserId user conns <- withStore (`getUserAddressConnections` user) procCmd $ do forM_ conns $ \conn -> deleteAgentConnectionAsync user conn `catchError` \_ -> pure () withStore' (`deleteUserAddress` user) pure $ CRUserContactLinkDeleted user - ShowMyAddress -> withUser $ \user@User {userId} -> do + DeleteMyAddress -> withUser $ \User {userId} -> + processChatCommand $ APIDeleteMyAddress userId + APIShowMyAddress cmdUserId -> withUser $ \user@User {userId} -> do + checkCorrectCmdUser cmdUserId user contactLink <- withStore (`getUserAddress` userId) pure $ CRUserContactLink user contactLink - AddressAutoAccept autoAccept_ -> withUser $ \user@User {userId} -> do + ShowMyAddress -> withUser $ \User {userId} -> + processChatCommand $ APIShowMyAddress userId + APIAddressAutoAccept cmdUserId autoAccept_ -> withUser $ \user@User {userId} -> do + checkCorrectCmdUser cmdUserId user contactLink <- withStore (\db -> updateUserAddressAutoAccept db userId autoAccept_) pure $ CRUserContactLinkUpdated user contactLink + AddressAutoAccept autoAccept_ -> withUser $ \User {userId} -> + processChatCommand $ APIAddressAutoAccept userId autoAccept_ AcceptContact cName -> withUser $ \User {userId} -> do connReqId <- withStore $ \db -> getContactRequestIdByName db userId cName processChatCommand $ APIAcceptContact connReqId @@ -962,10 +1001,13 @@ processChatCommand = \case chatRef <- getChatRef user chatName let mc = MCText $ safeDecodeUtf8 msg processChatCommand $ APIUpdateChatItem chatRef chatItemId live mc - NewGroup gProfile -> withUser $ \user -> do + APINewGroup cmdUserId gProfile -> withUser $ \user -> do + checkCorrectCmdUser cmdUserId user gVar <- asks idsDrg groupInfo <- withStore (\db -> createNewGroup db gVar user gProfile) pure $ CRGroupCreated user groupInfo + NewGroup gProfile -> withUser $ \User {userId} -> + processChatCommand $ APINewGroup userId gProfile APIAddMember groupId contactId memRole -> withUser $ \user -> withChatLock "addMember" $ do -- TODO for large groups: no need to load all members to determine if contact is a member (group, contact) <- withStore $ \db -> (,) <$> getGroup db user groupId <*> getContact db user contactId @@ -1282,6 +1324,8 @@ processChatCommand = \case withStoreChanged a = checkChatStopped $ a >> setStoreChanged $> CRCmdOk Nothing checkStoreNotChanged :: m ChatResponse -> m ChatResponse checkStoreNotChanged = ifM (asks chatStoreChanged >>= readTVarIO) (throwChatError CEChatStoreChanged) + checkCorrectCmdUser :: UserId -> User -> m () + checkCorrectCmdUser cmdUserId User {userId = activeUserId} = when (cmdUserId /= activeUserId) $ throwChatError (CEDifferentActiveUser cmdUserId activeUserId) withUserName :: UserName -> (UserId -> ChatCommand) -> m ChatResponse withUserName uName cmd = withStore (`getUserIdByName` uName) >>= processChatCommand . cmd withContactName :: ContactName -> (ContactId -> ChatCommand) -> m ChatResponse @@ -3710,7 +3754,7 @@ chatCommandP = "/db decrypt " *> (APIStorageEncryption . (`DBEncryptionConfig` "") <$> dbKeyP), "/sql chat " *> (ExecChatStoreSQL <$> textP), "/sql agent " *> (ExecAgentStoreSQL <$> textP), - "/_get chats" *> (APIGetChats <$> (" pcc=on" $> True <|> " pcc=off" $> False <|> pure False)), + "/_get chats " *> (APIGetChats <$> A.decimal <*> (" pcc=on" $> True <|> " pcc=off" $> False <|> pure False)), "/_get chat " *> (APIGetChat <$> chatRefP <* A.space <*> chatPaginationP <*> optional (" search=" *> stringP)), "/_get items count=" *> (APIGetChatItems <$> A.decimal), "/_send " *> (APISendMessage <$> chatRefP <*> liveMessageP <*> (" json " *> jsonP <|> " text " *> (ComposedMessage Nothing Nothing <$> mcTextP))), @@ -3730,8 +3774,8 @@ chatCommandP = "/_call extra @" *> (APISendCallExtraInfo <$> A.decimal <* A.space <*> jsonP), "/_call end @" *> (APIEndCall <$> A.decimal), "/_call status @" *> (APICallStatus <$> A.decimal <* A.space <*> strP), - "/_call get" $> APIGetCallInvitations, - "/_profile " *> (APIUpdateProfile <$> jsonP), + "/_call get " *> (APIGetCallInvitations <$> A.decimal), + "/_profile " *> (APIUpdateProfile <$> A.decimal <* A.space <*> jsonP), "/_set alias @" *> (APISetContactAlias <$> A.decimal <*> (A.space *> textP <|> pure "")), "/_set alias :" *> (APISetConnectionAlias <$> A.decimal <*> (A.space *> textP <|> pure "")), "/_set prefs @" *> (APISetContactPrefs <$> A.decimal <* A.space <*> jsonP), @@ -3740,7 +3784,7 @@ chatCommandP = "/_ntf register " *> (APIRegisterToken <$> strP_ <*> strP), "/_ntf verify " *> (APIVerifyToken <$> strP <* A.space <*> strP <* A.space <*> strP), "/_ntf delete " *> (APIDeleteToken <$> strP), - "/_ntf message " *> (APIGetNtfMessage <$> strP <* A.space <*> strP), + "/_ntf message " *> (APIGetNtfMessage <$> A.decimal <* A.space <*> strP <* A.space <*> strP), "/_add #" *> (APIAddMember <$> A.decimal <* A.space <*> A.decimal <*> memberRole), "/_join #" *> (APIJoinGroup <$> A.decimal), "/_member role #" *> (APIMemberRole <$> A.decimal <* A.space <*> A.decimal <*> memberRole), @@ -3753,12 +3797,14 @@ chatCommandP = "/smp_servers" $> GetUserSMPServers, "/smp default" $> SetUserSMPServers (SMPServersConfig []), "/smp test " *> (TestSMPServer <$> strP), - "/_smp " *> (SetUserSMPServers <$> jsonP), + "/_smp " *> (APISetUserSMPServers <$> A.decimal <* A.space <*> jsonP), "/smp " *> (SetUserSMPServers . SMPServersConfig . map toServerCfg <$> smpServersP), + "/_smp " *> (APIGetUserSMPServers <$> A.decimal), "/smp" $> GetUserSMPServers, - "/_ttl " *> (APISetChatItemTTL <$> ciTTLDecimal), - "/ttl " *> (APISetChatItemTTL <$> ciTTL), - "/ttl" $> APIGetChatItemTTL, + "/_ttl " *> (APISetChatItemTTL <$> A.decimal <* A.space <*> ciTTLDecimal), + "/ttl " *> (SetChatItemTTL <$> ciTTL), + "/_ttl " *> (APIGetChatItemTTL <$> A.decimal), + "/ttl" $> GetChatItemTTL, "/_network " *> (APISetNetworkConfig <$> jsonP), ("/network " <|> "/net ") *> (APISetNetworkConfig <$> netCfgP), ("/network" <|> "/net") $> APIGetNetworkConfig, @@ -3786,7 +3832,7 @@ chatCommandP = ("/help settings" <|> "/hs") $> ChatHelp HSSettings, ("/help" <|> "/h") $> ChatHelp HSMain, ("/group " <|> "/g ") *> char_ '#' *> (NewGroup <$> groupProfile), - "/_group " *> (NewGroup <$> jsonP), + "/_group " *> (APINewGroup <$> A.decimal <* A.space <*> jsonP), ("/add " <|> "/a ") *> char_ '#' *> (AddMember <$> displayName <* A.space <* char_ '@' <*> displayName <*> memberRole), ("/join " <|> "/j ") *> char_ '#' *> (JoinGroup <$> displayName), ("/member role " <|> "/mr ") *> char_ '#' *> (MemberRole <$> displayName <* A.space <* char_ '@' <*> displayName <*> memberRole), @@ -3810,7 +3856,10 @@ chatCommandP = "/show link #" *> (ShowGroupLink <$> displayName), (">#" <|> "> #") *> (SendGroupMessageQuote <$> displayName <* A.space <*> pure Nothing <*> quotedMsg <*> A.takeByteString), (">#" <|> "> #") *> (SendGroupMessageQuote <$> displayName <* A.space <* char_ '@' <*> (Just <$> displayName) <* A.space <*> quotedMsg <*> A.takeByteString), + "/_contacts " *> (APIListContacts <$> A.decimal), ("/contacts" <|> "/cs") $> ListContacts, + "/_connect " *> (APIConnect <$> A.decimal <* A.space <*> ((Just <$> strP) <|> A.takeByteString $> Nothing)), + "/_connect " *> (APIAddContact <$> A.decimal), ("/connect " <|> "/c ") *> (Connect <$> ((Just <$> strP) <|> A.takeByteString $> Nothing)), ("/connect" <|> "/c") $> AddContact, SendMessage <$> chatNameP <* A.space <*> A.takeByteString, @@ -3833,9 +3882,13 @@ chatCommandP = ("/fcancel " <|> "/fc ") *> (CancelFile <$> A.decimal), ("/fstatus " <|> "/fs ") *> (FileStatus <$> A.decimal), "/simplex" $> ConnectSimplex, + "/_address " *> (APICreateMyAddress <$> A.decimal), ("/address" <|> "/ad") $> CreateMyAddress, + "/_delete_address " *> (APIDeleteMyAddress <$> A.decimal), ("/delete_address" <|> "/da") $> DeleteMyAddress, + "/_show_address " *> (APIShowMyAddress <$> A.decimal), ("/show_address" <|> "/sa") $> ShowMyAddress, + "/_auto_accept " *> (APIAddressAutoAccept <$> A.decimal <* A.space <*> autoAcceptP), "/auto_accept " *> (AddressAutoAccept <$> autoAcceptP), ("/accept " <|> "/ac ") *> char_ '@' *> (AcceptContact <$> displayName), ("/reject " <|> "/rc ") *> char_ '@' *> (RejectContact <$> displayName), diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 3ad58bcc30..a2b5790943 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -158,7 +158,7 @@ data ChatCommand | APIStorageEncryption DBEncryptionConfig | ExecChatStoreSQL Text | ExecAgentStoreSQL Text - | APIGetChats {pendingConnections :: Bool} -- UserId + | APIGetChats {userId :: UserId, pendingConnections :: Bool} | APIGetChat ChatRef ChatPagination (Maybe String) | APIGetChatItems Int | APISendMessage {chatRef :: ChatRef, liveMessage :: Bool, composedMessage :: ComposedMessage} @@ -177,9 +177,9 @@ data ChatCommand | APISendCallAnswer ContactId WebRTCSession | APISendCallExtraInfo ContactId WebRTCExtraInfo | APIEndCall ContactId - | APIGetCallInvitations -- UserId + | APIGetCallInvitations UserId | APICallStatus ContactId WebRTCCallStatus - | APIUpdateProfile Profile -- UserId + | APIUpdateProfile UserId Profile | APISetContactPrefs ContactId Preferences | APISetContactAlias ContactId LocalAlias | APISetConnectionAlias Int64 LocalAlias @@ -188,7 +188,7 @@ data ChatCommand | APIRegisterToken DeviceToken NotificationsMode | APIVerifyToken DeviceToken C.CbNonce ByteString | APIDeleteToken DeviceToken - | APIGetNtfMessage {nonce :: C.CbNonce, encNtfInfo :: ByteString} -- UserId + | APIGetNtfMessage {userId :: UserId, nonce :: C.CbNonce, encNtfInfo :: ByteString} | APIAddMember GroupId ContactId GroupMemberRole | APIJoinGroup GroupId | APIMemberRole GroupId GroupMemberId GroupMemberRole @@ -199,11 +199,15 @@ data ChatCommand | APICreateGroupLink GroupId | APIDeleteGroupLink GroupId | APIGetGroupLink GroupId - | GetUserSMPServers -- UserId - | SetUserSMPServers SMPServersConfig -- UserId + | APIGetUserSMPServers UserId + | GetUserSMPServers + | APISetUserSMPServers UserId SMPServersConfig + | SetUserSMPServers SMPServersConfig | TestSMPServer SMPServerWithAuth - | APISetChatItemTTL (Maybe Int64) -- UserId - | APIGetChatItemTTL -- UserId + | APISetChatItemTTL UserId (Maybe Int64) + | SetChatItemTTL (Maybe Int64) + | APIGetChatItemTTL UserId + | GetChatItemTTL | APISetNetworkConfig NetworkConfig | APIGetNetworkConfig | APISetChatSettings ChatRef ChatSettings @@ -226,26 +230,34 @@ data ChatCommand | VerifyGroupMember GroupName ContactName (Maybe Text) | ChatHelp HelpSection | Welcome - | AddContact -- UserId - | Connect (Maybe AConnectionRequestUri) -- UserId - | ConnectSimplex -- UserId + | APIAddContact UserId + | AddContact + | APIConnect UserId (Maybe AConnectionRequestUri) + | Connect (Maybe AConnectionRequestUri) + | ConnectSimplex -- UserId (not used in UI) | DeleteContact ContactName | ClearContact ContactName - | ListContacts -- UserId - | CreateMyAddress -- UserId - | DeleteMyAddress -- UserId - | ShowMyAddress -- UserId - | AddressAutoAccept (Maybe AutoAccept) -- UserId + | APIListContacts UserId + | ListContacts + | APICreateMyAddress UserId + | CreateMyAddress + | APIDeleteMyAddress UserId + | DeleteMyAddress + | APIShowMyAddress UserId + | ShowMyAddress + | APIAddressAutoAccept UserId (Maybe AutoAccept) + | AddressAutoAccept (Maybe AutoAccept) | AcceptContact ContactName | RejectContact ContactName | SendMessage ChatName ByteString | SendLiveMessage ChatName ByteString | SendMessageQuote {contactName :: ContactName, msgDir :: AMsgDirection, quotedMsg :: ByteString, message :: ByteString} - | SendMessageBroadcast ByteString -- UserId + | SendMessageBroadcast ByteString -- UserId (not used in UI) | DeleteMessage ChatName ByteString | EditMessage {chatName :: ChatName, editedMsg :: ByteString, message :: ByteString} | UpdateLiveMessage {chatName :: ChatName, chatItemId :: ChatItemId, liveMessage :: Bool, message :: ByteString} - | NewGroup GroupProfile -- UserId + | APINewGroup UserId GroupProfile + | NewGroup GroupProfile | AddMember GroupName ContactName GroupMemberRole | JoinGroup GroupName | MemberRole GroupName ContactName GroupMemberRole @@ -254,7 +266,7 @@ data ChatCommand | DeleteGroup GroupName | ClearGroup GroupName | ListMembers GroupName - | ListGroups -- UserId + | ListGroups -- UserId (not used in UI) | UpdateGroupNames GroupName GroupProfile | ShowGroupProfile GroupName | UpdateGroupDescription GroupName (Maybe Text) @@ -262,9 +274,9 @@ data ChatCommand | DeleteGroupLink GroupName | ShowGroupLink GroupName | SendGroupMessageQuote {groupName :: GroupName, contactName_ :: Maybe ContactName, quotedMsg :: ByteString, message :: ByteString} - | LastMessages (Maybe ChatName) Int (Maybe String) -- UserId - | LastChatItemId (Maybe ChatName) Int -- UserId - | ShowChatItem (Maybe ChatItemId) -- UserId + | LastMessages (Maybe ChatName) Int (Maybe String) -- UserId (not used in UI) + | LastChatItemId (Maybe ChatName) Int -- UserId (not used in UI) + | ShowChatItem (Maybe ChatItemId) -- UserId (not used in UI) | ShowLiveItems Bool | SendFile ChatName FilePath | SendImage ChatName FilePath @@ -273,13 +285,13 @@ data ChatCommand | ReceiveFile {fileId :: FileTransferId, fileInline :: Maybe Bool, filePath :: Maybe FilePath} | CancelFile FileTransferId | FileStatus FileTransferId - | ShowProfile -- UserId - | UpdateProfile ContactName Text -- UserId - | UpdateProfileImage (Maybe ImageData) -- UserId - | SetUserFeature AChatFeature FeatureAllowed -- UserId + | ShowProfile -- UserId (not used in UI) + | UpdateProfile ContactName Text -- UserId (not used in UI) + | UpdateProfileImage (Maybe ImageData) -- UserId (not used in UI) + | SetUserFeature AChatFeature FeatureAllowed -- UserId (not used in UI) | SetContactFeature AChatFeature ContactName (Maybe FeatureAllowed) | SetGroupFeature AGroupFeature GroupName GroupFeatureEnabled - | SetUserTimedMessages Bool -- UserId + | SetUserTimedMessages Bool -- UserId (not used in UI) | SetContactTimedMessages ContactName (Maybe TimedMessagesEnabled) | SetGroupTimedMessages GroupName (Maybe Int) | QuitChat @@ -559,6 +571,7 @@ data ChatErrorType = CENoActiveUser | CENoConnectionUser {agentConnId :: AgentConnId} | CEActiveUserExists -- TODO delete + | CEDifferentActiveUser {commandUserId :: UserId, activeUserId :: UserId} | CEChatNotStarted | CEChatNotStopped | CEChatStoreChanged diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index ef7dac8411..42bce90491 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -1140,6 +1140,7 @@ viewChatError = \case CENoActiveUser -> ["error: active user is required"] CENoConnectionUser _agentConnId -> [] -- ["error: connection has no user, conn id: " <> sShow agentConnId] CEActiveUserExists -> ["error: active user already exists"] + CEDifferentActiveUser commandUserId activeUserId -> ["error: different active user, command user id: " <> sShow commandUserId <> ", active user id: " <> sShow activeUserId] CEChatNotStarted -> ["error: chat not started"] CEChatNotStopped -> ["error: chat not stopped"] CEChatStoreChanged -> ["error: chat store changed, please restart chat"] diff --git a/tests/ChatTests.hs b/tests/ChatTests.hs index f4dbb9633c..0c7f04897c 100644 --- a/tests/ChatTests.hs +++ b/tests/ChatTests.hs @@ -241,9 +241,9 @@ testAddContact :: Spec testAddContact = versionTestMatrix2 runTestAddContact where runTestAddContact alice bob = do - alice ##> "/c" + alice ##> "/_connect 1" inv <- getInvitation alice - bob ##> ("/c " <> inv) + bob ##> ("/_connect 1 " <> inv) bob <## "confirmation sent!" concurrently_ (bob <## "alice (Alice): contact is connected") @@ -324,7 +324,7 @@ testDeleteContactDeletesProfile = -- alice deletes contact, profile is deleted alice ##> "/d bob" alice <## "bob: contact is deleted" - alice ##> "/cs" + alice ##> "/_contacts 1" (alice "/profile_image" alice <## "profile image removed" - alice ##> "/_profile {\"displayName\": \"alice2\", \"fullName\": \"\"}" + alice ##> "/_profile 1 {\"displayName\": \"alice2\", \"fullName\": \"\"}" alice <## "user profile is changed to alice2 (your contacts are notified)" bob <## "contact alice changed to alice2" bob <## "use @alice2 to send messages" @@ -2700,7 +2700,7 @@ testDeduplicateContactRequestsProfileChange = testChat3 aliceProfile bobProfile testRejectContactAndDeleteUserContact :: IO () testRejectContactAndDeleteUserContact = testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do - alice ##> "/ad" + alice ##> "/_address 1" cLink <- getContactLink alice True bob ##> ("/c " <> cLink) alice <#? bob @@ -2708,12 +2708,12 @@ testRejectContactAndDeleteUserContact = testChat3 aliceProfile bobProfile cathPr alice <## "bob: contact request rejected" (bob "/sa" + alice ##> "/_show_address 1" cLink' <- getContactLink alice False alice <## "auto_accept off" cLink' `shouldBe` cLink - alice ##> "/da" + alice ##> "/_delete_address 1" alice <## "Your chat address is deleted - accepted contacts will remain connected." alice <## "To create a new chat address use /ad" @@ -2747,7 +2747,7 @@ testAutoReplyMessage = testChat2 aliceProfile bobProfile $ \alice bob -> do alice ##> "/ad" cLink <- getContactLink alice True - alice ##> "/auto_accept on incognito=off text hello!" + alice ##> "/_auto_accept 1 on incognito=off text hello!" alice <## "auto_accept on" alice <## "auto reply:" alice <## "hello!" @@ -3182,7 +3182,7 @@ testCantSeeGlobalPrefsUpdateIncognito = testChat3 aliceProfile bobProfile cathPr cath <## "alice (Alice): contact is connected" ] alice <## "cath (Catherine): contact is connected" - alice ##> "/_profile {\"displayName\": \"alice\", \"fullName\": \"\", \"preferences\": {\"fullDelete\": {\"allow\": \"always\"}}}" + alice ##> "/_profile 1 {\"displayName\": \"alice\", \"fullName\": \"\", \"preferences\": {\"fullDelete\": {\"allow\": \"always\"}}}" alice <## "user full name removed (your contacts are notified)" alice <## "updated preferences:" alice <## "Full deletion allowed: always" @@ -3353,7 +3353,7 @@ testSetContactPrefs = testChat2 aliceProfile bobProfile $ createDirectoryIfMissing True "./tests/tmp/bob" copyFile "./tests/fixtures/test.txt" "./tests/tmp/alice/test.txt" copyFile "./tests/fixtures/test.txt" "./tests/tmp/bob/test.txt" - bob ##> "/_profile {\"displayName\": \"bob\", \"fullName\": \"Bob\", \"preferences\": {\"voice\": {\"allow\": \"no\"}}}" + bob ##> "/_profile 1 {\"displayName\": \"bob\", \"fullName\": \"Bob\", \"preferences\": {\"voice\": {\"allow\": \"no\"}}}" bob <## "profile image removed" bob <## "updated preferences:" bob <## "Voice messages allowed: no" @@ -3390,7 +3390,7 @@ testSetContactPrefs = testChat2 aliceProfile bobProfile $ alice <## "started receiving file 1 (test.txt) from bob" alice <## "completed receiving file 1 (test.txt) from bob" (bob "/_profile {\"displayName\": \"alice\", \"fullName\": \"Alice\", \"preferences\": {\"voice\": {\"allow\": \"no\"}}}" + -- alice ##> "/_profile 1 {\"displayName\": \"alice\", \"fullName\": \"Alice\", \"preferences\": {\"voice\": {\"allow\": \"no\"}}}" alice ##> "/set voice no" alice <## "updated preferences:" alice <## "Voice messages allowed: no" @@ -3403,7 +3403,7 @@ testSetContactPrefs = testChat2 aliceProfile bobProfile $ bob <## "Voice messages: off (you allow: default (no), contact allows: yes)" bob #$> ("/_get chat @2 count=100", chat, startFeatures <> [(0, "Voice messages: enabled for you"), (1, "voice message (00:10)"), (0, "Voice messages: off")]) (bob "/_profile {\"displayName\": \"bob\", \"fullName\": \"\", \"preferences\": {\"voice\": {\"allow\": \"yes\"}}}" + bob ##> "/_profile 1 {\"displayName\": \"bob\", \"fullName\": \"\", \"preferences\": {\"voice\": {\"allow\": \"yes\"}}}" bob <## "user full name removed (your contacts are notified)" bob <## "updated preferences:" bob <## "Voice messages allowed: yes" @@ -3716,7 +3716,7 @@ testGetSetSMPServers :: IO () testGetSetSMPServers = testChat2 aliceProfile bobProfile $ \alice _ -> do - alice #$> ("/smp", id, "smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:5001") + alice #$> ("/_smp 1", id, "smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:5001") alice #$> ("/smp smp://1234-w==@smp1.example.im", id, "ok") alice #$> ("/smp", id, "smp://1234-w==@smp1.example.im") alice #$> ("/smp smp://1234-w==:password@smp1.example.im", id, "ok") @@ -4081,7 +4081,7 @@ testNegotiateCall = testChat2 aliceProfile bobProfile $ \alice bob -> do connectUsers alice bob -- just for testing db query - alice ##> "/_call get" + alice ##> "/_call get 1" -- alice invite bob to call alice ##> ("/_call invite @2 " <> serialize testCallType) alice <## "ok" @@ -4308,10 +4308,10 @@ testSetChatItemTTL = alice <# "bob> 4" alice #$> ("/_get chat @2 count=100", chatF, chatFeaturesF <> [((1, "1"), Nothing), ((0, "2"), Nothing), ((1, ""), Just "test.jpg"), ((1, "3"), Nothing), ((0, "4"), Nothing)]) checkActionDeletesFile "./tests/tmp/app_files/test.jpg" $ - alice #$> ("/_ttl 2", id, "ok") + alice #$> ("/_ttl 1 2", id, "ok") alice #$> ("/_get chat @2 count=100", chat, [(1, "3"), (0, "4")]) -- when expiration is turned on, first cycle is synchronous bob #$> ("/_get chat @2 count=100", chat, chatFeatures <> [(0, "1"), (1, "2"), (0, ""), (0, "3"), (1, "4")]) - alice #$> ("/ttl", id, "old messages are set to be deleted after: 2 second(s)") + alice #$> ("/_ttl 1", id, "old messages are set to be deleted after: 2 second(s)") alice #$> ("/ttl week", id, "ok") alice #$> ("/ttl", id, "old messages are set to be deleted after: one week") alice #$> ("/ttl none", id, "ok") @@ -5042,7 +5042,7 @@ itemId i = show $ length chatFeatures + i getChats :: (Eq a, Show a) => ([(String, String, Maybe ConnStatus)] -> [a]) -> TestCC -> [a] -> Expectation getChats f cc res = do - cc ##> "/_get chats pcc=on" + cc ##> "/_get chats 1 pcc=on" line <- getTermLine cc f (read line) `shouldMatchList` res