2022-01-29 11:10:04 +00:00
//
// C h a t A P I . s w i f t
// S i m p l e X
//
// C r e a t e d b y E v g e n y P o b e r e z k i n o n 2 7 / 0 1 / 2 0 2 2 .
// C o p y r i g h t © 2 0 2 2 S i m p l e X C h a t . A l l r i g h t s r e s e r v e d .
//
import Foundation
2022-01-29 23:37:02 +00:00
import UIKit
2022-02-09 22:53:06 +00:00
import Dispatch
import BackgroundTasks
2022-05-09 09:52:09 +01:00
import SwiftUI
2022-05-31 07:55:13 +01:00
import SimpleXChat
2022-01-29 11:10:04 +00:00
private var chatController : chat_ctrl ?
2022-01-29 23:37:02 +00:00
enum TerminalItem : Identifiable {
case cmd ( Date , ChatCommand )
case resp ( Date , ChatResponse )
var id : Date {
get {
switch self {
case let . cmd ( id , _ ) : return id
case let . resp ( id , _ ) : return id
}
}
}
var label : String {
get {
switch self {
case let . cmd ( _ , cmd ) : return " > \( cmd . cmdString . prefix ( 30 ) ) "
case let . resp ( _ , resp ) : return " < \( resp . responseType ) "
}
}
}
var details : String {
get {
switch self {
case let . cmd ( _ , cmd ) : return cmd . cmdString
case let . resp ( _ , resp ) : return resp . details
2022-01-29 11:10:04 +00:00
}
}
}
}
2022-06-19 19:49:39 +01:00
func beginBGTask ( _ handler : ( ( ) -> Void ) ? = nil ) -> ( ( ) -> Void ) {
2022-02-28 10:44:48 +00:00
var id : UIBackgroundTaskIdentifier !
var running = true
let endTask = {
// l o g g e r . d e b u g ( " b e g i n B G T a s k : e n d T a s k \ ( i d . r a w V a l u e ) " )
if running {
running = false
if let h = handler {
// l o g g e r . d e b u g ( " b e g i n B G T a s k : u s e r h a n d l e r " )
h ( )
}
if id != . invalid {
UIApplication . shared . endBackgroundTask ( id )
id = . invalid
}
}
}
id = UIApplication . shared . beginBackgroundTask ( expirationHandler : endTask )
// l o g g e r . d e b u g ( " b e g i n B G T a s k : \ ( i d . r a w V a l u e ) " )
return endTask
}
let msgDelay : Double = 7.5
let maxTaskDuration : Double = 15
2022-06-24 13:52:20 +01:00
private func withBGTask < T > ( bgDelay : Double ? = nil , f : @ escaping ( ) -> T ) -> T {
2022-02-28 10:44:48 +00:00
let endTask = beginBGTask ( )
DispatchQueue . global ( ) . asyncAfter ( deadline : . now ( ) + maxTaskDuration , execute : endTask )
let r = f ( )
if let d = bgDelay {
DispatchQueue . global ( ) . asyncAfter ( deadline : . now ( ) + d , execute : endTask )
} else {
endTask ( )
}
return r
}
func chatSendCmdSync ( _ cmd : ChatCommand , bgTask : Bool = true , bgDelay : Double ? = nil ) -> ChatResponse {
2022-02-12 15:59:43 +00:00
logger . debug ( " chatSendCmd \( cmd . cmdType ) " )
2022-02-28 10:44:48 +00:00
let resp = bgTask
2022-05-03 08:20:19 +01:00
? withBGTask ( bgDelay : bgDelay ) { sendSimpleXCmd ( cmd ) }
: sendSimpleXCmd ( cmd )
2022-02-12 15:59:43 +00:00
logger . debug ( " chatSendCmd \( cmd . cmdType ) : \( resp . responseType ) " )
2022-02-18 14:33:55 +00:00
if case let . response ( _ , json ) = resp {
logger . debug ( " chatSendCmd \( cmd . cmdType ) response: \( json ) " )
}
2022-06-24 13:52:20 +01:00
DispatchQueue . main . async {
2022-09-07 12:49:41 +01:00
ChatModel . shared . terminalItems . append ( . cmd ( . now , cmd . obfuscated ) )
2022-06-24 13:52:20 +01:00
ChatModel . shared . terminalItems . append ( . resp ( . now , resp ) )
2022-02-09 22:53:06 +00:00
}
return resp
2022-01-29 11:10:04 +00:00
}
2022-02-28 10:44:48 +00:00
func chatSendCmd ( _ cmd : ChatCommand , bgTask : Bool = true , bgDelay : Double ? = nil ) async -> ChatResponse {
2022-02-24 17:16:41 +00:00
await withCheckedContinuation { cont in
2022-02-28 10:44:48 +00:00
cont . resume ( returning : chatSendCmdSync ( cmd , bgTask : bgTask , bgDelay : bgDelay ) )
2022-02-24 17:16:41 +00:00
}
}
2022-06-24 13:52:20 +01:00
func chatRecvMsg ( ) async -> ChatResponse ? {
2022-02-24 17:16:41 +00:00
await withCheckedContinuation { cont in
2022-06-24 13:52:20 +01:00
_ = withBGTask ( bgDelay : msgDelay ) { ( ) -> ChatResponse ? in
let resp = recvSimpleXMsg ( )
2022-02-28 10:44:48 +00:00
cont . resume ( returning : resp )
return resp
}
2022-02-24 17:16:41 +00:00
}
2022-01-29 11:10:04 +00:00
}
2022-02-06 18:26:22 +00:00
func apiGetActiveUser ( ) throws -> User ? {
2022-02-24 17:16:41 +00:00
let r = chatSendCmdSync ( . showActiveUser )
2022-02-06 18:26:22 +00:00
switch r {
case let . activeUser ( user ) : return user
case . chatCmdError ( . error ( . noActiveUser ) ) : return nil
default : throw r
}
}
func apiCreateActiveUser ( _ p : Profile ) throws -> User {
2022-02-24 17:16:41 +00:00
let r = chatSendCmdSync ( . createActiveUser ( profile : p ) )
2022-02-06 18:26:22 +00:00
if case let . activeUser ( user ) = r { return user }
throw r
}
2022-05-09 17:40:39 +01:00
func apiStartChat ( ) throws -> Bool {
2022-10-03 16:42:43 +04:00
let r = chatSendCmdSync ( . startChat ( subscribe : true , expire : true ) )
2022-05-09 17:40:39 +01:00
switch r {
case . chatStarted : return true
case . chatRunning : return false
default : throw r
}
2022-02-06 18:26:22 +00:00
}
2022-06-24 13:52:20 +01:00
func apiStopChat ( ) async throws {
let r = await chatSendCmd ( . apiStopChat )
2022-06-19 19:49:39 +01:00
switch r {
case . chatStopped : return
default : throw r
}
}
2022-06-27 10:28:30 +01:00
func apiActivateChat ( ) {
let r = chatSendCmdSync ( . apiActivateChat )
2022-06-19 19:49:39 +01:00
if case . cmdOk = r { return }
2022-06-27 10:28:30 +01:00
logger . error ( " apiActivateChat error: \( String ( describing : r ) ) " )
}
func apiSuspendChat ( timeoutMicroseconds : Int ) {
let r = chatSendCmdSync ( . apiSuspendChat ( timeoutMicroseconds : timeoutMicroseconds ) )
if case . cmdOk = r { return }
logger . error ( " apiSuspendChat error: \( String ( describing : r ) ) " )
2022-06-19 19:49:39 +01:00
}
2022-04-19 12:29:03 +04:00
func apiSetFilesFolder ( filesFolder : String ) throws {
let r = chatSendCmdSync ( . setFilesFolder ( filesFolder : filesFolder ) )
if case . cmdOk = r { return }
throw r
}
2022-08-23 18:18:12 +04:00
func apiSetIncognito ( incognito : Bool ) throws {
let r = chatSendCmdSync ( . setIncognito ( incognito : incognito ) )
if case . cmdOk = r { return }
throw r
}
2022-06-24 13:52:20 +01:00
func apiExportArchive ( config : ArchiveConfig ) async throws {
try await sendCommandOkResp ( . apiExportArchive ( config : config ) )
}
func apiImportArchive ( config : ArchiveConfig ) async throws {
try await sendCommandOkResp ( . apiImportArchive ( config : config ) )
}
func apiDeleteStorage ( ) async throws {
try await sendCommandOkResp ( . apiDeleteStorage )
}
2022-09-07 12:49:41 +01:00
func apiStorageEncryption ( currentKey : String = " " , newKey : String = " " ) async throws {
try await sendCommandOkResp ( . apiStorageEncryption ( config : DBEncryptionConfig ( currentKey : currentKey , newKey : newKey ) ) )
}
2022-07-02 17:18:45 +01:00
func apiGetChats ( ) throws -> [ ChatData ] {
2022-02-24 17:16:41 +00:00
let r = chatSendCmdSync ( . apiGetChats )
2022-07-02 17:18:45 +01:00
if case let . apiChats ( chats ) = r { return chats }
2022-01-30 18:27:20 +00:00
throw r
}
2022-08-17 11:43:18 +01:00
func apiGetChat ( type : ChatType , id : Int64 , search : String = " " ) throws -> Chat {
let r = chatSendCmdSync ( . apiGetChat ( type : type , id : id , pagination : . last ( count : 50 ) , search : search ) )
2022-02-02 12:51:39 +00:00
if case let . apiChat ( chat ) = r { return Chat . init ( chat ) }
2022-01-30 18:27:20 +00:00
throw r
}
2022-08-17 11:43:18 +01:00
func apiGetChatItems ( type : ChatType , id : Int64 , pagination : ChatPagination , search : String = " " ) async throws -> [ ChatItem ] {
let r = await chatSendCmd ( . apiGetChat ( type : type , id : id , pagination : pagination , search : search ) )
2022-08-15 21:07:11 +01:00
if case let . apiChat ( chat ) = r { return chat . chatItems }
throw r
}
2022-08-17 11:43:18 +01:00
func loadChat ( chat : Chat , search : String = " " ) {
2022-07-02 17:18:45 +01:00
do {
let cInfo = chat . chatInfo
let m = ChatModel . shared
2022-08-29 14:08:46 +01:00
m . reversedChatItems = [ ]
let chat = try apiGetChat ( type : cInfo . chatType , id : cInfo . apiId , search : search )
2022-07-02 17:18:45 +01:00
m . updateChatInfo ( chat . chatInfo )
2022-08-15 21:07:11 +01:00
m . reversedChatItems = chat . chatItems . reversed ( )
2022-07-02 17:18:45 +01:00
} catch let error {
logger . error ( " loadChat error: \( responseError ( error ) ) " )
}
}
2022-09-21 17:18:48 +04:00
func apiSendMessage ( type : ChatType , id : Int64 , file : String ? , quotedItemId : Int64 ? , msg : MsgContent ) async -> ChatItem ? {
2022-02-28 10:44:48 +00:00
let chatModel = ChatModel . shared
2022-04-19 12:29:03 +04:00
let cmd : ChatCommand = . apiSendMessage ( type : type , id : id , file : file , quotedItemId : quotedItemId , msg : msg )
2022-02-28 10:44:48 +00:00
let r : ChatResponse
if type = = . direct {
var cItem : ChatItem !
let endTask = beginBGTask ( { if cItem != nil { chatModel . messageDelivery . removeValue ( forKey : cItem . id ) } } )
r = await chatSendCmd ( cmd , bgTask : false )
if case let . newChatItem ( aChatItem ) = r {
cItem = aChatItem . chatItem
chatModel . messageDelivery [ cItem . id ] = endTask
return cItem
}
2022-09-21 17:18:48 +04:00
if ! networkErrorAlert ( r ) {
sendMessageErrorAlert ( r )
}
2022-02-28 10:44:48 +00:00
endTask ( )
2022-09-21 17:18:48 +04:00
return nil
2022-02-28 10:44:48 +00:00
} else {
r = await chatSendCmd ( cmd , bgDelay : msgDelay )
if case let . newChatItem ( aChatItem ) = r {
return aChatItem . chatItem
}
2022-09-21 17:18:48 +04:00
sendMessageErrorAlert ( r )
return nil
2022-02-28 10:44:48 +00:00
}
2022-09-21 17:18:48 +04:00
}
private func sendMessageErrorAlert ( _ r : ChatResponse ) {
logger . error ( " apiSendMessage error: \( String ( describing : r ) ) " )
AlertManager . shared . showAlertMsg (
title : " Error sending message " ,
message : " Error: \( String ( describing : r ) ) "
)
2022-01-30 18:27:20 +00:00
}
2022-03-30 20:37:47 +04:00
func apiUpdateChatItem ( type : ChatType , id : Int64 , itemId : Int64 , msg : MsgContent ) async throws -> ChatItem {
let r = await chatSendCmd ( . apiUpdateChatItem ( type : type , id : id , itemId : itemId , msg : msg ) , bgDelay : msgDelay )
2022-03-25 22:26:05 +04:00
if case let . chatItemUpdated ( aChatItem ) = r { return aChatItem . chatItem }
throw r
}
2022-03-30 20:37:47 +04:00
func apiDeleteChatItem ( type : ChatType , id : Int64 , itemId : Int64 , mode : CIDeleteMode ) async throws -> ChatItem {
let r = await chatSendCmd ( . apiDeleteChatItem ( type : type , id : id , itemId : itemId , mode : mode ) , bgDelay : msgDelay )
if case let . chatItemDeleted ( _ , toChatItem ) = r { return toChatItem . chatItem }
2022-03-25 22:26:05 +04:00
throw r
}
2022-07-09 09:29:56 +01:00
func apiGetNtfToken ( ) -> ( DeviceToken ? , NtfTknStatus ? , NotificationsMode ) {
2022-07-01 09:49:30 +01:00
let r = chatSendCmdSync ( . apiGetNtfToken )
switch r {
case let . ntfToken ( token , status , ntfMode ) : return ( token , status , ntfMode )
case . chatCmdError ( . errorAgent ( . CMD ( . PROHIBITED ) ) ) : return ( nil , nil , . off )
2022-07-09 09:29:56 +01:00
default :
logger . debug ( " apiGetNtfToken response: \( String ( describing : r ) , privacy : . public ) " )
return ( nil , nil , . off )
2022-07-01 09:49:30 +01:00
}
}
func apiRegisterToken ( token : DeviceToken , notificationMode : NotificationsMode ) async throws -> NtfTknStatus {
2022-06-27 10:28:30 +01:00
let r = await chatSendCmd ( . apiRegisterToken ( token : token , notificationMode : notificationMode ) )
2022-04-23 06:32:16 +01:00
if case let . ntfTokenStatus ( status ) = r { return status }
throw r
2022-04-21 20:04:22 +01:00
}
2022-07-01 09:49:30 +01:00
func registerToken ( token : DeviceToken ) {
let m = ChatModel . shared
let mode = m . notificationMode
2022-07-09 09:29:56 +01:00
if mode != . off && ! m . tokenRegistered {
m . tokenRegistered = true
2022-07-01 09:49:30 +01:00
logger . debug ( " registerToken \( mode . rawValue ) " )
Task {
do {
let status = try await apiRegisterToken ( token : token , notificationMode : mode )
await MainActor . run { m . tokenStatus = status }
} catch let error {
logger . error ( " registerToken apiRegisterToken error: \( responseError ( error ) ) " )
}
}
}
}
2022-06-27 23:03:27 +01:00
func apiVerifyToken ( token : DeviceToken , nonce : String , code : String ) async throws {
try await sendCommandOkResp ( . apiVerifyToken ( token : token , nonce : nonce , code : code ) )
2022-04-22 13:46:05 +01:00
}
2022-06-27 23:03:27 +01:00
func apiDeleteToken ( token : DeviceToken ) async throws {
2022-04-22 13:46:05 +01:00
try await sendCommandOkResp ( . apiDeleteToken ( token : token ) )
2022-04-21 20:04:22 +01:00
}
2022-11-21 08:37:13 +00:00
func getUserSMPServers ( ) throws -> ( [ ServerCfg ] , [ String ] ) {
2022-03-10 15:45:40 +04:00
let r = chatSendCmdSync ( . getUserSMPServers )
2022-11-21 08:37:13 +00:00
if case let . userSMPServers ( smpServers , presetServers ) = r { return ( smpServers , presetServers ) }
2022-03-10 15:45:40 +04:00
throw r
}
2022-11-21 08:37:13 +00:00
func setUserSMPServers ( smpServers : [ ServerCfg ] ) async throws {
2022-04-22 13:46:05 +01:00
try await sendCommandOkResp ( . setUserSMPServers ( smpServers : smpServers ) )
2022-03-10 15:45:40 +04:00
}
2022-11-16 15:37:20 +00:00
func testSMPServer ( smpServer : String ) async throws -> Result < ( ) , SMPTestFailure > {
let r = await chatSendCmd ( . testSMPServer ( smpServer : smpServer ) )
2022-11-21 08:37:13 +00:00
if case let . smpTestResult ( testFailure ) = r {
2022-11-16 15:37:20 +00:00
if let t = testFailure {
return . failure ( t )
}
return . success ( ( ) )
}
throw r
}
2022-10-03 16:42:43 +04:00
func getChatItemTTL ( ) throws -> ChatItemTTL {
let r = chatSendCmdSync ( . apiGetChatItemTTL )
if case let . chatItemTTL ( chatItemTTL ) = r { return ChatItemTTL ( chatItemTTL ) }
throw r
}
func setChatItemTTL ( _ chatItemTTL : ChatItemTTL ) async throws {
try await sendCommandOkResp ( . apiSetChatItemTTL ( seconds : chatItemTTL . seconds ) )
}
2022-07-26 07:29:48 +01:00
func getNetworkConfig ( ) async throws -> NetCfg ? {
let r = await chatSendCmd ( . apiGetNetworkConfig )
if case let . networkConfig ( cfg ) = r { return cfg }
throw r
}
2022-08-03 12:36:51 +01:00
func setNetworkConfig ( _ cfg : NetCfg ) throws {
let r = chatSendCmdSync ( . apiSetNetworkConfig ( networkConfig : cfg ) )
if case . cmdOk = r { return }
throw r
2022-07-26 07:29:48 +01:00
}
2022-08-20 12:47:48 +01:00
func apiSetChatSettings ( type : ChatType , id : Int64 , chatSettings : ChatSettings ) async throws {
try await sendCommandOkResp ( . apiSetChatSettings ( type : type , id : id , chatSettings : chatSettings ) )
}
2022-08-23 18:18:12 +04:00
func apiContactInfo ( contactId : Int64 ) async throws -> ( ConnectionStats ? , Profile ? ) {
2022-07-26 07:29:48 +01:00
let r = await chatSendCmd ( . apiContactInfo ( contactId : contactId ) )
2022-08-23 18:18:12 +04:00
if case let . contactInfo ( _ , connStats , customUserProfile ) = r { return ( connStats , customUserProfile ) }
2022-07-26 07:29:48 +01:00
throw r
}
2022-08-29 14:47:29 +04:00
func apiGroupMemberInfo ( _ groupId : Int64 , _ groupMemberId : Int64 ) async throws -> ( ConnectionStats ? ) {
2022-07-26 07:29:48 +01:00
let r = await chatSendCmd ( . apiGroupMemberInfo ( groupId : groupId , groupMemberId : groupMemberId ) )
2022-08-29 14:47:29 +04:00
if case let . groupMemberInfo ( _ , _ , connStats_ ) = r { return ( connStats_ ) }
2022-07-26 07:29:48 +01:00
throw r
}
2022-11-01 20:30:53 +00:00
func apiSwitchContact ( contactId : Int64 ) async throws {
try await sendCommandOkResp ( . apiSwitchContact ( contactId : contactId ) )
}
func apiSwitchGroupMember ( _ groupId : Int64 , _ groupMemberId : Int64 ) async throws {
try await sendCommandOkResp ( . apiSwitchGroupMember ( groupId : groupId , groupMemberId : groupMemberId ) )
}
2022-09-21 16:28:01 +01:00
func apiAddContact ( ) async -> String ? {
let r = await chatSendCmd ( . addContact , bgTask : false )
2022-01-30 18:27:20 +00:00
if case let . invitation ( connReqInvitation ) = r { return connReqInvitation }
2022-09-21 17:18:48 +04:00
connectionErrorAlert ( r )
return nil
2022-01-29 23:37:02 +00:00
}
2022-09-21 17:18:48 +04:00
func apiConnect ( connReq : String ) async -> ConnReqType ? {
2022-02-24 17:16:41 +00:00
let r = await chatSendCmd ( . connect ( connReq : connReq ) )
2022-04-02 14:35:35 +01:00
let am = AlertManager . shared
2022-01-29 23:37:02 +00:00
switch r {
2022-04-25 07:54:07 +01:00
case . sentConfirmation : return . invitation
case . sentInvitation : return . contact
2022-04-02 14:35:35 +01:00
case let . contactAlreadyExists ( contact ) :
2022-11-11 12:30:10 +04:00
let m = ChatModel . shared
if let c = m . getContactChat ( contact . contactId ) {
await MainActor . run { m . chatId = c . id }
}
2022-04-02 14:35:35 +01:00
am . showAlertMsg (
title : " Contact already exists " ,
2022-11-11 12:30:10 +04:00
message : " You are already connected to \( contact . displayName ) . "
2022-04-02 14:35:35 +01:00
)
2022-04-25 07:54:07 +01:00
return nil
2022-04-02 14:35:35 +01:00
case . chatCmdError ( . error ( . invalidConnReq ) ) :
am . showAlertMsg (
title : " Invalid connection link " ,
message : " Please check that you used the correct link or ask your contact to send you another one. "
)
2022-04-25 07:54:07 +01:00
return nil
2022-04-21 11:50:24 +04:00
case . chatCmdError ( . errorAgent ( . SMP ( . AUTH ) ) ) :
am . showAlertMsg (
title : " Connection error (AUTH) " ,
message : " Unless your contact deleted the connection or this link was already used, it might be a bug - please report it. \n To connect, please ask your contact to create another connection link and check that you have a stable network connection. "
)
2022-04-25 07:54:07 +01:00
return nil
2022-05-09 09:52:09 +01:00
case let . chatCmdError ( . errorAgent ( . INTERNAL ( internalErr ) ) ) :
if internalErr = = " SEUniqueID " {
am . showAlertMsg (
title : " Already connected? " ,
message : " It seems like you are already connected via this link. If it is not the case, there was an error ( \( responseError ( r ) ) ). "
)
return nil
}
2022-09-21 17:18:48 +04:00
default : ( )
}
connectionErrorAlert ( r )
return nil
}
private func connectionErrorAlert ( _ r : ChatResponse ) {
if ! networkErrorAlert ( r ) {
AlertManager . shared . showAlertMsg (
title : " Connection error " ,
message : " Error: \( String ( describing : r ) ) "
)
2022-01-29 23:37:02 +00:00
}
}
2022-02-24 17:16:41 +00:00
func apiDeleteChat ( type : ChatType , id : Int64 ) async throws {
2022-02-28 10:44:48 +00:00
let r = await chatSendCmd ( . apiDeleteChat ( type : type , id : id ) , bgTask : false )
2022-04-25 10:39:28 +01:00
if case . direct = type , case . contactDeleted = r { return }
if case . contactConnection = type , case . contactConnectionDeleted = r { return }
2022-07-19 18:21:15 +04:00
if case . group = type , case . groupDeletedUser = r { return }
2022-01-31 21:28:07 +00:00
throw r
}
2022-07-19 18:21:15 +04:00
func deleteChat ( _ chat : Chat ) async {
do {
let cInfo = chat . chatInfo
try await apiDeleteChat ( type : cInfo . chatType , id : cInfo . apiId )
DispatchQueue . main . async { ChatModel . shared . removeChat ( cInfo . id ) }
2022-09-21 17:18:48 +04:00
} catch let error {
2022-07-19 18:21:15 +04:00
logger . error ( " deleteChat apiDeleteChat error: \( responseError ( error ) ) " )
2022-10-20 19:27:00 +04:00
AlertManager . shared . showAlertMsg (
title : " Error deleting chat! " ,
message : " Error: \( responseError ( error ) ) "
)
2022-07-19 18:21:15 +04:00
}
}
2022-05-20 12:00:58 +04:00
func apiClearChat ( type : ChatType , id : Int64 ) async throws -> ChatInfo {
2022-05-17 22:48:54 +04:00
let r = await chatSendCmd ( . apiClearChat ( type : type , id : id ) , bgTask : false )
2022-05-20 12:00:58 +04:00
if case let . chatCleared ( updatedChatInfo ) = r { return updatedChatInfo }
2022-05-17 22:48:54 +04:00
throw r
}
func clearChat ( _ chat : Chat ) async {
do {
let cInfo = chat . chatInfo
2022-05-20 12:00:58 +04:00
let updatedChatInfo = try await apiClearChat ( type : cInfo . chatType , id : cInfo . apiId )
DispatchQueue . main . async { ChatModel . shared . clearChat ( updatedChatInfo ) }
2022-05-17 22:48:54 +04:00
} catch {
logger . error ( " clearChat apiClearChat error: \( responseError ( error ) ) " )
}
}
2022-07-26 10:55:58 +04:00
func apiListContacts ( ) throws -> [ Contact ] {
let r = chatSendCmdSync ( . listContacts )
if case let . contactsList ( contacts ) = r { return contacts }
throw r
}
2022-02-24 17:16:41 +00:00
func apiUpdateProfile ( profile : Profile ) async throws -> Profile ? {
2022-03-25 22:13:01 +04:00
let r = await chatSendCmd ( . apiUpdateProfile ( profile : profile ) )
2022-01-31 21:28:07 +00:00
switch r {
case . userProfileNoChange : return nil
case let . userProfileUpdated ( _ , toProfile ) : return toProfile
default : throw r
}
}
2022-11-16 20:26:43 +04:00
func apiSetContactPrefs ( contactId : Int64 , preferences : Preferences ) async throws -> Contact ? {
let r = await chatSendCmd ( . apiSetContactPrefs ( contactId : contactId , preferences : preferences ) )
if case let . contactPrefsUpdated ( _ , toContact ) = r { return toContact }
throw r
}
2022-08-25 17:36:26 +04:00
func apiSetContactAlias ( contactId : Int64 , localAlias : String ) async throws -> Contact ? {
let r = await chatSendCmd ( . apiSetContactAlias ( contactId : contactId , localAlias : localAlias ) )
if case let . contactAliasUpdated ( toContact ) = r { return toContact }
throw r
}
2022-10-01 10:57:18 +01:00
func apiSetConnectionAlias ( connId : Int64 , localAlias : String ) async throws -> PendingContactConnection ? {
let r = await chatSendCmd ( . apiSetConnectionAlias ( connId : connId , localAlias : localAlias ) )
if case let . connectionAliasUpdated ( toConnection ) = r { return toConnection }
throw r
}
2022-02-24 17:16:41 +00:00
func apiCreateUserAddress ( ) async throws -> String {
let r = await chatSendCmd ( . createMyAddress )
2022-02-01 17:34:06 +00:00
if case let . userContactLinkCreated ( connReq ) = r { return connReq }
throw r
}
2022-02-24 17:16:41 +00:00
func apiDeleteUserAddress ( ) async throws {
let r = await chatSendCmd ( . deleteMyAddress )
2022-02-01 17:34:06 +00:00
if case . userContactLinkDeleted = r { return }
throw r
}
2022-10-23 11:16:56 +01:00
func apiGetUserAddress ( ) throws -> UserContactLink ? {
2022-04-19 12:29:03 +04:00
let r = chatSendCmdSync ( . showMyAddress )
2022-02-01 17:34:06 +00:00
switch r {
2022-10-23 11:16:56 +01:00
case let . userContactLink ( contactLink ) : return contactLink
case . chatCmdError ( chatError : . errorStore ( storeError : . userContactLinkNotFound ) ) : return nil
default : throw r
}
}
func userAddressAutoAccept ( _ autoAccept : AutoAccept ? ) async throws -> UserContactLink ? {
let r = await chatSendCmd ( . addressAutoAccept ( autoAccept : autoAccept ) )
switch r {
case let . userContactLinkUpdated ( contactLink ) : return contactLink
case . chatCmdError ( chatError : . errorStore ( storeError : . userContactLinkNotFound ) ) : return nil
2022-02-01 17:34:06 +00:00
default : throw r
}
}
2022-09-21 17:18:48 +04:00
func apiAcceptContactRequest ( contactReqId : Int64 ) async -> Contact ? {
2022-02-24 17:16:41 +00:00
let r = await chatSendCmd ( . apiAcceptContact ( contactReqId : contactReqId ) )
2022-09-21 17:18:48 +04:00
let am = AlertManager . shared
2022-02-01 17:34:06 +00:00
if case let . acceptingContactRequest ( contact ) = r { return contact }
2022-09-21 17:18:48 +04:00
if case . chatCmdError ( . errorAgent ( . SMP ( . AUTH ) ) ) = r {
am . showAlertMsg (
title : " Connection error (AUTH) " ,
message : " Sender may have deleted the connection request. "
)
} else if ! networkErrorAlert ( r ) {
logger . error ( " apiAcceptContactRequest error: \( String ( describing : r ) ) " )
am . showAlertMsg (
title : " Error accepting contact request " ,
message : " Error: \( String ( describing : r ) ) "
)
}
return nil
2022-02-01 17:34:06 +00:00
}
2022-02-24 17:16:41 +00:00
func apiRejectContactRequest ( contactReqId : Int64 ) async throws {
let r = await chatSendCmd ( . apiRejectContact ( contactReqId : contactReqId ) )
2022-02-01 17:34:06 +00:00
if case . contactRequestRejected = r { return }
throw r
}
2022-02-24 17:16:41 +00:00
func apiChatRead ( type : ChatType , id : Int64 , itemRange : ( Int64 , Int64 ) ) async throws {
2022-04-22 13:46:05 +01:00
try await sendCommandOkResp ( . apiChatRead ( type : type , id : id , itemRange : itemRange ) )
2022-02-12 15:59:43 +00:00
}
2022-10-21 12:32:11 +01:00
func apiChatUnread ( type : ChatType , id : Int64 , unreadChat : Bool ) async throws {
try await sendCommandOkResp ( . apiChatUnread ( type : type , id : id , unreadChat : unreadChat ) )
}
2022-05-04 09:10:36 +04:00
func receiveFile ( fileId : Int64 ) async {
2022-11-02 10:32:08 +00:00
let inline = privacyTransferImagesInlineGroupDefault . get ( )
if let chatItem = await apiReceiveFile ( fileId : fileId , inline : inline ) {
2022-05-04 09:10:36 +04:00
DispatchQueue . main . async { chatItemSimpleUpdate ( chatItem ) }
}
}
2022-11-02 10:32:08 +00:00
func apiReceiveFile ( fileId : Int64 , inline : Bool ) async -> AChatItem ? {
let r = await chatSendCmd ( . receiveFile ( fileId : fileId , inline : inline ) )
2022-09-21 17:18:48 +04:00
let am = AlertManager . shared
2022-07-10 14:28:00 +01:00
if case let . rcvFileAccepted ( chatItem ) = r { return chatItem }
2022-09-21 17:18:48 +04:00
if case . rcvFileAcceptedSndCancelled = r {
am . showAlertMsg (
title : " Cannot receive file " ,
message : " Sender cancelled file transfer. "
)
} else if ! networkErrorAlert ( r ) {
logger . error ( " apiReceiveFile error: \( String ( describing : r ) ) " )
am . showAlertMsg (
title : " Error receiving file " ,
message : " Error: \( String ( describing : r ) ) "
)
}
return nil
}
func networkErrorAlert ( _ r : ChatResponse ) -> Bool {
let am = AlertManager . shared
switch r {
case . chatCmdError ( . errorAgent ( . BROKER ( . TIMEOUT ) ) ) :
am . showAlertMsg (
title : " Connection timeout " ,
message : " Please check your network connection and try again. "
)
return true
case . chatCmdError ( . errorAgent ( . BROKER ( . NETWORK ) ) ) :
am . showAlertMsg (
title : " Connection error " ,
message : " Please check your network connection and try again. "
)
return true
default :
return false
}
2022-04-19 12:29:03 +04:00
}
2022-02-24 17:16:41 +00:00
func acceptContactRequest ( _ contactRequest : UserContactRequest ) async {
2022-09-21 17:18:48 +04:00
if let contact = await apiAcceptContactRequest ( contactReqId : contactRequest . apiId ) {
2022-02-09 22:53:06 +00:00
let chat = Chat ( chatInfo : ChatInfo . direct ( contact : contact ) , chatItems : [ ] )
2022-02-24 17:16:41 +00:00
DispatchQueue . main . async { ChatModel . shared . replaceChat ( contactRequest . id , chat ) }
2022-02-09 22:53:06 +00:00
}
}
2022-02-24 17:16:41 +00:00
func rejectContactRequest ( _ contactRequest : UserContactRequest ) async {
2022-02-09 22:53:06 +00:00
do {
2022-02-24 17:16:41 +00:00
try await apiRejectContactRequest ( contactReqId : contactRequest . apiId )
DispatchQueue . main . async { ChatModel . shared . removeChat ( contactRequest . id ) }
2022-02-09 22:53:06 +00:00
} catch let error {
2022-04-25 10:39:28 +01:00
logger . error ( " rejectContactRequest: \( responseError ( error ) ) " )
2022-02-09 22:53:06 +00:00
}
}
2022-05-07 06:40:46 +01:00
func apiSendCallInvitation ( _ contact : Contact , _ callType : CallType ) async throws {
try await sendCommandOkResp ( . apiSendCallInvitation ( contact : contact , callType : callType ) )
}
func apiRejectCall ( _ contact : Contact ) async throws {
try await sendCommandOkResp ( . apiRejectCall ( contact : contact ) )
}
2022-05-16 19:27:58 +01:00
func apiSendCallOffer ( _ contact : Contact , _ rtcSession : String , _ rtcIceCandidates : String , media : CallMediaType , capabilities : CallCapabilities ) async throws {
2022-05-07 06:40:46 +01:00
let webRtcSession = WebRTCSession ( rtcSession : rtcSession , rtcIceCandidates : rtcIceCandidates )
let callOffer = WebRTCCallOffer ( callType : CallType ( media : media , capabilities : capabilities ) , rtcSession : webRtcSession )
try await sendCommandOkResp ( . apiSendCallOffer ( contact : contact , callOffer : callOffer ) )
}
2022-05-16 19:27:58 +01:00
func apiSendCallAnswer ( _ contact : Contact , _ rtcSession : String , _ rtcIceCandidates : String ) async throws {
2022-05-07 06:40:46 +01:00
let answer = WebRTCSession ( rtcSession : rtcSession , rtcIceCandidates : rtcIceCandidates )
try await sendCommandOkResp ( . apiSendCallAnswer ( contact : contact , answer : answer ) )
}
2022-05-16 19:27:58 +01:00
func apiSendCallExtraInfo ( _ contact : Contact , _ rtcIceCandidates : String ) async throws {
2022-05-07 06:40:46 +01:00
let extraInfo = WebRTCExtraInfo ( rtcIceCandidates : rtcIceCandidates )
try await sendCommandOkResp ( . apiSendCallExtraInfo ( contact : contact , extraInfo : extraInfo ) )
}
func apiEndCall ( _ contact : Contact ) async throws {
try await sendCommandOkResp ( . apiEndCall ( contact : contact ) )
}
2022-07-05 15:15:15 +04:00
func apiGetCallInvitations ( ) throws -> [ RcvCallInvitation ] {
let r = chatSendCmdSync ( . apiGetCallInvitations )
if case let . callInvitations ( invs ) = r { return invs }
throw r
}
2022-05-07 06:40:46 +01:00
func apiCallStatus ( _ contact : Contact , _ status : String ) async throws {
if let callStatus = WebRTCCallStatus . init ( rawValue : status ) {
try await sendCommandOkResp ( . apiCallStatus ( contact : contact , callStatus : callStatus ) )
} else {
logger . debug ( " apiCallStatus: call status \( status ) not used " )
}
}
2022-08-16 13:13:29 +01:00
func markChatRead ( _ chat : Chat , aboveItem : ChatItem ? = nil ) async {
2022-02-12 15:59:43 +00:00
do {
2022-10-21 12:32:11 +01:00
if chat . chatStats . unreadCount > 0 {
let minItemId = chat . chatStats . minUnreadItemId
let itemRange = ( minItemId , aboveItem ? . id ? ? chat . chatItems . last ? . id ? ? minItemId )
let cInfo = chat . chatInfo
try await apiChatRead ( type : cInfo . chatType , id : cInfo . apiId , itemRange : itemRange )
await MainActor . run { ChatModel . shared . markChatItemsRead ( cInfo , aboveItem : aboveItem ) }
}
if chat . chatStats . unreadChat {
await markChatUnread ( chat , unreadChat : false )
}
2022-02-12 15:59:43 +00:00
} catch {
2022-04-25 10:39:28 +01:00
logger . error ( " markChatRead apiChatRead error: \( responseError ( error ) ) " )
2022-02-12 15:59:43 +00:00
}
}
2022-10-21 12:32:11 +01:00
func markChatUnread ( _ chat : Chat , unreadChat : Bool = true ) async {
do {
let cInfo = chat . chatInfo
try await apiChatUnread ( type : cInfo . chatType , id : cInfo . apiId , unreadChat : unreadChat )
await MainActor . run { ChatModel . shared . markChatUnread ( cInfo , unreadChat : unreadChat ) }
} catch {
logger . error ( " markChatUnread apiChatUnread error: \( responseError ( error ) ) " )
}
}
2022-05-03 08:20:19 +01:00
func apiMarkChatItemRead ( _ cInfo : ChatInfo , _ cItem : ChatItem ) async {
2022-02-12 15:59:43 +00:00
do {
2022-08-15 21:07:11 +01:00
logger . debug ( " apiMarkChatItemRead: \( cItem . id ) " )
2022-02-24 17:16:41 +00:00
try await apiChatRead ( type : cInfo . chatType , id : cInfo . apiId , itemRange : ( cItem . id , cItem . id ) )
2022-08-15 21:07:11 +01:00
await MainActor . run { ChatModel . shared . markChatItemRead ( cInfo , cItem ) }
2022-02-12 15:59:43 +00:00
} catch {
2022-08-15 21:07:11 +01:00
logger . error ( " apiMarkChatItemRead apiChatRead error: \( responseError ( error ) ) " )
2022-02-12 15:59:43 +00:00
}
}
2022-04-22 13:46:05 +01:00
private func sendCommandOkResp ( _ cmd : ChatCommand ) async throws {
let r = await chatSendCmd ( cmd )
if case . cmdOk = r { return }
throw r
}
2022-07-28 11:49:36 +01:00
func apiNewGroup ( _ p : GroupProfile ) throws -> GroupInfo {
let r = chatSendCmdSync ( . newGroup ( groupProfile : p ) )
2022-07-14 16:40:32 +04:00
if case let . groupCreated ( groupInfo ) = r { return groupInfo }
throw r
}
2022-08-10 14:54:15 +04:00
func apiAddMember ( _ groupId : Int64 , _ contactId : Int64 , _ memberRole : GroupMemberRole ) async throws -> GroupMember {
2022-07-26 10:55:58 +04:00
let r = await chatSendCmd ( . apiAddMember ( groupId : groupId , contactId : contactId , memberRole : memberRole ) )
2022-10-24 16:22:00 +04:00
if case let . sentGroupInvitation ( _ , _ , member ) = r { return member }
2022-07-26 10:55:58 +04:00
throw r
}
2022-08-01 08:34:07 +01:00
enum JoinGroupResult {
case joined ( groupInfo : GroupInfo )
case invitationRemoved
case groupNotFound
2022-07-18 21:58:32 +04:00
}
2022-08-01 08:34:07 +01:00
func apiJoinGroup ( _ groupId : Int64 ) async throws -> JoinGroupResult {
2022-07-18 21:58:32 +04:00
let r = await chatSendCmd ( . apiJoinGroup ( groupId : groupId ) )
2022-08-01 08:34:07 +01:00
switch r {
2022-11-05 17:48:57 +04:00
case let . userAcceptedGroupSent ( groupInfo , _ ) : return . joined ( groupInfo : groupInfo )
2022-08-01 08:34:07 +01:00
case . chatCmdError ( . errorAgent ( . SMP ( . AUTH ) ) ) : return . invitationRemoved
case . chatCmdError ( . errorStore ( . groupNotFound ) ) : return . groupNotFound
default : throw r
}
2022-07-18 21:58:32 +04:00
}
2022-08-09 13:43:19 +04:00
func apiRemoveMember ( _ groupId : Int64 , _ memberId : Int64 ) async throws -> GroupMember {
2022-07-26 12:33:10 +04:00
let r = await chatSendCmd ( . apiRemoveMember ( groupId : groupId , memberId : memberId ) , bgTask : false )
if case let . userDeletedMember ( _ , member ) = r { return member }
throw r
}
2022-10-04 09:53:43 +01:00
func apiMemberRole ( _ groupId : Int64 , _ memberId : Int64 , _ memberRole : GroupMemberRole ) async throws -> GroupMember {
let r = await chatSendCmd ( . apiMemberRole ( groupId : groupId , memberId : memberId , memberRole : memberRole ) , bgTask : false )
if case let . memberRoleUser ( _ , member , _ , _ ) = r { return member }
throw r
}
2022-07-26 10:55:58 +04:00
func leaveGroup ( _ groupId : Int64 ) async {
2022-07-19 18:21:15 +04:00
do {
2022-07-26 10:55:58 +04:00
let groupInfo = try await apiLeaveGroup ( groupId )
2022-07-19 18:21:15 +04:00
DispatchQueue . main . async { ChatModel . shared . updateGroup ( groupInfo ) }
} catch let error {
logger . error ( " leaveGroup error: \( responseError ( error ) ) " )
}
}
2022-07-26 10:55:58 +04:00
func apiLeaveGroup ( _ groupId : Int64 ) async throws -> GroupInfo {
2022-07-19 18:21:15 +04:00
let r = await chatSendCmd ( . apiLeaveGroup ( groupId : groupId ) , bgTask : false )
if case let . leftMemberUser ( groupInfo ) = r { return groupInfo }
throw r
}
2022-07-26 10:55:58 +04:00
func apiListMembers ( _ groupId : Int64 ) async -> [ GroupMember ] {
let r = await chatSendCmd ( . apiListMembers ( groupId : groupId ) )
if case let . groupMembers ( group ) = r { return group . members }
return [ ]
}
2022-07-30 13:03:44 +01:00
func filterMembersToAdd ( _ ms : [ GroupMember ] ) -> [ Contact ] {
let memberContactIds = ms . compactMap { m in m . memberCurrent ? m . memberContactId : nil }
return ChatModel . shared . chats
. compactMap { $0 . chatInfo . contact }
. filter { ! memberContactIds . contains ( $0 . apiId ) }
. sorted { $0 . displayName . lowercased ( ) < $1 . displayName . lowercased ( ) }
}
func apiUpdateGroup ( _ groupId : Int64 , _ groupProfile : GroupProfile ) async throws -> GroupInfo {
let r = await chatSendCmd ( . apiUpdateGroupProfile ( groupId : groupId , groupProfile : groupProfile ) )
if case let . groupUpdated ( toGroup ) = r { return toGroup }
throw r
}
2022-10-15 18:09:25 +04:00
func apiCreateGroupLink ( _ groupId : Int64 ) async throws -> String {
let r = await chatSendCmd ( . apiCreateGroupLink ( groupId : groupId ) )
if case let . groupLinkCreated ( _ , connReq ) = r { return connReq }
throw r
}
func apiDeleteGroupLink ( _ groupId : Int64 ) async throws {
let r = await chatSendCmd ( . apiDeleteGroupLink ( groupId : groupId ) )
if case . groupLinkDeleted = r { return }
throw r
}
func apiGetGroupLink ( _ groupId : Int64 ) throws -> String ? {
let r = chatSendCmdSync ( . apiGetGroupLink ( groupId : groupId ) )
switch r {
case let . groupLink ( _ , connReq ) :
return connReq
case . chatCmdError ( chatError : . errorStore ( storeError : . groupLinkNotFound ) ) :
return nil
default : throw r
}
}
2022-09-07 12:49:41 +01:00
func initializeChat ( start : Bool , dbKey : String ? = nil ) throws {
2022-05-09 09:52:09 +01:00
logger . debug ( " initializeChat " )
2022-09-07 12:49:41 +01:00
let m = ChatModel . shared
2022-09-24 09:28:22 +01:00
( m . chatDbEncrypted , m . chatDbStatus ) = chatMigrateInit ( dbKey )
2022-09-07 12:49:41 +01:00
if m . chatDbStatus != . ok { return }
2022-09-17 16:41:20 +04:00
// I f w e m i g r a t e d s u c c e s s f u l l y m e a n s p r e v i o u s r e - e n c r y p t i o n p r o c e s s o n d a t a b a s e l e v e l f i n i s h e d s u c c e s s f u l l y t o o
if encryptionStartedDefault . get ( ) {
encryptionStartedDefault . set ( false )
}
2022-09-07 12:49:41 +01:00
try apiSetFilesFolder ( filesFolder : getAppFilesDirectory ( ) . path )
try apiSetIncognito ( incognito : incognitoGroupDefault . get ( ) )
2022-09-23 12:51:40 +01:00
m . chatInitialized = true
2022-09-07 12:49:41 +01:00
m . currentUser = try apiGetActiveUser ( )
if m . currentUser = = nil {
m . onboardingStage = . step1_SimpleXInfo
} else if start {
try startChat ( )
} else {
m . chatRunning = false
2022-02-09 22:53:06 +00:00
}
}
2022-06-24 13:52:20 +01:00
func startChat ( ) throws {
2022-05-09 09:52:09 +01:00
logger . debug ( " startChat " )
2022-06-24 13:52:20 +01:00
let m = ChatModel . shared
2022-08-03 12:36:51 +01:00
try setNetworkConfig ( getNetCfg ( ) )
2022-06-24 13:52:20 +01:00
let justStarted = try apiStartChat ( )
if justStarted {
m . userAddress = try apiGetUserAddress ( )
2022-11-21 08:37:13 +00:00
( m . userSMPServers , m . presetSMPServers ) = try getUserSMPServers ( )
2022-10-03 16:42:43 +04:00
m . chatItemTTL = try getChatItemTTL ( )
2022-07-02 17:18:45 +01:00
let chats = try apiGetChats ( )
m . chats = chats . map { Chat . init ( $0 ) }
2022-07-20 08:58:53 +01:00
NtfManager . shared . setNtfBadgeCount ( m . totalUnreadCount ( ) )
2022-07-05 15:15:15 +04:00
try refreshCallInvitations ( )
2022-07-09 09:29:56 +01:00
( m . savedToken , m . tokenStatus , m . notificationMode ) = apiGetNtfToken ( )
2022-07-01 09:49:30 +01:00
if let token = m . deviceToken {
registerToken ( token : token )
}
2022-06-24 13:52:20 +01:00
withAnimation {
2022-07-03 19:53:07 +01:00
m . onboardingStage = m . onboardingStage = = . step2_CreateProfile
? . step3_SetNotificationsMode
2022-06-24 13:52:20 +01:00
: . onboardingComplete
2022-05-09 09:52:09 +01:00
}
}
2022-06-24 13:52:20 +01:00
ChatReceiver . shared . start ( )
m . chatRunning = true
chatLastStartGroupDefault . set ( Date . now )
2022-05-09 09:52:09 +01:00
}
2022-02-09 22:53:06 +00:00
class ChatReceiver {
2022-02-24 17:16:41 +00:00
private var receiveLoop : Task < Void , Never > ?
2022-02-09 22:53:06 +00:00
private var receiveMessages = true
private var _lastMsgTime = Date . now
static let shared = ChatReceiver ( )
var lastMsgTime : Date { get { _lastMsgTime } }
2022-02-10 15:52:11 +00:00
func start ( ) {
2022-02-09 22:53:06 +00:00
logger . debug ( " ChatReceiver.start " )
receiveMessages = true
_lastMsgTime = . now
if receiveLoop != nil { return }
2022-02-24 17:16:41 +00:00
receiveLoop = Task { await receiveMsgLoop ( ) }
}
func receiveMsgLoop ( ) async {
2022-06-24 13:52:20 +01:00
// T O D O u s e f u n c t i o n t h a t h a s t i m e o u t
if let msg = await chatRecvMsg ( ) {
self . _lastMsgTime = . now
await processReceivedMsg ( msg )
}
2022-02-24 17:16:41 +00:00
if self . receiveMessages {
2022-07-02 08:50:25 +01:00
_ = try ? await Task . sleep ( nanoseconds : 7_500_000 )
2022-02-24 17:16:41 +00:00
await receiveMsgLoop ( )
2022-02-09 22:53:06 +00:00
}
}
func stop ( ) {
2022-02-10 15:52:11 +00:00
logger . debug ( " ChatReceiver.stop " )
2022-02-09 22:53:06 +00:00
receiveMessages = false
receiveLoop ? . cancel ( )
receiveLoop = nil
}
}
2022-06-19 19:49:39 +01:00
func processReceivedMsg ( _ res : ChatResponse ) async {
2022-04-25 10:39:28 +01:00
let m = ChatModel . shared
2022-06-19 19:49:39 +01:00
await MainActor . run {
2022-04-25 10:39:28 +01:00
m . terminalItems . append ( . resp ( . now , res ) )
2022-02-09 22:53:06 +00:00
logger . debug ( " processReceivedMsg: \( res . responseType ) " )
2022-01-29 23:37:02 +00:00
switch res {
2022-04-26 07:41:08 +01:00
case let . newContactConnection ( connection ) :
m . updateContactConnection ( connection )
case let . contactConnectionDeleted ( connection ) :
m . removeChat ( connection . id )
2022-10-24 16:22:00 +04:00
case let . contactConnected ( contact , _ ) :
if ! contact . viaGroupLink {
2022-10-24 00:18:15 +04:00
m . updateContact ( contact )
2022-11-05 17:48:57 +04:00
m . dismissConnReqView ( contact . activeConn . id )
m . removeChat ( contact . activeConn . id )
2022-10-24 00:18:15 +04:00
m . updateNetworkStatus ( contact . id , . connected )
NtfManager . shared . notifyContactConnected ( contact )
}
2022-10-24 16:22:00 +04:00
case let . contactConnecting ( contact ) :
if ! contact . viaGroupLink {
2022-10-24 00:18:15 +04:00
m . updateContact ( contact )
m . dismissConnReqView ( contact . activeConn . id )
m . removeChat ( contact . activeConn . id )
}
2022-02-01 17:34:06 +00:00
case let . receivedContactRequest ( contactRequest ) :
2022-07-22 08:10:37 +01:00
let cInfo = ChatInfo . contactRequest ( contactRequest : contactRequest )
if m . hasChat ( contactRequest . id ) {
m . updateChatInfo ( cInfo )
} else {
m . addChat ( Chat (
chatInfo : cInfo ,
chatItems : [ ]
) )
NtfManager . shared . notifyContactRequest ( contactRequest )
}
2022-02-04 22:13:52 +00:00
case let . contactUpdated ( toContact ) :
let cInfo = ChatInfo . direct ( contact : toContact )
2022-04-25 10:39:28 +01:00
if m . hasChat ( toContact . id ) {
m . updateChatInfo ( cInfo )
2022-02-04 22:13:52 +00:00
}
2022-10-28 20:05:04 +04:00
case let . contactsMerged ( intoContact , mergedContact ) :
if m . hasChat ( mergedContact . id ) {
if m . chatId = = mergedContact . id {
m . chatId = intoContact . id
}
m . removeChat ( mergedContact . id )
}
2022-04-26 07:51:06 +01:00
case let . contactsSubscribed ( _ , contactRefs ) :
updateContactsStatus ( contactRefs , status : . connected )
case let . contactsDisconnected ( _ , contactRefs ) :
updateContactsStatus ( contactRefs , status : . disconnected )
2022-02-05 20:10:47 +00:00
case let . contactSubError ( contact , chatError ) :
2022-02-25 20:26:56 +00:00
processContactSubError ( contact , chatError )
case let . contactSubSummary ( contactSubscriptions ) :
for sub in contactSubscriptions {
if let err = sub . contactError {
processContactSubError ( sub . contact , err )
} else {
2022-04-26 07:51:06 +01:00
m . updateContact ( sub . contact )
m . updateNetworkStatus ( sub . contact . id , . connected )
2022-02-25 20:26:56 +00:00
}
2022-02-05 20:10:47 +00:00
}
2022-01-29 23:37:02 +00:00
case let . newChatItem ( aChatItem ) :
2022-02-09 22:53:06 +00:00
let cInfo = aChatItem . chatInfo
let cItem = aChatItem . chatItem
2022-04-25 10:39:28 +01:00
m . addChatItem ( cInfo , cItem )
2022-05-04 09:10:36 +04:00
if case . image = cItem . content . msgContent ,
let file = cItem . file ,
2022-11-26 12:43:26 +04:00
file . fileSize <= MAX_IMAGE_SIZE ,
2022-05-30 08:59:04 +01:00
UserDefaults . standard . bool ( forKey : DEFAULT_PRIVACY_ACCEPT_IMAGES ) {
2022-04-19 12:29:03 +04:00
Task {
2022-05-04 09:10:36 +04:00
await receiveFile ( fileId : file . fileId )
2022-04-19 12:29:03 +04:00
}
2022-11-24 21:18:28 +04:00
} else if case . voice = cItem . content . msgContent , // T O D O c h e c k i n l i n e F i l e M o d e ! = I F M S e n t
let file = cItem . file ,
2022-11-26 12:43:26 +04:00
file . fileSize <= MAX_IMAGE_SIZE ,
file . fileSize > MAX_VOICE_MESSAGE_SIZE_INLINE_SEND ,
2022-11-24 21:18:28 +04:00
UserDefaults . standard . bool ( forKey : DEFAULT_PRIVACY_ACCEPT_IMAGES ) {
Task {
await receiveFile ( fileId : file . fileId )
}
2022-04-19 12:29:03 +04:00
}
2022-11-24 21:18:28 +04:00
if ! cItem . chatDir . sent && ! cItem . isCall && ! cItem . isMutedMemberEvent {
2022-05-07 06:40:46 +01:00
NtfManager . shared . notifyMessageReceived ( cInfo , cItem )
}
2022-03-25 22:26:05 +04:00
case let . chatItemStatusUpdated ( aChatItem ) :
2022-02-12 15:59:43 +00:00
let cInfo = aChatItem . chatInfo
let cItem = aChatItem . chatItem
2022-03-30 20:37:47 +04:00
var res = false
2022-11-24 21:18:28 +04:00
if ! cItem . isDeletedContent {
2022-04-25 10:39:28 +01:00
res = m . upsertChatItem ( cInfo , cItem )
2022-03-30 20:37:47 +04:00
}
if res {
2022-02-12 15:59:43 +00:00
NtfManager . shared . notifyMessageReceived ( cInfo , cItem )
2022-04-25 10:39:28 +01:00
} else if let endTask = m . messageDelivery [ cItem . id ] {
2022-02-28 10:44:48 +00:00
switch cItem . meta . itemStatus {
case . sndSent : endTask ( )
case . sndErrorAuth : endTask ( )
case . sndError : endTask ( )
default : break
}
2022-02-12 15:59:43 +00:00
}
2022-03-25 22:26:05 +04:00
case let . chatItemUpdated ( aChatItem ) :
2022-05-04 09:10:36 +04:00
chatItemSimpleUpdate ( aChatItem )
2022-03-30 20:37:47 +04:00
case let . chatItemDeleted ( _ , toChatItem ) :
let cInfo = toChatItem . chatInfo
let cItem = toChatItem . chatItem
if cItem . meta . itemDeleted {
2022-04-25 10:39:28 +01:00
m . removeChatItem ( cInfo , cItem )
2022-03-30 20:37:47 +04:00
} else {
// c u r r e n t l y o n l y b r o a d c a s t d e l e t i o n o f r c v m e s s a g e c a n b e r e c e i v e d , a n d o n l y t h i s c a s e s h o u l d h a p p e n
2022-04-25 10:39:28 +01:00
_ = m . upsertChatItem ( cInfo , cItem )
2022-03-30 20:37:47 +04:00
}
2022-07-18 21:58:32 +04:00
case let . receivedGroupInvitation ( groupInfo , _ , _ ) :
2022-11-07 12:08:37 +04:00
m . updateGroup ( groupInfo ) // u p d a t e s o t h a t r e p e a t g r o u p i n v i t a t i o n s a r e n o t d u p l i c a t e d
2022-07-18 21:58:32 +04:00
// N t f M a n a g e r . s h a r e d . n o t i f y C o n t a c t R e q u e s t ( c o n t a c t R e q u e s t ) / / T O D O n o t i f y G r o u p I n v i t a t i o n ?
2022-11-05 17:48:57 +04:00
case let . userAcceptedGroupSent ( groupInfo , hostContact ) :
2022-11-04 15:33:29 +04:00
m . updateGroup ( groupInfo )
2022-11-05 17:48:57 +04:00
if let hostContact = hostContact {
m . dismissConnReqView ( hostContact . activeConn . id )
m . removeChat ( hostContact . activeConn . id )
}
2022-08-09 13:43:19 +04:00
case let . joinedGroupMemberConnecting ( groupInfo , _ , member ) :
_ = m . upsertGroupMember ( groupInfo , member )
case let . deletedMemberUser ( groupInfo , _ ) : // T O D O u p d a t e u s e r m e m b e r
2022-07-18 21:58:32 +04:00
m . updateGroup ( groupInfo )
2022-08-09 13:43:19 +04:00
case let . deletedMember ( groupInfo , _ , deletedMember ) :
2022-08-09 19:50:29 +04:00
_ = m . upsertGroupMember ( groupInfo , deletedMember )
2022-08-09 13:43:19 +04:00
case let . leftMember ( groupInfo , member ) :
2022-08-09 19:50:29 +04:00
_ = m . upsertGroupMember ( groupInfo , member )
2022-08-09 13:43:19 +04:00
case let . groupDeleted ( groupInfo , _ ) : // T O D O u p d a t e u s e r m e m b e r
2022-07-31 18:54:49 +01:00
m . updateGroup ( groupInfo )
2022-08-09 13:43:19 +04:00
case let . userJoinedGroup ( groupInfo ) :
2022-07-31 18:54:49 +01:00
m . updateGroup ( groupInfo )
2022-08-09 13:43:19 +04:00
case let . joinedGroupMember ( groupInfo , member ) :
_ = m . upsertGroupMember ( groupInfo , member )
case let . connectedToGroupMember ( groupInfo , member ) :
_ = m . upsertGroupMember ( groupInfo , member )
2022-08-01 16:32:42 +04:00
case let . groupUpdated ( toGroup ) :
m . updateGroup ( toGroup )
2022-05-04 09:10:36 +04:00
case let . rcvFileStart ( aChatItem ) :
chatItemSimpleUpdate ( aChatItem )
2022-04-19 12:29:03 +04:00
case let . rcvFileComplete ( aChatItem ) :
2022-05-04 09:10:36 +04:00
chatItemSimpleUpdate ( aChatItem )
2022-05-06 21:10:32 +04:00
case let . sndFileStart ( aChatItem , _ ) :
chatItemSimpleUpdate ( aChatItem )
case let . sndFileComplete ( aChatItem , _ ) :
chatItemSimpleUpdate ( aChatItem )
let cItem = aChatItem . chatItem
2022-11-21 07:42:36 +00:00
let mc = cItem . content . msgContent
2022-05-06 21:10:32 +04:00
if aChatItem . chatInfo . chatType = = . direct ,
2022-11-21 07:42:36 +00:00
case . file = mc ,
2022-05-06 21:10:32 +04:00
let fileName = cItem . file ? . filePath {
removeFile ( fileName )
}
2022-07-05 15:15:15 +04:00
case let . callInvitation ( invitation ) :
m . callInvitations [ invitation . contact . id ] = invitation
activateCall ( invitation )
2022-05-24 19:34:27 +01:00
// T h i s w i l l b e c a l l e d f r o m n o t i f i c a t i o n s e r v i c e e x t e n s i o n
// C X P r o v i d e r . r e p o r t N e w I n c o m i n g V o I P P u s h P a y l o a d ( [
// " d i s p l a y N a m e " : c o n t a c t . d i s p l a y N a m e ,
// " c o n t a c t I d " : c o n t a c t . i d ,
// " u u i d " : i n v i t a t i o n . c a l l k i t U U I D
// ] ) { e r r o r i n
// i f l e t e r r o r = e r r o r {
// l o g g e r . e r r o r ( " r e p o r t N e w I n c o m i n g V o I P P u s h P a y l o a d e r r o r \ ( e r r o r . l o c a l i z e d D e s c r i p t i o n ) " )
// } e l s e {
// l o g g e r . d e b u g ( " r e p o r t N e w I n c o m i n g V o I P P u s h P a y l o a d s u c c e s s f o r \ ( c o n t a c t . i d ) " )
// }
// }
2022-05-07 06:40:46 +01:00
case let . callOffer ( contact , callType , offer , sharedKey , _ ) :
withCall ( contact ) { call in
2022-05-24 19:34:27 +01:00
call . callState = . offerReceived
call . peerMedia = callType . media
call . sharedKey = sharedKey
2022-05-27 16:36:33 +01:00
let useRelay = UserDefaults . standard . bool ( forKey : DEFAULT_WEBRTC_POLICY_RELAY )
2022-09-21 10:19:13 +01:00
let iceServers = getIceServers ( )
2022-05-27 16:36:33 +01:00
logger . debug ( " .callOffer useRelay \( useRelay ) " )
2022-09-21 10:19:13 +01:00
logger . debug ( " .callOffer iceServers \( String ( describing : iceServers ) ) " )
m . callCommand = . offer (
offer : offer . rtcSession ,
iceCandidates : offer . rtcIceCandidates ,
media : callType . media , aesKey : sharedKey ,
useWorker : true ,
iceServers : iceServers ,
relay : useRelay
)
2022-05-07 06:40:46 +01:00
}
case let . callAnswer ( contact , answer ) :
withCall ( contact ) { call in
2022-05-24 19:34:27 +01:00
call . callState = . answerReceived
2022-05-07 06:40:46 +01:00
m . callCommand = . answer ( answer : answer . rtcSession , iceCandidates : answer . rtcIceCandidates )
}
case let . callExtraInfo ( contact , extraInfo ) :
withCall ( contact ) { _ in
m . callCommand = . ice ( iceCandidates : extraInfo . rtcIceCandidates )
}
case let . callEnded ( contact ) :
2022-05-24 19:34:27 +01:00
if let invitation = m . callInvitations . removeValue ( forKey : contact . id ) {
CallController . shared . reportCallRemoteEnded ( invitation : invitation )
}
withCall ( contact ) { call in
2022-05-07 06:40:46 +01:00
m . callCommand = . end
2022-06-02 12:10:41 +01:00
// C a l l C o n t r o l l e r . s h a r e d . r e p o r t C a l l R e m o t e E n d e d ( c a l l : c a l l )
2022-05-07 06:40:46 +01:00
}
2022-06-27 10:28:30 +01:00
case . chatSuspended :
chatSuspended ( )
2022-01-29 23:37:02 +00:00
default :
2022-02-09 22:53:06 +00:00
logger . debug ( " unsupported event: \( res . responseType ) " )
2022-01-29 11:10:04 +00:00
}
2022-05-07 06:40:46 +01:00
func withCall ( _ contact : Contact , _ perform : ( Call ) -> Void ) {
if let call = m . activeCall , call . contact . apiId = = contact . apiId {
perform ( call )
} else {
logger . debug ( " processReceivedMsg: ignoring \( res . responseType ) , not in call with the contact \( contact . id ) " )
}
}
2022-01-29 11:10:04 +00:00
}
}
2022-05-04 09:10:36 +04:00
func chatItemSimpleUpdate ( _ aChatItem : AChatItem ) {
let m = ChatModel . shared
let cInfo = aChatItem . chatInfo
let cItem = aChatItem . chatItem
if m . upsertChatItem ( cInfo , cItem ) {
NtfManager . shared . notifyMessageReceived ( cInfo , cItem )
}
}
2022-04-26 07:51:06 +01:00
func updateContactsStatus ( _ contactRefs : [ ContactRef ] , status : Chat . NetworkStatus ) {
2022-02-25 20:26:56 +00:00
let m = ChatModel . shared
2022-04-26 07:51:06 +01:00
for c in contactRefs {
m . updateNetworkStatus ( c . id , status )
}
2022-02-25 20:26:56 +00:00
}
func processContactSubError ( _ contact : Contact , _ chatError : ChatError ) {
let m = ChatModel . shared
m . updateContact ( contact )
var err : String
switch chatError {
case . errorAgent ( agentError : . BROKER ( brokerErr : . NETWORK ) ) : err = " network "
case . errorAgent ( agentError : . SMP ( smpErr : . AUTH ) ) : err = " contact deleted "
default : err = String ( describing : chatError )
}
2022-04-26 07:51:06 +01:00
m . updateNetworkStatus ( contact . id , . error ( err ) )
2022-02-25 20:26:56 +00:00
}
2022-07-05 15:15:15 +04:00
func refreshCallInvitations ( ) throws {
let m = ChatModel . shared
let callInvitations = try apiGetCallInvitations ( )
m . callInvitations = callInvitations . reduce ( into : [ ChatId : RcvCallInvitation ] ( ) ) { result , inv in result [ inv . contact . id ] = inv }
2022-07-22 08:10:37 +01:00
if let ( chatId , ntfAction ) = m . ntfCallInvitationAction ,
let invitation = m . callInvitations . removeValue ( forKey : chatId ) {
m . ntfCallInvitationAction = nil
CallController . shared . callAction ( invitation : invitation , action : ntfAction )
} else if let invitation = callInvitations . last {
activateCall ( invitation )
2022-07-05 15:15:15 +04:00
}
}
func activateCall ( _ callInvitation : RcvCallInvitation ) {
let m = ChatModel . shared
CallController . shared . reportNewIncomingCall ( invitation : callInvitation ) { error in
if let error = error {
m . callInvitations [ callInvitation . contact . id ] ? . callkitUUID = nil
logger . error ( " reportNewIncomingCall error: \( error . localizedDescription ) " )
} else {
logger . debug ( " reportNewIncomingCall success " )
}
}
}
2022-01-29 11:10:04 +00:00
private struct UserResponse : Decodable {
var user : User ?
var error : String ?
}