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-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-02-28 10:44:48 +00:00
private func beginBGTask ( _ handler : ( ( ) -> Void ) ? = nil ) -> ( ( ) -> Void ) {
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
private func withBGTask ( bgDelay : Double ? = nil , f : @ escaping ( ) -> ChatResponse ) -> ChatResponse {
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-04-08 18:58:09 +01:00
if case . apiParseMarkdown = cmd { } else {
DispatchQueue . main . async {
ChatModel . shared . terminalItems . append ( . cmd ( . now , cmd ) )
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
}
}
func chatRecvMsg ( ) async -> ChatResponse {
await withCheckedContinuation { cont in
2022-02-28 10:44:48 +00:00
_ = withBGTask ( bgDelay : msgDelay ) {
let resp = chatResponse ( chat_recv_msg ( getChatCtrl ( ) ) ! )
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-06 21:06:02 +00:00
let _ = getChatCtrl ( )
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
}
func apiStartChat ( ) throws {
2022-02-24 17:16:41 +00:00
let r = chatSendCmdSync ( . startChat )
2022-02-06 18:26:22 +00:00
if case . chatStarted = r { return }
throw r
}
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-01-31 21:28:07 +00:00
func apiGetChats ( ) throws -> [ Chat ] {
2022-02-24 17:16:41 +00:00
let r = chatSendCmdSync ( . apiGetChats )
2022-02-02 12:51:39 +00:00
if case let . apiChats ( chats ) = r { return chats . map { Chat . init ( $0 ) } }
2022-01-30 18:27:20 +00:00
throw r
}
2022-03-19 17:20:27 +00:00
func apiGetChat ( type : ChatType , id : Int64 ) throws -> Chat {
let r = chatSendCmdSync ( . apiGetChat ( type : type , id : id ) )
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-04-19 12:29:03 +04:00
func apiSendMessage ( type : ChatType , id : Int64 , file : String ? , quotedItemId : Int64 ? , msg : MsgContent ) async throws -> 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
}
endTask ( )
} else {
r = await chatSendCmd ( cmd , bgDelay : msgDelay )
if case let . newChatItem ( aChatItem ) = r {
return aChatItem . chatItem
}
}
2022-01-30 18:27:20 +00:00
throw r
}
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-04-23 06:32:16 +01:00
func apiRegisterToken ( token : String ) async throws -> NtfTknStatus {
let r = await chatSendCmd ( . apiRegisterToken ( token : token ) )
if case let . ntfTokenStatus ( status ) = r { return status }
throw r
2022-04-21 20:04:22 +01:00
}
2022-04-22 13:46:05 +01:00
func apiVerifyToken ( token : String , code : String , nonce : String ) async throws {
try await sendCommandOkResp ( . apiVerifyToken ( token : token , code : code , nonce : nonce ) )
}
func apiIntervalNofication ( token : String , interval : Int ) async throws {
try await sendCommandOkResp ( . apiIntervalNofication ( token : token , interval : interval ) )
}
func apiDeleteToken ( token : String ) async throws {
try await sendCommandOkResp ( . apiDeleteToken ( token : token ) )
2022-04-21 20:04:22 +01:00
}
2022-03-10 15:45:40 +04:00
func getUserSMPServers ( ) throws -> [ String ] {
let r = chatSendCmdSync ( . getUserSMPServers )
if case let . userSMPServers ( smpServers ) = r { return smpServers }
throw r
}
func setUserSMPServers ( smpServers : [ String ] ) 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-02-26 20:21:32 +00:00
func apiAddContact ( ) throws -> String {
2022-02-28 10:44:48 +00:00
let r = chatSendCmdSync ( . addContact , bgTask : false )
2022-01-30 18:27:20 +00:00
if case let . invitation ( connReqInvitation ) = r { return connReqInvitation }
throw r
2022-01-29 23:37:02 +00:00
}
2022-04-25 07:54:07 +01:00
func apiConnect ( connReq : String ) async throws -> 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 ) :
am . showAlertMsg (
title : " Contact already exists " ,
message : " You are already connected to \( contact . displayName ) via this link. "
)
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-02 14:35:35 +01:00
case . chatCmdError ( . errorAgent ( . BROKER ( . TIMEOUT ) ) ) :
am . showAlertMsg (
title : " Connection timeout " ,
2022-04-16 09:37:01 +01:00
message : " Please check your network connection and try again. "
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 ( . errorAgent ( . BROKER ( . NETWORK ) ) ) :
am . showAlertMsg (
title : " Connection error " ,
2022-04-16 09:37:01 +01:00
message : " Please check your network connection and try again. "
2022-04-02 14:35:35 +01:00
)
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
} else {
throw r
}
2022-01-29 23:37:02 +00:00
default : throw r
}
}
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-01-31 21:28:07 +00:00
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-04-08 18:17:10 +01:00
func apiParseMarkdown ( text : String ) throws -> [ FormattedText ] ? {
let r = chatSendCmdSync ( . apiParseMarkdown ( text : text ) )
if case let . apiParsedMarkdown ( formattedText ) = r { return formattedText }
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-04-19 12:29:03 +04:00
func apiGetUserAddress ( ) throws -> String ? {
let r = chatSendCmdSync ( . showMyAddress )
2022-02-01 17:34:06 +00:00
switch r {
case let . userContactLink ( connReq ) :
return connReq
case . chatCmdError ( chatError : . errorStore ( storeError : . userContactLinkNotFound ) ) :
return nil
default : throw r
}
}
2022-02-24 17:16:41 +00:00
func apiAcceptContactRequest ( contactReqId : Int64 ) async throws -> Contact {
let r = await chatSendCmd ( . apiAcceptContact ( contactReqId : contactReqId ) )
2022-02-01 17:34:06 +00:00
if case let . acceptingContactRequest ( contact ) = r { return contact }
throw r
}
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-05-04 09:10:36 +04:00
func receiveFile ( fileId : Int64 ) async {
do {
let chatItem = try await apiReceiveFile ( fileId : fileId )
DispatchQueue . main . async { chatItemSimpleUpdate ( chatItem ) }
} catch let error {
logger . error ( " receiveFile error: \( responseError ( error ) ) " )
}
}
func apiReceiveFile ( fileId : Int64 ) async throws -> AChatItem {
2022-04-19 12:29:03 +04:00
let r = await chatSendCmd ( . receiveFile ( fileId : fileId ) )
2022-05-04 09:10:36 +04:00
if case . rcvFileAccepted ( let chatItem ) = r { return chatItem }
2022-04-19 12:29:03 +04:00
throw r
}
2022-02-24 17:16:41 +00:00
func acceptContactRequest ( _ contactRequest : UserContactRequest ) async {
2022-02-09 22:53:06 +00:00
do {
2022-02-24 17:16:41 +00:00
let contact = try 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
} catch let error {
2022-04-25 10:39:28 +01:00
logger . error ( " acceptContactRequest error: \( responseError ( error ) ) " )
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 ) )
}
func apiSendCallOffer ( _ contact : Contact , _ rtcSession : String , _ rtcIceCandidates : [ String ] , media : CallMediaType , capabilities : CallCapabilities ) async throws {
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 ) )
}
func apiSendCallAnswer ( _ contact : Contact , _ rtcSession : String , _ rtcIceCandidates : [ String ] ) async throws {
let answer = WebRTCSession ( rtcSession : rtcSession , rtcIceCandidates : rtcIceCandidates )
try await sendCommandOkResp ( . apiSendCallAnswer ( contact : contact , answer : answer ) )
}
func apiSendCallExtraInfo ( _ contact : Contact , _ rtcIceCandidates : [ String ] ) async throws {
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 ) )
}
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-02-24 17:16:41 +00:00
func markChatRead ( _ chat : Chat ) async {
2022-02-12 15:59:43 +00:00
do {
let minItemId = chat . chatStats . minUnreadItemId
let itemRange = ( minItemId , chat . chatItems . last ? . id ? ? minItemId )
let cInfo = chat . chatInfo
2022-02-24 17:16:41 +00:00
try await apiChatRead ( type : cInfo . chatType , id : cInfo . apiId , itemRange : itemRange )
DispatchQueue . main . async { ChatModel . shared . markChatItemsRead ( cInfo ) }
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-05-03 08:20:19 +01:00
func apiMarkChatItemRead ( _ cInfo : ChatInfo , _ cItem : ChatItem ) async {
2022-02-12 15:59:43 +00:00
do {
2022-02-24 17:16:41 +00:00
try await apiChatRead ( type : cInfo . chatType , id : cInfo . apiId , itemRange : ( cItem . id , cItem . id ) )
DispatchQueue . main . async { ChatModel . shared . markChatItemRead ( cInfo , cItem ) }
2022-02-12 15:59:43 +00:00
} catch {
2022-04-25 10:39:28 +01:00
logger . error ( " markChatItemRead 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-02-09 22:53:06 +00:00
func initializeChat ( ) {
2022-05-09 09:52:09 +01:00
logger . debug ( " initializeChat " )
2022-02-09 22:53:06 +00:00
do {
2022-05-09 09:52:09 +01:00
let m = ChatModel . shared
m . currentUser = try apiGetActiveUser ( )
if m . currentUser = = nil {
m . onboardingStage = . step1_SimpleXInfo
} else {
startChat ( )
}
2022-02-09 22:53:06 +00:00
} catch {
fatalError ( " Failed to initialize chat controller or database: \( error ) " )
}
}
2022-05-09 09:52:09 +01:00
func startChat ( ) {
logger . debug ( " startChat " )
do {
let m = ChatModel . shared
try apiStartChat ( )
try apiSetFilesFolder ( filesFolder : getAppFilesDirectory ( ) . path )
m . userAddress = try apiGetUserAddress ( )
m . userSMPServers = try getUserSMPServers ( )
m . chats = try apiGetChats ( )
withAnimation {
m . onboardingStage = m . chats . isEmpty
? . step3_MakeConnection
: . onboardingComplete
}
ChatReceiver . shared . start ( )
} catch {
fatalError ( " Failed to start or load chats: \( error ) " )
}
}
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 {
let msg = await chatRecvMsg ( )
self . _lastMsgTime = . now
processReceivedMsg ( msg )
if self . receiveMessages {
2022-02-28 10:44:48 +00:00
do { try await Task . sleep ( nanoseconds : 7_500_000 ) }
catch { logger . error ( " receiveMsgLoop: Task.sleep error: \( error . localizedDescription ) " ) }
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
}
}
func processReceivedMsg ( _ res : ChatResponse ) {
2022-04-25 10:39:28 +01:00
let m = ChatModel . shared
2022-01-29 23:37:02 +00:00
DispatchQueue . main . async {
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-01-29 23:37:02 +00:00
case let . contactConnected ( contact ) :
2022-04-25 10:39:28 +01:00
m . updateContact ( contact )
m . removeChat ( contact . activeConn . id )
2022-04-26 07:51:06 +01:00
m . updateNetworkStatus ( contact . id , . connected )
2022-02-09 22:53:06 +00:00
NtfManager . shared . notifyContactConnected ( contact )
2022-04-22 17:26:17 +01:00
case let . contactConnecting ( contact ) :
2022-04-25 10:39:28 +01:00
m . updateContact ( contact )
m . removeChat ( contact . activeConn . id )
2022-02-01 17:34:06 +00:00
case let . receivedContactRequest ( contactRequest ) :
2022-04-25 10:39:28 +01:00
m . addChat ( Chat (
2022-02-02 12:51:39 +00:00
chatInfo : ChatInfo . contactRequest ( contactRequest : contactRequest ) ,
chatItems : [ ]
) )
2022-02-09 22:53:06 +00:00
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-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-04-25 12:44:24 +04:00
file . fileSize <= maxImageSize {
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-05-07 06:40:46 +01:00
if ! cItem . isCall ( ) {
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
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-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
if aChatItem . chatInfo . chatType = = . direct ,
let mc = cItem . content . msgContent ,
mc . isFile ( ) ,
let fileName = cItem . file ? . filePath {
removeFile ( fileName )
}
2022-05-07 06:40:46 +01:00
case let . callInvitation ( contact , callType , sharedKey ) :
let invitation = CallInvitation ( peerMedia : callType . media , sharedKey : sharedKey )
m . callInvitations [ contact . id ] = invitation
if ( m . activeCallInvitation = = nil ) {
m . activeCallInvitation = ContactRef ( contactId : contact . apiId , localDisplayName : contact . localDisplayName )
}
NtfManager . shared . notifyCallInvitation ( contact , invitation )
case let . callOffer ( contact , callType , offer , sharedKey , _ ) :
// T O D O a s k C o n f i r m a t i o n ?
// T O D O c h e c k e n c r y p t i o n i s c o m p a t i b l e
withCall ( contact ) { call in
m . activeCall = call . copy ( callState : . offerReceived , peerMedia : callType . media , sharedKey : sharedKey )
m . callCommand = . accept ( offer : offer . rtcSession , iceCandidates : offer . rtcIceCandidates , media : callType . media , aesKey : sharedKey )
}
case let . callAnswer ( contact , answer ) :
withCall ( contact ) { call in
m . activeCall = call . copy ( callState : . negotiated )
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 ) :
m . activeCallInvitation = nil
withCall ( contact ) { _ in
m . callCommand = . end
}
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-01-29 11:10:04 +00:00
private struct UserResponse : Decodable {
var user : User ?
var error : String ?
}