core, ios, android: add UserId to api commands (#1696)

This commit is contained in:
JRoberts 2023-01-05 20:38:31 +04:00 committed by GitHub
parent fa9e0086f6
commit bb0482104c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 341 additions and 189 deletions

View file

@ -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<Chat> {
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<ServerCfg>, List<String>>? {
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<ServerCfg>): 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<Contact>? {
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<ServerCfg>): CC()
class APIGetUserSMPServers(val userId: Long): CC()
class APISetUserSMPServers(val userId: Long, val smpServers: List<ServerCfg>): 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"

View file

@ -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
}
}

View file

@ -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)
}

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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 ()

View file

@ -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),

View file

@ -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

View file

@ -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"]

View file

@ -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 </)
alice `hasContactProfiles` ["alice"]
-- bob deletes contact, profile is deleted
@ -1791,7 +1791,7 @@ testUpdateProfileImage =
alice <## "profile image updated"
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 <message> 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 </)
alice ##> "/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 </)
-- alice ##> "/_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 </)
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