diff --git a/apps/ios/Shared/Model/NSESubscriber.swift b/apps/ios/Shared/Model/NSESubscriber.swift index f52e72beab..a4a5dc8152 100644 --- a/apps/ios/Shared/Model/NSESubscriber.swift +++ b/apps/ios/Shared/Model/NSESubscriber.swift @@ -16,13 +16,13 @@ private var nseSubscribers: [UUID:NSESubscriber] = [:] private let SUSPENDING_TIMEOUT: TimeInterval = 2 // timeout should be larger than SUSPENDING_TIMEOUT -func waitNSESuspended(timeout: TimeInterval, dispatchQueue: DispatchQueue = DispatchQueue.main, suspended: @escaping (Bool) -> Void) { +func waitNSESuspended(timeout: TimeInterval, suspended: @escaping (Bool) -> Void) { if timeout <= SUSPENDING_TIMEOUT { logger.warning("waitNSESuspended: small timeout \(timeout), using \(SUSPENDING_TIMEOUT + 1)") } var state = nseStateGroupDefault.get() if case .suspended = state { - dispatchQueue.async { suspended(true) } + DispatchQueue.main.async { suspended(true) } return } let id = UUID() @@ -45,7 +45,7 @@ func waitNSESuspended(timeout: TimeInterval, dispatchQueue: DispatchQueue = Disp logger.debug("waitNSESuspended notifySuspended: calling suspended(\(ok))") suspendedCalled = true nseSubscribers.removeValue(forKey: id) - dispatchQueue.async { suspended(ok) } + DispatchQueue.main.async { suspended(ok) } } } diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index eff3110962..442ba20796 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -403,7 +403,7 @@ func apiGetNtfToken() -> (DeviceToken?, NtfTknStatus?, NotificationsMode) { case let .ntfToken(token, status, ntfMode): return (token, status, ntfMode) case .chatCmdError(_, .errorAgent(.CMD(.PROHIBITED))): return (nil, nil, .off) default: - logger.debug("apiGetNtfToken response: \(String(describing: r), privacy: .public)") + logger.debug("apiGetNtfToken response: \(String(describing: r))") return (nil, nil, .off) } } diff --git a/apps/ios/Shared/Model/SuspendChat.swift b/apps/ios/Shared/Model/SuspendChat.swift index 9b03f38f3c..0229bff2b3 100644 --- a/apps/ios/Shared/Model/SuspendChat.swift +++ b/apps/ios/Shared/Model/SuspendChat.swift @@ -19,11 +19,13 @@ let terminationTimeout: Int = 3 // seconds let activationDelay: TimeInterval = 1.5 +let nseSuspendTimeout: TimeInterval = 5 + private func _suspendChat(timeout: Int) { // this is a redundant check to prevent logical errors, like the one fixed in this PR let state = AppChatState.shared.value if !state.canSuspend { - logger.error("_suspendChat called, current state: \(state.rawValue, privacy: .public)") + logger.error("_suspendChat called, current state: \(state.rawValue)") } else if ChatModel.ok { AppChatState.shared.set(.suspending) apiSuspendChat(timeoutMicroseconds: timeout * 1000000) @@ -134,20 +136,33 @@ func initChatAndMigrate(refreshInvitations: Bool = true) { } } -func startChatAndActivate(dispatchQueue: DispatchQueue = DispatchQueue.main, _ completion: @escaping () -> Void) { +func startChatForCall() { + logger.debug("DEBUGGING: startChatForCall") + if ChatModel.shared.chatRunning == true { + ChatReceiver.shared.start() + logger.debug("DEBUGGING: startChatForCall: after ChatReceiver.shared.start") + } + if .active != AppChatState.shared.value { + logger.debug("DEBUGGING: startChatForCall: before activateChat") + activateChat() + logger.debug("DEBUGGING: startChatForCall: after activateChat") + } +} + +func startChatAndActivate(_ completion: @escaping () -> Void) { logger.debug("DEBUGGING: startChatAndActivate") if ChatModel.shared.chatRunning == true { ChatReceiver.shared.start() logger.debug("DEBUGGING: startChatAndActivate: after ChatReceiver.shared.start") } - if .active == AppChatState.shared.value { + if case .active = AppChatState.shared.value { completion() } else if nseStateGroupDefault.get().inactive { activate() } else { // setting app state to "activating" to notify NSE that it should suspend setAppState(.activating) - waitNSESuspended(timeout: 10, dispatchQueue: dispatchQueue) { ok in + waitNSESuspended(timeout: nseSuspendTimeout) { ok in if !ok { // if for some reason NSE failed to suspend, // e.g., it crashed previously without setting its state to "suspended", diff --git a/apps/ios/Shared/SimpleXApp.swift b/apps/ios/Shared/SimpleXApp.swift index f72ffcaaaf..60d1cf7256 100644 --- a/apps/ios/Shared/SimpleXApp.swift +++ b/apps/ios/Shared/SimpleXApp.swift @@ -98,12 +98,12 @@ struct SimpleXApp: App { if legacyDatabase, case .documents = dbContainerGroupDefault.get() { dbContainerGroupDefault.set(.documents) setMigrationState(.offer) - logger.debug("SimpleXApp init: using legacy DB in documents folder: \(getAppDatabasePath(), privacy: .public)*.db") + logger.debug("SimpleXApp init: using legacy DB in documents folder: \(getAppDatabasePath())*.db") } else { dbContainerGroupDefault.set(.group) setMigrationState(.ready) - logger.debug("SimpleXApp init: using DB in app group container: \(getAppDatabasePath(), privacy: .public)*.db") - logger.debug("SimpleXApp init: legacy DB\(legacyDatabase ? "" : " not", privacy: .public) present") + logger.debug("SimpleXApp init: using DB in app group container: \(getAppDatabasePath())*.db") + logger.debug("SimpleXApp init: legacy DB\(legacyDatabase ? "" : " not") present") } } diff --git a/apps/ios/Shared/Views/Call/ActiveCallView.swift b/apps/ios/Shared/Views/Call/ActiveCallView.swift index e613476a1d..a3be2e900a 100644 --- a/apps/ios/Shared/Views/Call/ActiveCallView.swift +++ b/apps/ios/Shared/Views/Call/ActiveCallView.swift @@ -38,13 +38,13 @@ struct ActiveCallView: View { } } .onAppear { - logger.debug("ActiveCallView: appear client is nil \(client == nil), scenePhase \(String(describing: scenePhase), privacy: .public), canConnectCall \(canConnectCall)") + logger.debug("ActiveCallView: appear client is nil \(client == nil), scenePhase \(String(describing: scenePhase)), canConnectCall \(canConnectCall)") AppDelegate.keepScreenOn(true) createWebRTCClient() dismissAllSheets() } .onChange(of: canConnectCall) { _ in - logger.debug("ActiveCallView: canConnectCall changed to \(canConnectCall, privacy: .public)") + logger.debug("ActiveCallView: canConnectCall changed to \(canConnectCall)") createWebRTCClient() } .onDisappear { diff --git a/apps/ios/Shared/Views/Call/CallController.swift b/apps/ios/Shared/Views/Call/CallController.swift index fcd3a85584..6da8294ef8 100644 --- a/apps/ios/Shared/Views/Call/CallController.swift +++ b/apps/ios/Shared/Views/Call/CallController.swift @@ -130,7 +130,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse // The delay allows to accept the second call before suspending a chat // see `.onChange(of: scenePhase)` in SimpleXApp DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in - logger.debug("CallController: shouldSuspendChat \(String(describing: self?.shouldSuspendChat), privacy: .public)") + logger.debug("CallController: shouldSuspendChat \(String(describing: self?.shouldSuspendChat))") if ChatModel.shared.activeCall == nil && self?.shouldSuspendChat == true { self?.shouldSuspendChat = false suspendChat() @@ -142,45 +142,57 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse @objc(pushRegistry:didUpdatePushCredentials:forType:) func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) { - logger.debug("CallController: didUpdate push credentials for type \(type.rawValue, privacy: .public)") + logger.debug("CallController: didUpdate push credentials for type \(type.rawValue)") } func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) { - logger.debug("CallController: did receive push with type \(type.rawValue, privacy: .public)") + logger.debug("CallController: did receive push with type \(type.rawValue)") if type != .voIP { completion() return } - logger.debug("CallController: initializing chat") - if (!ChatModel.shared.chatInitialized) { - initChatAndMigrate(refreshInvitations: false) + if AppChatState.shared.value == .stopped { + self.reportExpiredCall(payload: payload, completion) + return } - startChatAndActivate(dispatchQueue: DispatchQueue.global()) { - self.shouldSuspendChat = true - // There are no invitations in the model, as it was processed by NSE - _ = try? justRefreshCallInvitations() - // logger.debug("CallController justRefreshCallInvitations: \(String(describing: m.callInvitations))") - // Extract the call information from the push notification payload - let m = ChatModel.shared - if let contactId = payload.dictionaryPayload["contactId"] as? String, - let invitation = m.callInvitations[contactId] { + if (!ChatModel.shared.chatInitialized) { + logger.debug("CallController: initializing chat") + do { + try initializeChat(start: true, refreshInvitations: false) + } catch let error { + logger.error("CallController: initializing chat error: \(error)") + self.reportExpiredCall(payload: payload, completion) + return + } + } + logger.debug("CallController: initialized chat") + startChatForCall() + logger.debug("CallController: started chat") + self.shouldSuspendChat = true + // There are no invitations in the model, as it was processed by NSE + _ = try? justRefreshCallInvitations() + logger.debug("CallController: updated call invitations chat") + // logger.debug("CallController justRefreshCallInvitations: \(String(describing: m.callInvitations))") + // Extract the call information from the push notification payload + let m = ChatModel.shared + if let contactId = payload.dictionaryPayload["contactId"] as? String, + let invitation = m.callInvitations[contactId] { + let update = self.cxCallUpdate(invitation: invitation) + if let uuid = invitation.callkitUUID { + logger.debug("CallController: report pushkit call via CallKit") let update = self.cxCallUpdate(invitation: invitation) - if let uuid = invitation.callkitUUID { - logger.debug("CallController: report pushkit call via CallKit") - let update = self.cxCallUpdate(invitation: invitation) - self.provider.reportNewIncomingCall(with: uuid, update: update) { error in - if error != nil { - m.callInvitations.removeValue(forKey: contactId) - } - // Tell PushKit that the notification is handled. - completion() + self.provider.reportNewIncomingCall(with: uuid, update: update) { error in + if error != nil { + m.callInvitations.removeValue(forKey: contactId) } - } else { - self.reportExpiredCall(update: update, completion) + // Tell PushKit that the notification is handled. + completion() } } else { - self.reportExpiredCall(payload: payload, completion) + self.reportExpiredCall(update: update, completion) } + } else { + self.reportExpiredCall(payload: payload, completion) } } @@ -211,7 +223,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse } func reportNewIncomingCall(invitation: RcvCallInvitation, completion: @escaping (Error?) -> Void) { - logger.debug("CallController.reportNewIncomingCall, UUID=\(String(describing: invitation.callkitUUID), privacy: .public)") + logger.debug("CallController.reportNewIncomingCall, UUID=\(String(describing: invitation.callkitUUID))") if CallController.useCallKit(), let uuid = invitation.callkitUUID { if invitation.callTs.timeIntervalSinceNow >= -180 { let update = cxCallUpdate(invitation: invitation) @@ -351,7 +363,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse private func requestTransaction(with action: CXAction, onSuccess: @escaping () -> Void = {}) { controller.request(CXTransaction(action: action)) { error in if let error = error { - logger.error("CallController.requestTransaction error requesting transaction: \(error.localizedDescription, privacy: .public)") + logger.error("CallController.requestTransaction error requesting transaction: \(error.localizedDescription)") } else { logger.debug("CallController.requestTransaction requested transaction successfully") onSuccess() diff --git a/apps/ios/SimpleX NSE/NotificationService.swift b/apps/ios/SimpleX NSE/NotificationService.swift index f9b4852e53..5b1988e896 100644 --- a/apps/ios/SimpleX NSE/NotificationService.swift +++ b/apps/ios/SimpleX NSE/NotificationService.swift @@ -16,9 +16,11 @@ let logger = Logger() let appSuspendingDelay: UInt64 = 2_500_000_000 -let nseSuspendDelay: TimeInterval = 2 +typealias SuspendSchedule = (delay: TimeInterval, timeout: Int) -let nseSuspendTimeout: Int = 5 +let nseSuspendSchedule: SuspendSchedule = (2, 4) + +let fastNSESuspendSchedule: SuspendSchedule = (1, 1) typealias NtfStream = ConcurrentQueue @@ -32,7 +34,7 @@ actor PendingNtfs { private var ntfStreams: [String: NtfStream] = [:] func createStream(_ id: String) async { - logger.debug("NotificationService PendingNtfs.createStream: \(id, privacy: .public)") + logger.debug("NotificationService PendingNtfs.createStream: \(id)") if ntfStreams[id] == nil { ntfStreams[id] = ConcurrentQueue() logger.debug("NotificationService PendingNtfs.createStream: created ConcurrentQueue") @@ -40,14 +42,14 @@ actor PendingNtfs { } func readStream(_ id: String, for nse: NotificationService, ntfInfo: NtfMessages) async { - logger.debug("NotificationService PendingNtfs.readStream: \(id, privacy: .public) \(ntfInfo.ntfMessages.count, privacy: .public)") + logger.debug("NotificationService PendingNtfs.readStream: \(id) \(ntfInfo.ntfMessages.count)") if !ntfInfo.user.showNotifications { nse.setBestAttemptNtf(.empty) } if let s = ntfStreams[id] { logger.debug("NotificationService PendingNtfs.readStream: has stream") var expected = Set(ntfInfo.ntfMessages.map { $0.msgId }) - logger.debug("NotificationService PendingNtfs.readStream: expecting: \(expected, privacy: .public)") + logger.debug("NotificationService PendingNtfs.readStream: expecting: \(expected)") var readCancelled = false var dequeued: DequeueElement? nse.cancelRead = { @@ -66,7 +68,7 @@ actor PendingNtfs { } else if case let .msgInfo(info) = ntf { let found = expected.remove(info.msgId) if found != nil { - logger.debug("NotificationService PendingNtfs.readStream: msgInfo, last: \(expected.isEmpty, privacy: .public)") + logger.debug("NotificationService PendingNtfs.readStream: msgInfo, last: \(expected.isEmpty)") if expected.isEmpty { break } } else if let msgTs = ntfInfo.msgTs, info.msgTs > msgTs { logger.debug("NotificationService PendingNtfs.readStream: unexpected msgInfo") @@ -88,7 +90,7 @@ actor PendingNtfs { } func writeStream(_ id: String, _ ntf: NSENotification) async { - logger.debug("NotificationService PendingNtfs.writeStream: \(id, privacy: .public)") + logger.debug("NotificationService PendingNtfs.writeStream: \(id)") if let s = ntfStreams[id] { logger.debug("NotificationService PendingNtfs.writeStream: writing ntf") s.enqueue(ntf) @@ -208,7 +210,7 @@ class NotificationService: UNNotificationServiceExtension { self.contentHandler = contentHandler registerGroupDefaults() let appState = appStateGroupDefault.get() - logger.debug("NotificationService: app is \(appState.rawValue, privacy: .public)") + logger.debug("NotificationService: app is \(appState.rawValue)") switch appState { case .stopped: setBadgeCount() @@ -238,7 +240,7 @@ class NotificationService: UNNotificationServiceExtension { } } } - logger.debug("NotificationService: app state is now \(state.rawValue, privacy: .public)") + logger.debug("NotificationService: app state is now \(state.rawValue)") if state.inactive { receiveNtfMessages(request, contentHandler) } else { @@ -267,7 +269,7 @@ class NotificationService: UNNotificationServiceExtension { let dbStatus = startChat() if case .ok = dbStatus, let ntfInfo = apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo) { - logger.debug("NotificationService: receiveNtfMessages: apiGetNtfMessage \(String(describing: ntfInfo.ntfMessages.count), privacy: .public)") + logger.debug("NotificationService: receiveNtfMessages: apiGetNtfMessage \(String(describing: ntfInfo.ntfMessages.count))") if let connEntity = ntfInfo.connEntity_ { setBestAttemptNtf( ntfInfo.ntfsEnabled @@ -279,7 +281,7 @@ class NotificationService: UNNotificationServiceExtension { NtfStreamSemaphores.shared.waitForStream(id) if receiveEntityId != nil { Task { - logger.debug("NotificationService: receiveNtfMessages: in Task, connEntity id \(id, privacy: .public)") + logger.debug("NotificationService: receiveNtfMessages: in Task, connEntity id \(id)") await PendingNtfs.shared.createStream(id) await PendingNtfs.shared.readStream(id, for: self, ntfInfo: ntfInfo) deliverBestAttemptNtf() @@ -297,7 +299,7 @@ class NotificationService: UNNotificationServiceExtension { override func serviceExtensionTimeWillExpire() { logger.debug("DEBUGGING: NotificationService.serviceExtensionTimeWillExpire") - deliverBestAttemptNtf() + deliverBestAttemptNtf(urgent: true) } func setBadgeCount() { @@ -319,7 +321,7 @@ class NotificationService: UNNotificationServiceExtension { } } - private func deliverBestAttemptNtf() { + private func deliverBestAttemptNtf(urgent: Bool = false) { logger.debug("NotificationService.deliverBestAttemptNtf") if let cancel = cancelRead { cancelRead = nil @@ -329,20 +331,55 @@ class NotificationService: UNNotificationServiceExtension { receiveEntityId = nil NtfStreamSemaphores.shared.signalStreamReady(id) } + let suspend: Bool if let t = threadId { threadId = nil - if NSEThreads.shared.endThread(t) { - logger.debug("NotificationService.deliverBestAttemptNtf: will suspend") - // suspension is delayed to allow chat core finalise any processing - // (e.g., send delivery receipts) - DispatchQueue.global().asyncAfter(deadline: .now() + nseSuspendDelay) { - if NSEThreads.shared.noThreads { - logger.debug("NotificationService.deliverBestAttemptNtf: suspending...") - suspendChat(nseSuspendTimeout) + suspend = NSEThreads.shared.endThread(t) && NSEThreads.shared.noThreads + } else { + suspend = false + } + deliverCallkitOrNotification(urgent: urgent, suspend: suspend) + } + + private func deliverCallkitOrNotification(urgent: Bool, suspend: Bool = false) { + if case .callkit = bestAttemptNtf { + logger.debug("NotificationService.deliverCallkitOrNotification: will suspend, callkit") + if urgent { + // suspending NSE even though there may be other notifications + // to allow the app to process callkit call + suspendChat(0) + deliverNotification() + } else { + // suspending NSE with delay and delivering after the suspension + // because pushkit notification must be processed without delay + // to avoid app termination + DispatchQueue.global().asyncAfter(deadline: .now() + fastNSESuspendSchedule.delay) { + suspendChat(fastNSESuspendSchedule.timeout) + DispatchQueue.global().asyncAfter(deadline: .now() + Double(fastNSESuspendSchedule.timeout)) { + self.deliverNotification() } } } + } else { + if suspend { + logger.debug("NotificationService.deliverCallkitOrNotification: will suspend") + if urgent { + suspendChat(0) + } else { + // suspension is delayed to allow chat core finalise any processing + // (e.g., send delivery receipts) + DispatchQueue.global().asyncAfter(deadline: .now() + nseSuspendSchedule.delay) { + if NSEThreads.shared.noThreads { + suspendChat(nseSuspendSchedule.timeout) + } + } + } + } + deliverNotification() } + } + + private func deliverNotification() { if let handler = contentHandler, let ntf = bestAttemptNtf { contentHandler = nil bestAttemptNtf = nil @@ -357,17 +394,14 @@ class NotificationService: UNNotificationServiceExtension { switch ntf { case let .nse(content): deliver(content) case let .callkit(invitation): + logger.debug("NotificationService reportNewIncomingVoIPPushPayload for \(invitation.contact.id)") CXProvider.reportNewIncomingVoIPPushPayload([ "displayName": invitation.contact.displayName, "contactId": invitation.contact.id, "media": invitation.callType.media.rawValue ]) { error in - if error == nil { - deliver(nil) - } else { - logger.debug("NotificationService reportNewIncomingVoIPPushPayload success to CallController for \(invitation.contact.id)") - deliver(createCallInvitationNtf(invitation)) - } + logger.debug("reportNewIncomingVoIPPushPayload result: \(error)") + deliver(error == nil ? nil : createCallInvitationNtf(invitation)) } case .empty: deliver(nil) // used to mute notifications that did not unsubscribe yet case .msgInfo: deliver(nil) // unreachable, the best attempt is never set to msgInfo @@ -402,14 +436,14 @@ 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") - suspendChat(nseSuspendTimeout) + suspendChat(fastNSESuspendSchedule.timeout) } } func appStateSubscriber(onState: @escaping (AppState) -> Void) -> AppSubscriber { appMessageSubscriber { msg in if case let .state(state) = msg { - logger.debug("NotificationService: appStateSubscriber \(state.rawValue, privacy: .public)") + logger.debug("NotificationService: appStateSubscriber \(state.rawValue)") onState(state) } } @@ -425,23 +459,31 @@ let xftpConfig: XFTPFileConfig? = getXFTPCfg() // Subsequent calls to didReceive will be waiting on semaphore and won't start chat again, as it will be .active func startChat() -> DBMigrationResult? { logger.debug("NotificationService: startChat") - if case .active = NSEChatState.shared.value { return .ok } + // only skip creating if there is chat controller + if case .active = NSEChatState.shared.value, hasChatCtrl() { return .ok } startLock.wait() defer { startLock.signal() } - return switch NSEChatState.shared.value { - case .created: doStartChat() - case .starting: .ok // it should never get to this branch, as it would be waiting for start on startLock - case .active: .ok - case .suspending: activateChat() - case .suspended: activateChat() + if hasChatCtrl() { + return switch NSEChatState.shared.value { + case .created: doStartChat() + case .starting: .ok // it should never get to this branch, as it would be waiting for start on startLock + case .active: .ok + case .suspending: activateChat() + case .suspended: activateChat() + } + } else { + // Ignore state in preference if there is no chat controller. + // State in preference may have failed to update e.g. because of a crash. + NSEChatState.shared.set(.created) + return doStartChat() } } func doStartChat() -> DBMigrationResult? { logger.debug("NotificationService: doStartChat") - hs_init(0, nil) + haskell_init_nse() let (_, dbStatus) = chatMigrateInit(confirmMigrations: defaultMigrationConfirmation(), backgroundMode: true) if dbStatus != .ok { resetChatCtrl() @@ -477,7 +519,7 @@ func doStartChat() -> DBMigrationResult? { return .ok } } catch { - logger.error("NotificationService startChat error: \(responseError(error), privacy: .public)") + logger.error("NotificationService startChat error: \(responseError(error))") } } else { logger.debug("NotificationService: no active user") @@ -504,8 +546,10 @@ func suspendChat(_ timeout: Int) { logger.debug("NotificationService: suspendChat") let state = NSEChatState.shared.value if !state.canSuspend { - logger.error("NotificationService suspendChat called, current state: \(state.rawValue, privacy: .public)") - } else { + logger.error("NotificationService suspendChat called, current state: \(state.rawValue)") + } else if hasChatCtrl() { + // only suspend if we have chat controller to avoid crashes when suspension is + // attempted when chat controller was not created suspendLock.wait() defer { suspendLock.signal() } @@ -571,7 +615,7 @@ private let isInChina = SKStorefront().countryCode == "CHN" private func useCallKit() -> Bool { !isInChina && callKitEnabledGroupDefault.get() } func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? { - logger.debug("NotificationService receivedMsgNtf: \(res.responseType, privacy: .public)") + logger.debug("NotificationService receivedMsgNtf: \(res.responseType)") switch res { case let .contactConnected(user, contact, _): return (contact.id, .nse(createContactConnectedNtf(user, contact))) @@ -613,6 +657,9 @@ func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? { case .chatSuspended: chatSuspended() return nil + case let .chatError(_, err): + logger.error("NotificationService receivedMsgNtf error: \(String(describing: err))") + return nil default: logger.debug("NotificationService receivedMsgNtf ignored event: \(res.responseType)") return nil @@ -627,17 +674,22 @@ func updateNetCfg() { try setNetworkConfig(networkConfig) networkConfig = newNetConfig } catch { - logger.error("NotificationService apply changed network config error: \(responseError(error), privacy: .public)") + logger.error("NotificationService apply changed network config error: \(responseError(error))") } } } func apiGetActiveUser() -> User? { let r = sendSimpleXCmd(.showActiveUser) - logger.debug("apiGetActiveUser sendSimpleXCmd response: \(String(describing: r))") + logger.debug("apiGetActiveUser sendSimpleXCmd response: \(r.responseType)") switch r { case let .activeUser(user): return user - case .chatCmdError(_, .error(.noActiveUser)): return nil + 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 default: logger.error("NotificationService apiGetActiveUser unexpected response: \(String(describing: r))") return nil @@ -699,11 +751,12 @@ func apiGetNtfMessage(nonce: String, encNtfInfo: String) -> NtfMessages? { } let r = sendSimpleXCmd(.apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo)) if case let .ntfMessages(user, connEntity_, msgTs, ntfMessages) = r, let user = user { + logger.debug("apiGetNtfMessage response ntfMessages: \(ntfMessages.count)") return NtfMessages(user: user, connEntity_: connEntity_, msgTs: msgTs, ntfMessages: ntfMessages) } else if case let .chatCmdError(_, error) = r { logger.debug("apiGetNtfMessage error response: \(String.init(describing: error))") } else { - logger.debug("apiGetNtfMessage ignored response: \(r.responseType, privacy: .public) \(String.init(describing: r), privacy: .private)") + logger.debug("apiGetNtfMessage ignored response: \(r.responseType) \(String.init(describing: r))") } return nil } diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 801116bf80..c0d4d99a34 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -43,11 +43,11 @@ 5C3F1D562842B68D00EC8A82 /* IntegrityErrorItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3F1D552842B68D00EC8A82 /* IntegrityErrorItemView.swift */; }; 5C3F1D58284363C400EC8A82 /* PrivacySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */; }; 5C4B3B0A285FB130003915F2 /* DatabaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B3B09285FB130003915F2 /* DatabaseView.swift */; }; - 5C4E80DA2B3CCD090080FAE2 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D52B3CCD090080FAE2 /* libgmp.a */; }; - 5C4E80DB2B3CCD090080FAE2 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D62B3CCD090080FAE2 /* libffi.a */; }; - 5C4E80DC2B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D72B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a */; }; - 5C4E80DD2B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D82B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a */; }; - 5C4E80DE2B3CCD090080FAE2 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D92B3CCD090080FAE2 /* libgmpxx.a */; }; + 5C4E80EE2B4991300080FAE2 /* libHSsimplex-chat-5.4.2.1-6ax7BlvjLCrBLFyG7Bue3U.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80E92B4991300080FAE2 /* libHSsimplex-chat-5.4.2.1-6ax7BlvjLCrBLFyG7Bue3U.a */; }; + 5C4E80EF2B4991300080FAE2 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80EA2B4991300080FAE2 /* libgmpxx.a */; }; + 5C4E80F02B4991300080FAE2 /* libHSsimplex-chat-5.4.2.1-6ax7BlvjLCrBLFyG7Bue3U-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80EB2B4991300080FAE2 /* libHSsimplex-chat-5.4.2.1-6ax7BlvjLCrBLFyG7Bue3U-ghc9.6.3.a */; }; + 5C4E80F12B4991300080FAE2 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80EC2B4991300080FAE2 /* libgmp.a */; }; + 5C4E80F22B4991300080FAE2 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80ED2B4991300080FAE2 /* libffi.a */; }; 5C5346A827B59A6A004DF848 /* ChatHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5346A727B59A6A004DF848 /* ChatHelp.swift */; }; 5C55A91F283AD0E400C4E99E /* CallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C55A91E283AD0E400C4E99E /* CallManager.swift */; }; 5C55A921283CCCB700C4E99E /* IncomingCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C55A920283CCCB700C4E99E /* IncomingCallView.swift */; }; @@ -294,11 +294,11 @@ 5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacySettings.swift; sourceTree = ""; }; 5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "SimpleX (iOS).entitlements"; sourceTree = ""; }; 5C4B3B09285FB130003915F2 /* DatabaseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseView.swift; sourceTree = ""; }; - 5C4E80D52B3CCD090080FAE2 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - 5C4E80D62B3CCD090080FAE2 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 5C4E80D72B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a"; sourceTree = ""; }; - 5C4E80D82B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a"; sourceTree = ""; }; - 5C4E80D92B3CCD090080FAE2 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 5C4E80E92B4991300080FAE2 /* libHSsimplex-chat-5.4.2.1-6ax7BlvjLCrBLFyG7Bue3U.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.2.1-6ax7BlvjLCrBLFyG7Bue3U.a"; sourceTree = ""; }; + 5C4E80EA2B4991300080FAE2 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 5C4E80EB2B4991300080FAE2 /* libHSsimplex-chat-5.4.2.1-6ax7BlvjLCrBLFyG7Bue3U-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.2.1-6ax7BlvjLCrBLFyG7Bue3U-ghc9.6.3.a"; sourceTree = ""; }; + 5C4E80EC2B4991300080FAE2 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + 5C4E80ED2B4991300080FAE2 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; 5C5346A727B59A6A004DF848 /* ChatHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHelp.swift; sourceTree = ""; }; 5C55A91E283AD0E400C4E99E /* CallManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallManager.swift; sourceTree = ""; }; 5C55A920283CCCB700C4E99E /* IncomingCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomingCallView.swift; sourceTree = ""; }; @@ -519,13 +519,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5C4E80DD2B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a in Frameworks */, + 5C4E80EF2B4991300080FAE2 /* libgmpxx.a in Frameworks */, + 5C4E80EE2B4991300080FAE2 /* libHSsimplex-chat-5.4.2.1-6ax7BlvjLCrBLFyG7Bue3U.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - 5C4E80DA2B3CCD090080FAE2 /* libgmp.a in Frameworks */, - 5C4E80DC2B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - 5C4E80DB2B3CCD090080FAE2 /* libffi.a in Frameworks */, - 5C4E80DE2B3CCD090080FAE2 /* libgmpxx.a in Frameworks */, + 5C4E80F22B4991300080FAE2 /* libffi.a in Frameworks */, + 5C4E80F12B4991300080FAE2 /* libgmp.a in Frameworks */, + 5C4E80F02B4991300080FAE2 /* libHSsimplex-chat-5.4.2.1-6ax7BlvjLCrBLFyG7Bue3U-ghc9.6.3.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -587,11 +587,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - 5C4E80D62B3CCD090080FAE2 /* libffi.a */, - 5C4E80D52B3CCD090080FAE2 /* libgmp.a */, - 5C4E80D92B3CCD090080FAE2 /* libgmpxx.a */, - 5C4E80D82B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a */, - 5C4E80D72B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a */, + 5C4E80ED2B4991300080FAE2 /* libffi.a */, + 5C4E80EC2B4991300080FAE2 /* libgmp.a */, + 5C4E80EA2B4991300080FAE2 /* libgmpxx.a */, + 5C4E80EB2B4991300080FAE2 /* libHSsimplex-chat-5.4.2.1-6ax7BlvjLCrBLFyG7Bue3U-ghc9.6.3.a */, + 5C4E80E92B4991300080FAE2 /* libHSsimplex-chat-5.4.2.1-6ax7BlvjLCrBLFyG7Bue3U.a */, ); path = Libraries; sourceTree = ""; diff --git a/apps/ios/SimpleX.xcodeproj/xcshareddata/xcschemes/SimpleX (iOS).xcscheme b/apps/ios/SimpleX.xcodeproj/xcshareddata/xcschemes/SimpleX (iOS).xcscheme index 973c30c71e..6a1d4192e6 100644 --- a/apps/ios/SimpleX.xcodeproj/xcshareddata/xcschemes/SimpleX (iOS).xcscheme +++ b/apps/ios/SimpleX.xcodeproj/xcshareddata/xcschemes/SimpleX (iOS).xcscheme @@ -41,7 +41,7 @@ + version = "1.3"> @@ -47,16 +47,14 @@ + allowLocationSimulation = "YES"> chat_ctrl { +public func hasChatCtrl() -> Bool { + chatController != nil +} + +public func getChatCtrl() -> chat_ctrl { if let controller = chatController { return controller } fatalError("chat controller not initialized") } diff --git a/apps/ios/SimpleXChat/hs_init.c b/apps/ios/SimpleXChat/hs_init.c index 7a5ea24560..b597453be5 100644 --- a/apps/ios/SimpleXChat/hs_init.c +++ b/apps/ios/SimpleXChat/hs_init.c @@ -23,3 +23,17 @@ void haskell_init(void) { char **pargv = argv; hs_init_with_rtsopts(&argc, &pargv); } + +void haskell_init_nse(void) { + int argc = 5; + char *argv[] = { + "simplex", + "+RTS", // requires `hs_init_with_rtsopts` + "-A1m", // chunk size for new allocations + "-H1m", // initial heap size + "-xn", // non-moving GC + 0 + }; + char **pargv = argv; + hs_init_with_rtsopts(&argc, &pargv); +} diff --git a/apps/ios/SimpleXChat/hs_init.h b/apps/ios/SimpleXChat/hs_init.h index 48850e819c..a732fd7113 100644 --- a/apps/ios/SimpleXChat/hs_init.h +++ b/apps/ios/SimpleXChat/hs_init.h @@ -11,4 +11,6 @@ void haskell_init(void); +void haskell_init_nse(void); + #endif /* hs_init_h */