2022-05-03 08:20:19 +01:00
//
// N o t i f i c a t i o n S e r v i c e . s w i f t
// S i m p l e X N S E
//
// C r e a t e d b y E v g e n y o n 2 6 / 0 4 / 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 UserNotifications
import OSLog
2023-03-14 11:12:40 +03:00
import StoreKit
import CallKit
2022-05-31 07:55:13 +01:00
import SimpleXChat
2022-05-03 08:20:19 +01:00
let logger = Logger ( )
2023-12-18 10:36:25 +00:00
let appSuspendingDelay : UInt64 = 2_500_000_000
2022-07-06 16:06:35 +01:00
2024-01-08 10:56:01 +00:00
typealias SuspendSchedule = ( delay : TimeInterval , timeout : Int )
2023-12-18 10:36:25 +00:00
2024-01-08 10:56:01 +00:00
let nseSuspendSchedule : SuspendSchedule = ( 2 , 4 )
let fastNSESuspendSchedule : SuspendSchedule = ( 1 , 1 )
2022-07-21 14:26:46 +01:00
2023-03-22 15:58:01 +00:00
enum NSENotification {
2023-12-09 21:59:40 +00:00
case nse ( UNMutableNotificationContent )
case callkit ( RcvCallInvitation )
2023-03-22 15:58:01 +00:00
case empty
2024-10-25 20:09:59 +04:00
}
public enum NSENotificationData {
case connectionEvent ( _ user : User , _ connEntity : ConnectionEntity )
case contactConnected ( _ user : any UserLike , _ contact : Contact )
case contactRequest ( _ user : any UserLike , _ contactRequest : UserContactRequest )
case messageReceived ( _ user : any UserLike , _ cInfo : ChatInfo , _ cItem : ChatItem )
case callInvitation ( _ invitation : RcvCallInvitation )
2024-10-07 19:35:38 +04:00
case msgInfo ( NtfMsgAckInfo )
2024-10-25 20:09:59 +04:00
case noNtf
2022-07-21 14:26:46 +01:00
2024-10-25 20:09:59 +04:00
var callInvitation : RcvCallInvitation ? {
2023-03-22 15:58:01 +00:00
switch self {
2024-10-25 20:09:59 +04:00
case let . callInvitation ( invitation ) : invitation
default : nil
}
}
func notificationContent ( _ badgeCount : Int ) -> UNMutableNotificationContent {
return switch self {
case let . connectionEvent ( user , connEntity ) : createConnectionEventNtf ( user , connEntity , badgeCount )
case let . contactConnected ( user , contact ) : createContactConnectedNtf ( user , contact , badgeCount )
case let . contactRequest ( user , contactRequest ) : createContactRequestNtf ( user , contactRequest , badgeCount )
case let . messageReceived ( user , cInfo , cItem ) : createMessageReceivedNtf ( user , cInfo , cItem , badgeCount )
case let . callInvitation ( invitation ) : createCallInvitationNtf ( invitation , badgeCount )
case . msgInfo : UNMutableNotificationContent ( )
case . noNtf : UNMutableNotificationContent ( )
}
}
var notificationEvent : NSENotificationData ? {
return switch self {
case . connectionEvent : self
case . contactConnected : self
case . contactRequest : self
case . messageReceived : self
case . callInvitation : self
case . msgInfo : nil
case . noNtf : nil
}
}
var newMsgData : ( any UserLike , ChatInfo ) ? {
return switch self {
case let . messageReceived ( user , cInfo , _ ) : ( user , cInfo )
default : nil
2023-12-09 21:59:40 +00:00
}
}
}
// O n c e t h e l a s t t h r e a d i n t h e p r o c e s s c o m p l e t e s p r o c e s s i n g c h a t c o n t r o l l e r i s s u s p e n d e d , a n d t h e d a t a b a s e i s c l o s e d , t o a v o i d
// b a c k g r o u n d c r a s h e s a n d c o n t e n t i o n f o r d a t a b a s e w i t h t h e a p p l i c a t i o n ( b o t h U I a n d b a c k g r o u n d f e t c h t r i g g e r e d e i t h e r o n s c h e d u l e
// o r w h e n b a c k g r o u n d n o t i f i c a t i o n i s r e c e i v e d .
class NSEThreads {
static let shared = NSEThreads ( )
2024-10-25 20:09:59 +04:00
static let queue = DispatchQueue ( label : " chat.simplex.app.SimpleX-NSE.notification-threads.lock " )
2023-12-11 12:34:56 +00:00
private var allThreads : Set < UUID > = [ ]
2024-10-25 20:09:59 +04:00
var activeThreads : [ ( UUID , NotificationService ) ] = [ ]
var droppedNotifications : [ ( ChatId , NSENotificationData ) ] = [ ]
2023-12-09 21:59:40 +00:00
2023-12-11 12:34:56 +00:00
func newThread ( ) -> UUID {
2023-12-09 21:59:40 +00:00
NSEThreads . queue . sync {
2023-12-11 12:34:56 +00:00
let ( _ , t ) = allThreads . insert ( UUID ( ) )
2023-12-09 21:59:40 +00:00
return t
}
}
2024-03-29 00:28:10 +07:00
func startThread ( _ t : UUID , _ service : NotificationService ) {
2023-12-11 12:34:56 +00:00
NSEThreads . queue . sync {
if allThreads . contains ( t ) {
2024-03-29 00:28:10 +07:00
activeThreads . append ( ( t , service ) )
2023-12-11 12:34:56 +00:00
} else {
logger . warning ( " NotificationService startThread: thread \( t ) was removed before it started " )
}
}
}
2024-10-25 20:09:59 +04:00
func processNotification ( _ id : ChatId , _ ntf : NSENotificationData ) async -> Void {
if let ( _ , nse ) = rcvEntityThread ( id ) ,
nse . expectedMessages [ id ] ? . shouldProcessNtf ? ? false {
nse . processReceivedNtf ( id , ntf , signalReady : true )
2024-03-29 00:28:10 +07:00
}
}
2024-04-01 13:52:28 +01:00
private func rcvEntityThread ( _ id : ChatId ) -> ( UUID , NotificationService ) ? {
NSEThreads . queue . sync {
2024-10-25 20:09:59 +04:00
// t h i s s e l e c t s t h e e a r l i e s t t h r e a d t h a t :
// 1 ) h a s t h i s c o n n e c t i o n i n n s e . e x p e c t e d M e s s a g e s
// 2 ) h a s n o t c o m p l e t e d p r o c e s s i n g m e s s a g e s f o r t h i s c o n n e c t i o n ( n o t r e a d y )
activeThreads . first ( where : { ( _ , nse ) in nse . expectedMessages [ id ] ? . ready = = false } )
2024-04-01 13:52:28 +01:00
}
}
2023-12-09 21:59:40 +00:00
func endThread ( _ t : UUID ) -> Bool {
NSEThreads . queue . sync {
2024-03-29 00:28:10 +07:00
let tActive : UUID ? = if let index = activeThreads . firstIndex ( where : { $0 . 0 = = t } ) {
activeThreads . remove ( at : index ) . 0
} else {
nil
}
2023-12-11 12:34:56 +00:00
let t = allThreads . remove ( t )
if tActive != nil && activeThreads . isEmpty {
return true
}
if t != nil && allThreads . isEmpty {
NSEChatState . shared . set ( . suspended )
}
return false
2023-03-22 15:58:01 +00:00
}
}
2023-12-18 10:36:25 +00:00
var noThreads : Bool {
allThreads . isEmpty
}
2023-03-22 15:58:01 +00:00
}
2022-07-21 14:26:46 +01:00
2024-10-25 20:09:59 +04:00
struct ExpectedMessage {
var ntfConn : UserNtfConn
var receiveConnId : String ?
var expectedMsgId : String ?
var allowedGetNextAttempts : Int
var msgBestAttemptNtf : NSENotificationData ?
var ready : Bool
var shouldProcessNtf : Bool
var startedProcessingNewMsgs : Bool
var semaphore : DispatchSemaphore
}
2023-12-09 21:59:40 +00:00
// 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 r e a t e s a n e w i n s t a n c e o f t h e c l a s s a n d c a l l s d i d R e c e i v e f o r e a c h n o t i f i c a t i o n .
// E a c h d i d R e c e i v e i s c a l l e d i n i t s o w n t h r e a d , b u t m u l t i p l e c a l l s c a n b e m a d e i n o n e p r o c e s s , a n d , e m p i r i c a l l y , t h e r e i s n e v e r
2023-12-11 12:59:49 +00:00
// m o r e t h a n o n e p r o c e s s o f 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 e x i s t s a t a t i m e .
2023-12-09 21:59:40 +00:00
// S o o n a f t e r n o t i f i c a t i o n s e r v i c e d e l i v e r s t h e l a s t n o t i f i c a t i o n i t i s e i t h e r s u s p e n d e d o r t e r m i n a t e d .
2022-06-02 13:16:22 +01:00
class NotificationService : UNNotificationServiceExtension {
2022-05-03 08:20:19 +01:00
var contentHandler : ( ( UNNotificationContent ) -> Void ) ?
2024-10-25 20:09:59 +04:00
// s e r v e d a s n o t i f i c a t i o n i f n o m e s s a g e a t t e m p t s ( m s g B e s t A t t e m p t N t f ) c o u l d b e p r o d u c e d
var serviceBestAttemptNtf : NSENotification ?
2022-07-20 08:58:53 +01:00
var badgeCount : Int = 0
2023-12-11 12:34:56 +00:00
// t h r e a d i s a d d e d t o a l l T h r e a d s h e r e - i f t h r e a d d i d n o t s t a r t c h a t ,
// c h a t d o e s n o t n e e d t o b e s u s p e n d e d b u t N S E s t a t e s t i l l n e e d s t o b e s e t t o " s u s p e n d e d " .
2023-12-11 12:59:49 +00:00
var threadId : UUID ? = NSEThreads . shared . newThread ( )
2024-10-25 20:09:59 +04:00
var expectedMessages : Dictionary < String , ExpectedMessage > = [ : ] // k e y i s r e c e i v e E n t i t y I d
2023-12-11 12:34:56 +00:00
var appSubscriber : AppSubscriber ?
var returnedSuspension = false
2022-05-03 08:20:19 +01:00
override func didReceive ( _ request : UNNotificationRequest , withContentHandler contentHandler : @ escaping ( UNNotificationContent ) -> Void ) {
2023-07-20 12:07:00 +01:00
logger . debug ( " DEBUGGING: NotificationService.didReceive " )
2023-12-11 12:34:56 +00:00
let ntf = if let ntf_ = request . content . mutableCopy ( ) as ? UNMutableNotificationContent { ntf_ } else { UNMutableNotificationContent ( ) }
2024-10-25 20:09:59 +04:00
setServiceBestAttemptNtf ( ntf )
2022-07-06 16:06:35 +01:00
self . contentHandler = contentHandler
2022-08-03 17:39:01 +01:00
registerGroupDefaults ( )
2022-06-24 13:52:20 +01:00
let appState = appStateGroupDefault . get ( )
2024-01-08 10:56:01 +00:00
logger . debug ( " NotificationService: app is \( appState . rawValue ) " )
2022-06-27 10:28:30 +01:00
switch appState {
2023-12-11 12:59:49 +00:00
case . stopped :
setBadgeCount ( )
2024-10-25 20:09:59 +04:00
setServiceBestAttemptNtf ( createAppStoppedNtf ( badgeCount ) )
2023-12-11 12:59:49 +00:00
deliverBestAttemptNtf ( )
2022-06-27 10:28:30 +01:00
case . suspended :
2023-12-11 12:59:49 +00:00
receiveNtfMessages ( request , contentHandler )
2022-06-27 10:28:30 +01:00
case . suspending :
2022-07-06 16:06:35 +01:00
Task {
2023-12-11 12:34:56 +00:00
let state : AppState = await withCheckedContinuation { cont in
appSubscriber = appStateSubscriber { s in
if s = = . suspended { appSuspension ( s ) }
}
DispatchQueue . global ( ) . asyncAfter ( deadline : . now ( ) + Double ( appSuspendTimeout ) + 1 ) {
logger . debug ( " NotificationService: appSuspension timeout " )
appSuspension ( appStateGroupDefault . get ( ) )
}
@ Sendable
func appSuspension ( _ s : AppState ) {
if ! self . returnedSuspension {
self . returnedSuspension = true
self . appSubscriber = nil // t h i s d i s p o s e s o f a p p S t a t e S u b s c r i b e r
cont . resume ( returning : s )
}
}
2022-07-06 16:06:35 +01:00
}
2024-01-08 10:56:01 +00:00
logger . debug ( " NotificationService: app state is now \( state . rawValue ) " )
2022-07-06 16:06:35 +01:00
if state . inactive {
2023-12-11 12:59:49 +00:00
receiveNtfMessages ( request , contentHandler )
2022-07-21 14:26:46 +01:00
} else {
deliverBestAttemptNtf ( )
2022-07-06 16:06:35 +01:00
}
}
2024-10-25 20:09:59 +04:00
case . active : contentHandler ( UNMutableNotificationContent ( ) )
case . activating : contentHandler ( UNMutableNotificationContent ( ) )
case . bgRefresh : contentHandler ( UNMutableNotificationContent ( ) )
2022-05-03 08:20:19 +01:00
}
2022-06-27 10:28:30 +01:00
}
2023-12-11 12:59:49 +00:00
func receiveNtfMessages ( _ request : UNNotificationRequest , _ contentHandler : @ escaping ( UNNotificationContent ) -> Void ) {
2022-07-06 15:22:01 +01:00
logger . debug ( " NotificationService: receiveNtfMessages " )
2022-07-01 20:33:20 +01:00
if case . documents = dbContainerGroupDefault . get ( ) {
2022-07-21 14:26:46 +01:00
deliverBestAttemptNtf ( )
2022-07-01 20:33:20 +01:00
return
}
2022-06-19 19:49:39 +01:00
let userInfo = request . content . userInfo
if let ntfData = userInfo [ " notificationData " ] as ? [ AnyHashable : Any ] ,
let nonce = ntfData [ " nonce " ] as ? String ,
let encNtfInfo = ntfData [ " message " ] as ? String ,
2023-12-11 12:34:56 +00:00
// c h e c k i t h e r e a g a i n
appStateGroupDefault . get ( ) . inactive {
// t h r e a d i s a d d e d t o a c t i v e T h r e a d s t r a c k i n g s e t h e r e - i f t h r e a d s t a r t e d c h a t i t n e e d s t o b e s u s p e n d e d
2024-03-29 00:28:10 +07:00
if let t = threadId { NSEThreads . shared . startThread ( t , self ) }
2023-12-11 12:34:56 +00:00
let dbStatus = startChat ( )
2022-09-07 20:06:16 +01:00
if case . ok = dbStatus ,
2024-10-25 20:09:59 +04:00
let ntfConns = apiGetNtfConns ( nonce : nonce , encNtfInfo : encNtfInfo ) {
logger . debug ( " NotificationService: receiveNtfMessages: apiGetNtfConns ntfConns count = \( ntfConns . count ) " )
for ntfConn in ntfConns {
addExpectedMessage ( ntfConn : ntfConn )
}
let connIdsToGet = expectedMessages . compactMap { ( id , _ ) in
let started = NSEThreads . queue . sync {
let canStart = checkCanStart ( id )
if let t = threadId { logger . debug ( " NotificationService thread \( t , privacy : . private ) : receiveNtfMessages: can start: \( canStart ) " ) }
if canStart {
processDroppedNotifications ( id )
expectedMessages [ id ] ? . startedProcessingNewMsgs = true
expectedMessages [ id ] ? . shouldProcessNtf = true
}
return canStart
2022-07-21 14:26:46 +01:00
}
2024-10-25 20:09:59 +04:00
if started {
return expectedMessages [ id ] ? . receiveConnId
} else {
if let t = threadId { logger . debug ( " NotificationService thread \( t , privacy : . private ) : receiveNtfMessages: entity \( id , privacy : . private ) waiting on semaphore " ) }
expectedMessages [ id ] ? . semaphore . wait ( )
if let t = threadId { logger . debug ( " NotificationService thread \( t , privacy : . private ) : receiveNtfMessages: entity \( id , privacy : . private ) proceeding after semaphore " ) }
Task {
NSEThreads . queue . sync {
processDroppedNotifications ( id )
expectedMessages [ id ] ? . startedProcessingNewMsgs = true
expectedMessages [ id ] ? . shouldProcessNtf = true
}
if let connId = expectedMessages [ id ] ? . receiveConnId {
let _ = getConnNtfMessage ( connId : connId )
}
}
return nil
}
}
if ! connIdsToGet . isEmpty {
if let r = apiGetConnNtfMessages ( connIds : connIdsToGet ) {
logger . debug ( " NotificationService: receiveNtfMessages: apiGetConnNtfMessages count = \( r . count ) " )
}
return
2022-07-06 11:52:10 +01:00
}
2023-12-11 12:34:56 +00:00
} else if let dbStatus = dbStatus {
2024-10-25 20:09:59 +04:00
setServiceBestAttemptNtf ( createErrorNtf ( dbStatus , badgeCount ) )
2022-06-24 13:52:20 +01:00
}
2022-05-03 08:20:19 +01:00
}
2022-07-21 14:26:46 +01:00
deliverBestAttemptNtf ( )
2022-05-03 08:20:19 +01:00
}
2022-06-27 10:28:30 +01:00
2024-10-25 20:09:59 +04:00
func addExpectedMessage ( ntfConn : UserNtfConn ) {
if let connEntity = ntfConn . connEntity_ ,
let receiveEntityId = connEntity . id , ntfConn . expectedMsg_ != nil {
let expectedMsgId = ntfConn . expectedMsg_ ? . msgId
logger . debug ( " NotificationService: addExpectedMessage: expectedMsgId = \( expectedMsgId ? ? " nil " , privacy : . private ) " )
expectedMessages [ receiveEntityId ] = ExpectedMessage (
ntfConn : ntfConn ,
receiveConnId : connEntity . conn . agentConnId ,
expectedMsgId : expectedMsgId ,
allowedGetNextAttempts : 3 ,
msgBestAttemptNtf : ntfConn . defaultBestAttemptNtf ,
ready : false ,
shouldProcessNtf : false ,
startedProcessingNewMsgs : false ,
semaphore : DispatchSemaphore ( value : 0 )
)
}
}
func checkCanStart ( _ entityId : String ) -> Bool {
return ! NSEThreads . shared . activeThreads . contains ( where : {
( tId , nse ) in tId != threadId && nse . expectedMessages . contains ( where : { $0 . key = = entityId } )
} )
}
func processDroppedNotifications ( _ entityId : String ) {
if ! NSEThreads . shared . droppedNotifications . isEmpty {
let messagesToProcess = NSEThreads . shared . droppedNotifications . filter { ( eId , _ ) in eId = = entityId }
NSEThreads . shared . droppedNotifications . removeAll ( where : { ( eId , _ ) in eId = = entityId } )
for ( index , ( _ , ntf ) ) in messagesToProcess . enumerated ( ) {
if let t = threadId { logger . debug ( " NotificationService thread \( t , privacy : . private ) : entity \( entityId , privacy : . private ) : processing dropped notification \( index , privacy : . private ) " ) }
processReceivedNtf ( entityId , ntf , signalReady : false )
}
}
}
2022-05-03 08:20:19 +01:00
override func serviceExtensionTimeWillExpire ( ) {
2023-07-20 12:07:00 +01:00
logger . debug ( " DEBUGGING: NotificationService.serviceExtensionTimeWillExpire " )
2024-01-08 10:56:01 +00:00
deliverBestAttemptNtf ( urgent : true )
2022-07-21 14:26:46 +01:00
}
2024-10-25 20:09:59 +04:00
var expectingMoreMessages : Bool {
! expectedMessages . allSatisfy { $0 . value . ready }
}
func processReceivedNtf ( _ id : ChatId , _ ntf : NSENotificationData , signalReady : Bool ) {
guard let expectedMessage = expectedMessages [ id ] else {
return
}
guard let expectedMsgTs = expectedMessage . ntfConn . expectedMsg_ ? . msgTs else {
NSEThreads . shared . droppedNotifications . append ( ( id , ntf ) )
if signalReady { entityReady ( id ) }
return
2024-04-01 13:52:28 +01:00
}
if case let . msgInfo ( info ) = ntf {
2024-10-25 20:09:59 +04:00
if info . msgId = = expectedMessage . expectedMsgId {
2024-10-07 19:35:38 +04:00
logger . debug ( " NotificationService processNtf: msgInfo msgId = \( info . msgId , privacy : . private ) : expected " )
2024-10-25 20:09:59 +04:00
expectedMessages [ id ] ? . expectedMsgId = nil
if signalReady { entityReady ( id ) }
2024-07-22 15:48:57 +01:00
self . deliverBestAttemptNtf ( )
2024-10-07 19:35:38 +04:00
} else if let msgTs = info . msgTs_ , msgTs > expectedMsgTs {
logger . debug ( " NotificationService processNtf: msgInfo msgId = \( info . msgId , privacy : . private ) : unexpected msgInfo, let other instance to process it, stopping this one " )
2024-10-25 20:09:59 +04:00
NSEThreads . shared . droppedNotifications . append ( ( id , ntf ) )
if signalReady { entityReady ( id ) }
2024-04-01 13:52:28 +01:00
self . deliverBestAttemptNtf ( )
2024-10-25 20:09:59 +04:00
} else if ( expectedMessages [ id ] ? . allowedGetNextAttempts ? ? 0 ) > 0 , let receiveConnId = expectedMessages [ id ] ? . receiveConnId {
2024-10-07 19:35:38 +04:00
logger . debug ( " NotificationService processNtf: msgInfo msgId = \( info . msgId , privacy : . private ) : unexpected msgInfo, get next message " )
2024-10-25 20:09:59 +04:00
expectedMessages [ id ] ? . allowedGetNextAttempts -= 1
if let receivedMsg = getConnNtfMessage ( connId : receiveConnId ) {
logger . debug ( " NotificationService processNtf, on getConnNtfMessage: msgInfo msgId = \( info . msgId , privacy : . private ) , receivedMsg msgId = \( receivedMsg . msgId , privacy : . private ) " )
2024-10-07 19:35:38 +04:00
} else {
2024-10-25 20:09:59 +04:00
logger . debug ( " NotificationService processNtf, on getConnNtfMessage: msgInfo msgId = \( info . msgId , privacy : . private ) : no next message, deliver best attempt " )
NSEThreads . shared . droppedNotifications . append ( ( id , ntf ) )
if signalReady { entityReady ( id ) }
2024-10-07 19:35:38 +04:00
self . deliverBestAttemptNtf ( )
}
2024-04-01 13:52:28 +01:00
} else {
2024-10-07 19:35:38 +04:00
logger . debug ( " NotificationService processNtf: msgInfo msgId = \( info . msgId , privacy : . private ) : unknown message, let other instance to process it " )
2024-10-25 20:09:59 +04:00
NSEThreads . shared . droppedNotifications . append ( ( id , ntf ) )
if signalReady { entityReady ( id ) }
2024-10-07 19:35:38 +04:00
self . deliverBestAttemptNtf ( )
2024-04-01 13:52:28 +01:00
}
2024-10-25 20:09:59 +04:00
} else if expectedMessage . ntfConn . user . showNotifications {
2024-04-01 13:52:28 +01:00
logger . debug ( " NotificationService processNtf: setting best attempt " )
2024-10-25 20:09:59 +04:00
if ntf . notificationEvent != nil {
setBadgeCount ( )
2024-04-01 13:52:28 +01:00
}
2024-10-25 20:09:59 +04:00
let prevBestAttempt = expectedMessages [ id ] ? . msgBestAttemptNtf
if prevBestAttempt ? . callInvitation != nil {
if ntf . callInvitation != nil { // r e p l a c e w i t h n e w e r c a l l
expectedMessages [ id ] ? . msgBestAttemptNtf = ntf
} // o t h e r w i s e k e e p c a l l a s b e s t a t t e m p t
} else {
expectedMessages [ id ] ? . msgBestAttemptNtf = ntf
}
} else {
NSEThreads . shared . droppedNotifications . append ( ( id , ntf ) )
if signalReady { entityReady ( id ) }
}
}
func entityReady ( _ entityId : ChatId ) {
if let t = threadId { logger . debug ( " NotificationService thread \( t , privacy : . private ) : entityReady: entity \( entityId , privacy : . private ) " ) }
expectedMessages [ entityId ] ? . ready = true
if let ( tNext , nse ) = NSEThreads . shared . activeThreads . first ( where : { ( _ , nse ) in nse . expectedMessages [ entityId ] ? . startedProcessingNewMsgs = = false } ) {
if let t = threadId { logger . debug ( " NotificationService thread \( t , privacy : . private ) : entityReady: signal next thread \( tNext , privacy : . private ) for entity \( entityId , privacy : . private ) " ) }
nse . expectedMessages [ entityId ] ? . semaphore . signal ( )
2024-04-01 13:52:28 +01:00
}
}
2022-08-04 22:25:52 +01:00
func setBadgeCount ( ) {
badgeCount = ntfBadgeCountGroupDefault . get ( ) + 1
ntfBadgeCountGroupDefault . set ( badgeCount )
}
2024-10-25 20:09:59 +04:00
func setServiceBestAttemptNtf ( _ ntf : UNMutableNotificationContent ) {
logger . debug ( " NotificationService.setServiceBestAttemptNtf " )
serviceBestAttemptNtf = . nse ( ntf )
2022-07-21 14:26:46 +01:00
}
2024-01-08 10:56:01 +00:00
private func deliverBestAttemptNtf ( urgent : Bool = false ) {
2024-10-25 20:09:59 +04:00
if ( urgent || ! expectingMoreMessages ) {
logger . debug ( " NotificationService.deliverBestAttemptNtf " )
// s t o p p r o c e s s i n g o t h e r m e s s a g e s
for ( key , _ ) in expectedMessages {
expectedMessages [ key ] ? . shouldProcessNtf = false
}
let suspend : Bool
if let t = threadId {
threadId = nil
suspend = NSEThreads . shared . endThread ( t ) && NSEThreads . shared . noThreads
} else {
suspend = false
}
deliverCallkitOrNotification ( urgent : urgent , suspend : suspend )
2024-01-08 10:56:01 +00:00
}
}
private func deliverCallkitOrNotification ( urgent : Bool , suspend : Bool = false ) {
2024-10-25 20:09:59 +04:00
if useCallKit ( ) && expectedMessages . contains ( where : { $0 . value . msgBestAttemptNtf ? . callInvitation != nil } ) {
2024-01-08 10:56:01 +00:00
logger . debug ( " NotificationService.deliverCallkitOrNotification: will suspend, callkit " )
if urgent {
// s u s p e n d i n g N S E e v e n t h o u g h t h e r e m a y b e o t h e r n o t i f i c a t i o n s
// t o a l l o w t h e a p p t o p r o c e s s c a l l k i t c a l l
suspendChat ( 0 )
deliverNotification ( )
} else {
// s u s p e n d i n g N S E w i t h d e l a y a n d d e l i v e r i n g a f t e r t h e s u s p e n s i o n
// b e c a u s e p u s h k i t n o t i f i c a t i o n m u s t b e p r o c e s s e d w i t h o u t d e l a y
// t o a v o i d a p p t e r m i n a t i o n
DispatchQueue . global ( ) . asyncAfter ( deadline : . now ( ) + fastNSESuspendSchedule . delay ) {
suspendChat ( fastNSESuspendSchedule . timeout )
DispatchQueue . global ( ) . asyncAfter ( deadline : . now ( ) + Double ( fastNSESuspendSchedule . timeout ) ) {
self . deliverNotification ( )
2023-12-18 10:36:25 +00:00
}
}
2023-12-09 21:59:40 +00:00
}
2024-01-08 10:56:01 +00:00
} else {
if suspend {
logger . debug ( " NotificationService.deliverCallkitOrNotification: will suspend " )
if urgent {
suspendChat ( 0 )
} else {
// s u s p e n s i o n i s d e l a y e d t o a l l o w c h a t c o r e f i n a l i s e a n y p r o c e s s i n g
// ( e . g . , s e n d d e l i v e r y r e c e i p t s )
DispatchQueue . global ( ) . asyncAfter ( deadline : . now ( ) + nseSuspendSchedule . delay ) {
if NSEThreads . shared . noThreads {
suspendChat ( nseSuspendSchedule . timeout )
}
}
}
}
deliverNotification ( )
2023-12-09 21:59:40 +00:00
}
2024-01-08 10:56:01 +00:00
}
private func deliverNotification ( ) {
2024-10-25 20:09:59 +04:00
if let handler = contentHandler , let ntf = prepareNotification ( ) {
2023-12-09 21:59:40 +00:00
contentHandler = nil
2024-10-25 20:09:59 +04:00
serviceBestAttemptNtf = nil
2023-03-22 15:58:01 +00:00
switch ntf {
2024-10-25 20:09:59 +04:00
case let . nse ( content ) :
content . badge = badgeCount as NSNumber
handler ( content )
2023-03-22 15:58:01 +00:00
case let . callkit ( invitation ) :
2024-01-08 10:56:01 +00:00
logger . debug ( " NotificationService reportNewIncomingVoIPPushPayload for \( invitation . contact . id ) " )
2023-03-22 15:58:01 +00:00
CXProvider . reportNewIncomingVoIPPushPayload ( [
" displayName " : invitation . contact . displayName ,
" contactId " : invitation . contact . id ,
2024-08-28 14:49:11 +00:00
" callUUID " : invitation . callUUID ? ? " " ,
" media " : invitation . callType . media . rawValue ,
" callTs " : invitation . callTs . timeIntervalSince1970
2023-03-22 15:58:01 +00:00
] ) { error in
2024-01-08 10:56:01 +00:00
logger . debug ( " reportNewIncomingVoIPPushPayload result: \( error ) " )
2024-10-25 20:09:59 +04:00
handler ( error = = nil ? UNMutableNotificationContent ( ) : createCallInvitationNtf ( invitation , self . badgeCount ) )
2023-03-22 15:58:01 +00:00
}
2024-10-25 20:09:59 +04:00
case . empty :
handler ( UNMutableNotificationContent ( ) ) // u s e d t o m u t e n o t i f i c a t i o n s t h a t d i d n o t u n s u b s c r i b e y e t
}
}
}
private func prepareNotification ( ) -> NSENotification ? {
if expectedMessages . isEmpty {
return serviceBestAttemptNtf
} else if let callNtfKV = expectedMessages . first ( where : { $0 . value . msgBestAttemptNtf ? . callInvitation != nil } ) ,
let callInv = callNtfKV . value . msgBestAttemptNtf ? . callInvitation ,
let callNtf = callNtfKV . value . msgBestAttemptNtf {
return useCallKit ( ) ? . callkit ( callInv ) : . nse ( callNtf . notificationContent ( badgeCount ) )
} else {
let ntfEvents = expectedMessages . compactMap { $0 . value . msgBestAttemptNtf ? . notificationEvent }
if ntfEvents . isEmpty {
return . empty
} else if let ntfEvent = ntfEvents . count = = 1 ? ntfEvents . first : nil {
return . nse ( ntfEvent . notificationContent ( badgeCount ) )
} else {
return . nse ( createJointNtf ( ntfEvents ) )
}
}
}
private func createJointNtf ( _ ntfEvents : [ NSENotificationData ] ) -> UNMutableNotificationContent {
let previewMode = ntfPreviewModeGroupDefault . get ( )
let newMsgsData : [ ( any UserLike , ChatInfo ) ] = ntfEvents . compactMap { $0 . newMsgData }
if ! newMsgsData . isEmpty , let userId = newMsgsData . first ? . 0. userId {
let newMsgsChats : [ ChatInfo ] = newMsgsData . map { $0 . 1 }
let uniqueChatsNames = uniqueNewMsgsChatsNames ( newMsgsChats )
var body : String
if previewMode = = . hidden {
body = String . localizedStringWithFormat ( NSLocalizedString ( " New messages in %d chats " , comment : " notification body " ) , uniqueChatsNames . count )
} else {
body = String . localizedStringWithFormat ( NSLocalizedString ( " From: %@ " , comment : " notification body " ) , newMsgsChatsNamesStr ( uniqueChatsNames ) )
}
return createNotification (
categoryIdentifier : ntfCategoryManyEvents ,
title : NSLocalizedString ( " New messages " , comment : " notification " ) ,
body : body ,
userInfo : [ " userId " : userId ] ,
badgeCount : badgeCount
)
} else {
return createNotification (
categoryIdentifier : ntfCategoryManyEvents ,
title : NSLocalizedString ( " New events " , comment : " notification " ) ,
body : String . localizedStringWithFormat ( NSLocalizedString ( " %d new events " , comment : " notification body " ) , ntfEvents . count ) ,
badgeCount : badgeCount
)
}
}
private func uniqueNewMsgsChatsNames ( _ newMsgsChats : [ ChatInfo ] ) -> [ String ] {
var seenChatIds = Set < ChatId > ( )
var uniqueChatsNames : [ String ] = [ ]
for chat in newMsgsChats {
if ! seenChatIds . contains ( chat . id ) {
seenChatIds . insert ( chat . id )
uniqueChatsNames . append ( chat . chatViewName )
2023-03-22 15:58:01 +00:00
}
2022-05-03 08:20:19 +01:00
}
2024-10-25 20:09:59 +04:00
return uniqueChatsNames
}
private func newMsgsChatsNamesStr ( _ names : [ String ] ) -> String {
return switch names . count {
case 1 : names [ 0 ]
case 2 : " \( names [ 0 ] ) and \( names [ 1 ] ) "
case 3 : " \( names [ 0 ] + " , " + names [ 1 ] ) and \( names [ 2 ] ) "
default :
names . count > 3
? " \( names [ 0 ] ) , \( names [ 1 ] ) and \( names . count - 2 ) other chats "
: " "
}
2022-05-03 08:20:19 +01:00
}
2022-06-02 13:16:22 +01:00
}
2022-05-03 08:20:19 +01:00
2023-12-11 12:34:56 +00:00
// n s e S t a t e G r o u p D e f a u l t m u s t n o t b e u s e d i n N S E d i r e c t l y , o n l y v i a t h i s s i n g l e t o n
2023-12-09 21:59:40 +00:00
class NSEChatState {
static let shared = NSEChatState ( )
private var value_ = NSEState . created
var value : NSEState {
value_
}
func set ( _ state : NSEState ) {
nseStateGroupDefault . set ( state )
2023-12-11 12:34:56 +00:00
sendNSEState ( state )
2023-12-09 21:59:40 +00:00
value_ = state
}
init ( ) {
2023-12-11 12:34:56 +00:00
// T h i s i s a l w a y s s e t t o . c r e a t e d s t a t e , a s i n c a s e p r e v i o u s s t a r t o f N S E c r a s h e d i n . a c t i v e s t a t e , i t i s s t o r e d c o r r e c t l y .
// O t h e r w i s e t h e a p p w i l l b e a c t i v a t i n g s l o w e r
2023-12-09 21:59:40 +00:00
set ( . created )
}
}
2023-12-11 12:34:56 +00:00
var appSubscriber : AppSubscriber = appStateSubscriber { state in
logger . debug ( " NotificationService: appSubscriber " )
if state . running && NSEChatState . shared . value . canSuspend {
logger . debug ( " NotificationService: appSubscriber app state \( state . rawValue ) , suspending " )
2024-01-08 10:56:01 +00:00
suspendChat ( fastNSESuspendSchedule . timeout )
2023-12-11 12:34:56 +00:00
}
}
func appStateSubscriber ( onState : @ escaping ( AppState ) -> Void ) -> AppSubscriber {
appMessageSubscriber { msg in
if case let . state ( state ) = msg {
2024-01-08 10:56:01 +00:00
logger . debug ( " NotificationService: appStateSubscriber \( state . rawValue ) " )
2023-12-11 12:34:56 +00:00
onState ( state )
}
}
}
2024-07-28 17:54:58 +01:00
let seSubscriber = seMessageSubscriber {
switch $0 {
case let . state ( state ) :
if state = = . sendingMessage && NSEChatState . shared . value . canSuspend {
logger . debug ( " NotificationService: seSubscriber app state \( state . rawValue ) , suspending " )
suspendChat ( fastNSESuspendSchedule . timeout )
}
}
}
2023-12-09 21:59:40 +00:00
var receiverStarted = false
let startLock = DispatchSemaphore ( value : 1 )
let suspendLock = DispatchSemaphore ( value : 1 )
2023-01-13 19:05:53 +00:00
var networkConfig : NetCfg = getNetCfg ( )
2022-09-07 20:06:16 +01:00
2023-12-09 21:59:40 +00:00
// s t a r t C h a t u s e s s e m a p h o r e s t a r t L o c k t o e n s u r e t h a t o n l y o n e d i d R e c e i v e t h r e a d c a n s t a r t c h a t c o n t r o l l e r
// S u b s e q u e n t c a l l s t o d i d R e c e i v e w i l l b e w a i t i n g o n s e m a p h o r e a n d w o n ' t s t a r t c h a t a g a i n , a s i t w i l l b e . a c t i v e
2022-09-07 20:06:16 +01:00
func startChat ( ) -> DBMigrationResult ? {
2023-12-09 21:59:40 +00:00
logger . debug ( " NotificationService: startChat " )
2024-01-08 10:56:01 +00:00
// o n l y s k i p c r e a t i n g i f t h e r e i s c h a t c o n t r o l l e r
if case . active = NSEChatState . shared . value , hasChatCtrl ( ) { return . ok }
2023-12-09 21:59:40 +00:00
startLock . wait ( )
defer { startLock . signal ( ) }
2024-01-08 10:56:01 +00:00
if hasChatCtrl ( ) {
return switch NSEChatState . shared . value {
case . created : doStartChat ( )
case . starting : . ok // i t s h o u l d n e v e r g e t t o t h i s b r a n c h , a s i t w o u l d b e w a i t i n g f o r s t a r t o n s t a r t L o c k
case . active : . ok
case . suspending : activateChat ( )
case . suspended : activateChat ( )
}
} else {
// I g n o r e s t a t e i n p r e f e r e n c e i f t h e r e i s n o c h a t c o n t r o l l e r .
// S t a t e i n p r e f e r e n c e m a y h a v e f a i l e d t o u p d a t e e . g . b e c a u s e o f a c r a s h .
NSEChatState . shared . set ( . created )
return doStartChat ( )
2023-12-09 21:59:40 +00:00
}
}
func doStartChat ( ) -> DBMigrationResult ? {
logger . debug ( " NotificationService: doStartChat " )
2024-01-08 10:56:01 +00:00
haskell_init_nse ( )
2023-12-23 13:06:59 +00:00
let ( _ , dbStatus ) = chatMigrateInit ( confirmMigrations : defaultMigrationConfirmation ( ) , backgroundMode : true )
2024-01-09 19:34:54 +00:00
logger . debug ( " NotificationService: doStartChat \( String ( describing : dbStatus ) ) " )
2022-09-25 20:53:32 +01:00
if dbStatus != . ok {
resetChatCtrl ( )
2023-12-09 21:59:40 +00:00
NSEChatState . shared . set ( . created )
2022-09-25 20:53:32 +01:00
return dbStatus
}
2023-12-11 12:34:56 +00:00
let state = NSEChatState . shared . value
NSEChatState . shared . set ( . starting )
2022-05-03 08:20:19 +01:00
if let user = apiGetActiveUser ( ) {
2023-12-09 21:59:40 +00:00
logger . debug ( " NotificationService active user \( String ( describing : user ) ) " )
2022-05-03 08:20:19 +01:00
do {
2023-01-13 19:05:53 +00:00
try setNetworkConfig ( networkConfig )
2024-07-03 22:42:13 +01:00
try apiSetAppFilePaths ( filesFolder : getAppFilesDirectory ( ) . path , tempFolder : getTempFilesDirectory ( ) . path , assetsFolder : getWallpaperDirectory ( ) . deletingLastPathComponent ( ) . path )
2023-10-15 20:58:39 +01:00
try apiSetEncryptLocalFiles ( privacyEncryptLocalFilesGroupDefault . get ( ) )
2023-12-11 12:34:56 +00:00
// p r e v e n t s u s p e n s i o n w h i l e s t a r t i n g c h a t
suspendLock . wait ( )
defer { suspendLock . signal ( ) }
if NSEChatState . shared . value = = . starting {
updateNetCfg ( )
let justStarted = try apiStartChat ( )
NSEChatState . shared . set ( . active )
if justStarted {
chatLastStartGroupDefault . set ( Date . now )
Task {
if ! receiverStarted {
receiverStarted = true
await receiveMessages ( )
}
2023-12-09 21:59:40 +00:00
}
}
2023-12-11 12:34:56 +00:00
return . ok
2022-07-21 14:26:46 +01:00
}
2022-05-03 08:20:19 +01:00
} catch {
2024-01-08 10:56:01 +00:00
logger . error ( " NotificationService startChat error: \( responseError ( error ) ) " )
2022-05-03 08:20:19 +01:00
}
} else {
2023-12-09 21:59:40 +00:00
logger . debug ( " NotificationService: no active user " )
2022-05-03 08:20:19 +01:00
}
2023-12-11 12:34:56 +00:00
if NSEChatState . shared . value = = . starting { NSEChatState . shared . set ( state ) }
2022-05-03 08:20:19 +01:00
return nil
}
2023-12-09 21:59:40 +00:00
func activateChat ( ) -> DBMigrationResult ? {
logger . debug ( " NotificationService: activateChat " )
let state = NSEChatState . shared . value
NSEChatState . shared . set ( . active )
if apiActivateChat ( ) {
logger . debug ( " NotificationService: activateChat: after apiActivateChat " )
return . ok
} else {
NSEChatState . shared . set ( state )
return nil
}
}
// s u s p e n d C h a t u s e s s e m a p h o r e s u s p e n d L o c k t o e n s u r e t h a t o n l y o n e s u s p e n s i o n c a n h a p p e n .
func suspendChat ( _ timeout : Int ) {
logger . debug ( " NotificationService: suspendChat " )
let state = NSEChatState . shared . value
if ! state . canSuspend {
2024-01-08 10:56:01 +00:00
logger . error ( " NotificationService suspendChat called, current state: \( state . rawValue ) " )
} else if hasChatCtrl ( ) {
// o n l y s u s p e n d i f w e h a v e c h a t c o n t r o l l e r t o a v o i d c r a s h e s w h e n s u s p e n s i o n i s
// a t t e m p t e d w h e n c h a t c o n t r o l l e r w a s n o t c r e a t e d
2023-12-09 21:59:40 +00:00
suspendLock . wait ( )
defer { suspendLock . signal ( ) }
NSEChatState . shared . set ( . suspending )
if apiSuspendChat ( timeoutMicroseconds : timeout * 1000000 ) {
2023-12-18 10:36:25 +00:00
logger . debug ( " NotificationService: suspendChat: after apiSuspendChat " )
2023-12-09 21:59:40 +00:00
DispatchQueue . global ( ) . asyncAfter ( deadline : . now ( ) + Double ( timeout ) + 1 , execute : chatSuspended )
} else {
NSEChatState . shared . set ( state )
}
}
}
func chatSuspended ( ) {
logger . debug ( " NotificationService chatSuspended " )
if case . suspending = NSEChatState . shared . value {
NSEChatState . shared . set ( . suspended )
chatCloseStore ( )
2023-12-18 10:36:25 +00:00
logger . debug ( " NotificationService chatSuspended: suspended " )
2023-12-09 21:59:40 +00:00
}
}
// A s i n g l e l o o p i s u s e d p e r 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 p r o c e s s t o r e c e i v e a n d p r o c e s s a l l m e s s a g e s d e p e n d i n g o n t h e N S E s t a t e
2024-03-29 00:28:10 +07:00
// I f t h e e x t e n s i o n i s n o t a c t i v e y e t , o r s u s p e n d e d / s u s p e n d i n g , o r t h e a p p i s r u n n i n g , t h e n o t i f i c a t i o n s w i l l n o t b e r e c e i v e d .
2022-07-21 14:26:46 +01:00
func receiveMessages ( ) async {
logger . debug ( " NotificationService receiveMessages " )
2022-05-03 08:20:19 +01:00
while true {
2023-12-09 21:59:40 +00:00
switch NSEChatState . shared . value {
2023-12-11 12:34:56 +00:00
// i t s h o u l d n e v e r g e t t o " c r e a t e d " a n d " s t a r t i n g " b r a n c h e s , a s N S E s t a t e i s s e t t o . a c t i v e b e f o r e t h e l o o p s t a r t
2023-12-09 21:59:40 +00:00
case . created : await delayWhenInactive ( )
2023-12-11 12:34:56 +00:00
case . starting : await delayWhenInactive ( )
case . active : await receiveMsg ( )
2023-12-09 21:59:40 +00:00
case . suspending : await receiveMsg ( )
case . suspended : await delayWhenInactive ( )
}
}
func receiveMsg ( ) async {
2022-07-21 14:26:46 +01:00
if let msg = await chatRecvMsg ( ) {
2023-12-09 21:59:40 +00:00
logger . debug ( " NotificationService receiveMsg: message " )
2022-07-21 14:26:46 +01:00
if let ( id , ntf ) = await receivedMsgNtf ( msg ) {
2023-12-09 21:59:40 +00:00
logger . debug ( " NotificationService receiveMsg: notification " )
2024-03-29 00:28:10 +07:00
await NSEThreads . shared . processNotification ( id , ntf )
2022-06-24 13:52:20 +01:00
}
2022-05-03 08:20:19 +01:00
}
}
2023-12-09 21:59:40 +00:00
func delayWhenInactive ( ) async {
logger . debug ( " NotificationService delayWhenInactive " )
_ = try ? await Task . sleep ( nanoseconds : 1000_000000 )
}
2022-05-03 08:20:19 +01:00
}
2022-07-21 14:26:46 +01:00
func chatRecvMsg ( ) async -> ChatResponse ? {
await withCheckedContinuation { cont in
let resp = recvSimpleXMsg ( )
cont . resume ( returning : resp )
}
}
2023-03-14 11:12:40 +03:00
private let isInChina = SKStorefront ( ) . countryCode = = " CHN "
private func useCallKit ( ) -> Bool { ! isInChina && callKitEnabledGroupDefault . get ( ) }
2024-10-25 20:09:59 +04:00
func receivedMsgNtf ( _ res : ChatResponse ) async -> ( String , NSENotificationData ) ? {
2024-01-08 10:56:01 +00:00
logger . debug ( " NotificationService receivedMsgNtf: \( res . responseType ) " )
2022-07-21 14:26:46 +01:00
switch res {
2023-01-19 16:22:56 +00:00
case let . contactConnected ( user , contact , _ ) :
2024-10-25 20:09:59 +04:00
return ( contact . id , . contactConnected ( user , contact ) )
2022-10-24 16:22:00 +04:00
// c a s e l e t . c o n t a c t C o n n e c t i n g ( c o n t a c t ) :
2022-07-21 14:26:46 +01:00
// T O D O p r o f i l e u p d a t e
2023-01-19 16:22:56 +00:00
case let . receivedContactRequest ( user , contactRequest ) :
2024-10-25 20:09:59 +04:00
return ( UserContact ( contactRequest : contactRequest ) . id , . contactRequest ( user , contactRequest ) )
2024-08-22 21:38:22 +04:00
case let . newChatItems ( user , chatItems ) :
// R e c e i v e d i t e m s a r e c r e a t e d o n e a t a t i m e
if let chatItem = chatItems . first {
let cInfo = chatItem . chatInfo
var cItem = chatItem . chatItem
if let file = cItem . autoReceiveFile ( ) {
cItem = autoReceiveFile ( file ) ? ? cItem
}
2025-02-03 20:47:32 +00:00
let ntf : NSENotificationData = ( cInfo . ntfsEnabled ( chatItem : cItem ) && cItem . showNotification ) ? . messageReceived ( user , cInfo , cItem ) : . noNtf
2025-02-10 22:07:14 +07:00
let chatIdOrMemberId = if case let . groupRcv ( groupMember ) = chatItem . chatItem . chatDir {
groupMember . id
} else {
chatItem . chatInfo . id
}
return ( chatIdOrMemberId , ntf )
2024-08-22 21:38:22 +04:00
} else {
return nil
2023-04-20 16:52:55 +04:00
}
2023-04-07 17:41:07 +04:00
case let . rcvFileSndCancelled ( _ , aChatItem , _ ) :
cleanupFile ( aChatItem )
return nil
case let . sndFileComplete ( _ , aChatItem , _ ) :
cleanupDirectFile ( aChatItem )
return nil
case let . sndFileRcvCancelled ( _ , aChatItem , _ ) :
2024-03-11 21:17:28 +07:00
if let aChatItem = aChatItem {
cleanupDirectFile ( aChatItem )
}
2023-04-07 17:41:07 +04:00
return nil
2022-07-22 08:10:37 +01:00
case let . callInvitation ( invitation ) :
2023-03-14 11:12:40 +03:00
// D o n o t p o s t i t w i t h o u t C a l l K i t s u p p o r t , i O S w i l l s t o p l a u n c h i n g t h e a p p w i t h o u t s h o w i n g C a l l K i t
2024-10-25 20:09:59 +04:00
return ( invitation . contact . id , . callInvitation ( invitation ) )
2023-12-09 21:59:40 +00:00
case let . ntfMessage ( _ , connEntity , ntfMessage ) :
return if let id = connEntity . id { ( id , . msgInfo ( ntfMessage ) ) } else { nil }
case . chatSuspended :
chatSuspended ( )
return nil
2024-01-08 10:56:01 +00:00
case let . chatError ( _ , err ) :
logger . error ( " NotificationService receivedMsgNtf error: \( String ( describing : err ) ) " )
return nil
2022-07-21 14:26:46 +01:00
default :
2023-12-09 21:59:40 +00:00
logger . debug ( " NotificationService receivedMsgNtf ignored event: \( res . responseType ) " )
2022-07-21 14:26:46 +01:00
return nil
}
}
2023-01-13 19:05:53 +00:00
func updateNetCfg ( ) {
let newNetConfig = getNetCfg ( )
if newNetConfig != networkConfig {
logger . debug ( " NotificationService applying changed network config " )
do {
try setNetworkConfig ( networkConfig )
networkConfig = newNetConfig
} catch {
2024-01-08 10:56:01 +00:00
logger . error ( " NotificationService apply changed network config error: \( responseError ( error ) ) " )
2023-01-13 19:05:53 +00:00
}
}
}
2022-05-03 08:20:19 +01:00
func apiGetActiveUser ( ) -> User ? {
let r = sendSimpleXCmd ( . showActiveUser )
2024-01-08 10:56:01 +00:00
logger . debug ( " apiGetActiveUser sendSimpleXCmd response: \( r . responseType ) " )
2022-05-03 08:20:19 +01:00
switch r {
case let . activeUser ( user ) : return user
2024-01-08 10:56:01 +00:00
case . chatCmdError ( _ , . error ( . noActiveUser ) ) :
logger . debug ( " apiGetActiveUser sendSimpleXCmd no active user " )
return nil
case let . chatCmdError ( _ , err ) :
logger . debug ( " apiGetActiveUser sendSimpleXCmd error: \( String ( describing : err ) ) " )
return nil
2022-05-03 08:20:19 +01:00
default :
logger . error ( " NotificationService apiGetActiveUser unexpected response: \( String ( describing : r ) ) " )
return nil
}
}
2022-07-21 14:26:46 +01:00
func apiStartChat ( ) throws -> Bool {
2024-07-17 14:14:19 +01:00
let r = sendSimpleXCmd ( . startChat ( mainApp : false , enableSndFiles : false ) )
2022-05-31 07:55:13 +01:00
switch r {
2022-07-21 14:26:46 +01:00
case . chatStarted : return true
case . chatRunning : return false
2022-05-31 07:55:13 +01:00
default : throw r
}
2022-05-03 08:20:19 +01:00
}
2023-12-09 21:59:40 +00:00
func apiActivateChat ( ) -> Bool {
chatReopenStore ( )
let r = sendSimpleXCmd ( . apiActivateChat ( restoreChat : false ) )
if case . cmdOk = r { return true }
logger . error ( " NotificationService apiActivateChat error: \( String ( describing : r ) ) " )
return false
}
func apiSuspendChat ( timeoutMicroseconds : Int ) -> Bool {
let r = sendSimpleXCmd ( . apiSuspendChat ( timeoutMicroseconds : timeoutMicroseconds ) )
if case . cmdOk = r { return true }
logger . error ( " NotificationService apiSuspendChat error: \( String ( describing : r ) ) " )
return false
}
2024-07-03 22:42:13 +01:00
func apiSetAppFilePaths ( filesFolder : String , tempFolder : String , assetsFolder : String ) throws {
let r = sendSimpleXCmd ( . apiSetAppFilePaths ( filesFolder : filesFolder , tempFolder : tempFolder , assetsFolder : assetsFolder ) )
2022-05-03 08:20:19 +01:00
if case . cmdOk = r { return }
2022-08-23 18:18:12 +04:00
throw r
}
2023-10-15 20:58:39 +01:00
func apiSetEncryptLocalFiles ( _ enable : Bool ) throws {
let r = sendSimpleXCmd ( . apiSetEncryptLocalFiles ( enable : enable ) )
2023-10-15 18:16:12 +01:00
if case . cmdOk = r { return }
throw r
}
2024-10-25 20:09:59 +04:00
func apiGetNtfConns ( nonce : String , encNtfInfo : String ) -> [ UserNtfConn ] ? {
2023-01-23 13:20:58 +00:00
guard apiGetActiveUser ( ) != nil else {
logger . debug ( " no active user " )
return nil
2023-01-05 20:38:31 +04:00
}
2024-10-25 20:09:59 +04:00
let r = sendSimpleXCmd ( . apiGetNtfConns ( nonce : nonce , encNtfInfo : encNtfInfo ) )
if case let . ntfConns ( ntfConns ) = r {
logger . debug ( " apiGetNtfConns response ntfConns: \( ntfConns . count ) " )
return ntfConns . compactMap { toUserNtfConn ( $0 ) }
2023-01-23 13:20:58 +00:00
} else if case let . chatCmdError ( _ , error ) = r {
logger . debug ( " apiGetNtfMessage error response: \( String . init ( describing : error ) ) " )
} else {
2024-01-08 10:56:01 +00:00
logger . debug ( " apiGetNtfMessage ignored response: \( r . responseType ) \( String . init ( describing : r ) ) " )
2022-06-19 19:49:39 +01:00
}
2023-01-23 13:20:58 +00:00
return nil
2022-06-27 10:28:30 +01:00
}
2024-10-25 20:09:59 +04:00
func toUserNtfConn ( _ ntfConn : NtfConn ) -> UserNtfConn ? {
if let user = ntfConn . user_ {
return UserNtfConn ( user : user , connEntity_ : ntfConn . connEntity_ , expectedMsg_ : ntfConn . expectedMsg_ )
} else {
return nil
}
}
func apiGetConnNtfMessages ( connIds : [ String ] ) -> [ NtfMsgInfo ? ] ? {
2024-10-07 19:35:38 +04:00
guard apiGetActiveUser ( ) != nil else {
logger . debug ( " no active user " )
return nil
}
2024-10-25 20:09:59 +04:00
let r = sendSimpleXCmd ( . apiGetConnNtfMessages ( connIds : connIds ) )
if case let . connNtfMessages ( receivedMsgs ) = r {
logger . debug ( " apiGetConnNtfMessages response receivedMsgs: \( receivedMsgs . count ) " )
return receivedMsgs
}
logger . debug ( " apiGetConnNtfMessages error: \( responseError ( r ) ) " )
return nil
}
func getConnNtfMessage ( connId : String ) -> NtfMsgInfo ? {
let r_ = apiGetConnNtfMessages ( connIds : [ connId ] )
if let r = r_ , let receivedMsg = r . count = = 1 ? r . first : nil {
return receivedMsg
2024-10-07 19:35:38 +04:00
}
return nil
}
2023-09-07 11:28:37 +01:00
func apiReceiveFile ( fileId : Int64 , encrypted : Bool , inline : Bool ? = nil ) -> AChatItem ? {
2024-05-20 17:49:19 +04:00
let userApprovedRelays = ! privacyAskToApproveRelaysGroupDefault . get ( )
let r = sendSimpleXCmd ( . receiveFile ( fileId : fileId , userApprovedRelays : userApprovedRelays , encrypted : encrypted , inline : inline ) )
2023-01-19 16:22:56 +00:00
if case let . rcvFileAccepted ( _ , chatItem ) = r { return chatItem }
2022-07-10 14:28:00 +01:00
logger . error ( " receiveFile error: \( responseError ( r ) ) " )
return nil
}
2023-09-07 11:28:37 +01:00
func apiSetFileToReceive ( fileId : Int64 , encrypted : Bool ) {
2024-05-20 17:49:19 +04:00
let userApprovedRelays = ! privacyAskToApproveRelaysGroupDefault . get ( )
let r = sendSimpleXCmd ( . setFileToReceive ( fileId : fileId , userApprovedRelays : userApprovedRelays , encrypted : encrypted ) )
2023-04-20 16:52:55 +04:00
if case . cmdOk = r { return }
logger . error ( " setFileToReceive error: \( responseError ( r ) ) " )
}
2024-01-16 18:49:44 +07:00
func autoReceiveFile ( _ file : CIFile ) -> ChatItem ? {
let encrypted = privacyEncryptLocalFilesGroupDefault . get ( )
2023-04-20 16:52:55 +04:00
switch file . fileProtocol {
case . smp :
2023-09-21 19:02:47 +01:00
return apiReceiveFile ( fileId : file . fileId , encrypted : encrypted ) ? . chatItem
2023-04-20 16:52:55 +04:00
case . xftp :
2023-09-07 11:28:37 +01:00
apiSetFileToReceive ( fileId : file . fileId , encrypted : encrypted )
2023-04-20 16:52:55 +04:00
return nil
2024-01-18 22:57:14 +07:00
case . local :
return nil
2023-04-20 16:52:55 +04:00
}
}
2022-08-03 12:36:51 +01:00
func setNetworkConfig ( _ cfg : NetCfg ) throws {
let r = sendSimpleXCmd ( . apiSetNetworkConfig ( networkConfig : cfg ) )
if case . cmdOk = r { return }
throw r
}
2024-10-25 20:09:59 +04:00
struct UserNtfConn {
2023-01-19 16:22:56 +00:00
var user : User
2023-12-09 21:59:40 +00:00
var connEntity_ : ConnectionEntity ?
2024-10-07 19:35:38 +04:00
var expectedMsg_ : NtfMsgInfo ?
2023-08-11 16:55:00 +01:00
2024-10-25 20:09:59 +04:00
var defaultBestAttemptNtf : NSENotificationData {
return if ! user . showNotifications {
. noNtf
} else if let connEntity = connEntity_ {
switch connEntity {
case let . rcvDirectMsgConnection ( _ , contact ) :
contact ? . chatSettings . enableNtfs = = . all
? . connectionEvent ( user , connEntity )
: . noNtf
case let . rcvGroupMsgConnection ( _ , groupInfo , _ ) :
groupInfo . chatSettings . enableNtfs = = . all
? . connectionEvent ( user , connEntity )
: . noNtf
case . sndFileConnection : . noNtf
case . rcvFileConnection : . noNtf
case let . userContactConnection ( _ , userContact ) :
userContact . groupId = = nil
? . connectionEvent ( user , connEntity )
: . noNtf
}
} else {
. noNtf
}
2023-08-11 16:55:00 +01:00
}
2022-06-19 19:49:39 +01:00
}