diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 6a4c9c465..0234064e8 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -8042,7 +8042,7 @@ CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 453; + CURRENT_PROJECT_VERSION = 454; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -8120,7 +8120,7 @@ CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 453; + CURRENT_PROJECT_VERSION = 454; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 12b244c7c..52eafd404 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -235,6 +235,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD Log.info("[AppDelegate] applicationDidBecomeActive.") guard !SNUtilitiesKit.isRunningTests else { return } + Log.info("[AppDelegate] Setting 'isMainAppActive' to true.") UserDefaults.sharedLokiProject?[.isMainAppActive] = true ensureRootViewController(calledFrom: .didBecomeActive) @@ -263,6 +264,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD Log.info("[AppDelegate] applicationWillResignActive.") clearAllNotificationsAndRestoreBadgeCount() + Log.info("[AppDelegate] Setting 'isMainAppActive' to false.") UserDefaults.sharedLokiProject?[.isMainAppActive] = false Log.flush() diff --git a/SessionMessagingKit/Database/Models/SessionThread.swift b/SessionMessagingKit/Database/Models/SessionThread.swift index 608c211e0..b55d160a4 100644 --- a/SessionMessagingKit/Database/Models/SessionThread.swift +++ b/SessionMessagingKit/Database/Models/SessionThread.swift @@ -488,24 +488,11 @@ public extension SessionThread { // If the thread is a message request then we only want to notify for the first message if self.variant == .contact && isMessageRequest { - let hasHiddenMessageRequests: Bool = db[.hasHiddenMessageRequests] - - // If the user hasn't hidden the message requests section then only show the notification if - // all the other message request threads have been read - if !hasHiddenMessageRequests { - let numUnreadMessageRequestThreads: Int = (try? SessionThread - .unreadMessageRequestsCountQuery(userPublicKey: userPublicKey, includeNonVisible: true) - .fetchOne(db)) - .defaulting(to: 1) - - guard numUnreadMessageRequestThreads == 1 else { return false } - } - // We only want to show a notification for the first interaction in the thread guard ((try? self.interactions.fetchCount(db)) ?? 0) <= 1 else { return false } // Need to re-show the message requests section if it had been hidden - if hasHiddenMessageRequests { + if db[.hasHiddenMessageRequests] { db[.hasHiddenMessageRequests] = false } } diff --git a/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift b/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift index 0ac3d639c..3c5ed9054 100644 --- a/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift +++ b/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift @@ -19,7 +19,6 @@ public enum MessageReceiverError: LocalizedError { case noThread case selfSend case decryptionFailed - case invalidGroupPublicKey case noGroupKeyPair case invalidConfigMessageHandling case requiredThreadNotInConfig @@ -68,7 +67,6 @@ public enum MessageReceiverError: LocalizedError { case .decryptionFailed: return "Decryption failed." // Shared sender keys - case .invalidGroupPublicKey: return "Invalid group public key." case .noGroupKeyPair: return "Missing group key pair." case .invalidConfigMessageHandling: return "Invalid handling of a config message." diff --git a/SessionNotificationServiceExtension/NSENotificationPresenter.swift b/SessionNotificationServiceExtension/NSENotificationPresenter.swift index 425b1180a..1cc37a61b 100644 --- a/SessionNotificationServiceExtension/NSENotificationPresenter.swift +++ b/SessionNotificationServiceExtension/NSENotificationPresenter.swift @@ -15,6 +15,7 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol { // Ensure we should be showing a notification for the thread guard thread.shouldShowNotification(db, for: interaction, isMessageRequest: isMessageRequest) else { + Log.info("Ignoring notification because thread reported that we shouldn't show it.") return } diff --git a/SessionNotificationServiceExtension/NotificationError.swift b/SessionNotificationServiceExtension/NotificationError.swift index 355c5563c..8f16548fc 100644 --- a/SessionNotificationServiceExtension/NotificationError.swift +++ b/SessionNotificationServiceExtension/NotificationError.swift @@ -17,8 +17,8 @@ enum NotificationError: Error, CustomStringConvertible { case .processing(let result): return "Failed to process notification (\(result)) (NotificationError.processing)." case .messageProcessing: return "Failed to process message (NotificationError.messageProcessing)." case .ignorableMessage: return "Ignorable message (NotificationError.ignorableMessage)." - case .messageHandling(let error): return "Failed to handle message (\(error)) (NotificationError.messageHandling)." - case .other(let error): return "Unknown error occurred: \(error) (NotificationError.other)." + case .messageHandling(let error): return "Failed to handle message (\("\(error)".noPeriod)) (NotificationError.messageHandling)." + case .other(let error): return "Unknown error occurred: \("\(error)".noPeriod)) (NotificationError.other)." } } } diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index eeb90cbc9..4dace37f6 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -36,12 +36,12 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension // Abort if the main app is running guard !(UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else { Log.info("didReceive called while main app running.") - return self.completeSilenty(isMainAppAndActive: true) + return self.completeSilenty(handledNotification: false, isMainAppAndActive: true) } guard let notificationContent = request.content.mutableCopy() as? UNMutableNotificationContent else { Log.info("didReceive called with no content.") - return self.completeSilenty() + return self.completeSilenty(handledNotification: false) } Log.info("didReceive called.") @@ -69,7 +69,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension guard metadata.accountId == getUserHexEncodedPublicKey(using: dependencies) else { guard !isPerformingResetup else { Log.error("Received notification for an accountId that isn't the current user, resetup failed.") - return self.completeSilenty() + return self.completeSilenty(handledNotification: false) } Log.warn("Received notification for an accountId that isn't the current user, attempting to resetup.") @@ -90,9 +90,15 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension // these will most commonly be call or config messages) case .successTooLong: Log.info("Received too long notification for namespace: \(metadata.namespace), dataLength: \(metadata.dataLength).") - return self.completeSilenty() + return self.completeSilenty(handledNotification: false) - case .legacyForceSilent, .failureNoContent: return self.completeSilenty() + case .legacyForceSilent: + Log.info("Ignoring non-group legacy notification.") + return self.completeSilenty(handledNotification: false) + + case .failureNoContent: + Log.warn("Failed due to missing notification content.") + return self.completeSilenty(handledNotification: false) } } @@ -189,6 +195,15 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension case (true, false): try MessageReceiver.insertCallInfoMessage(db, for: callMessage) + + // Perform any required post-handling logic + try MessageReceiver.postHandleMessage( + db, + threadId: threadId, + threadVariant: threadVariant, + message: messageInfo.message + ) + return self?.handleSuccessForIncomingCall(db, for: callMessage) } @@ -213,8 +228,8 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension } db.afterNextTransaction( - onCommit: { _ in self?.completeSilenty() }, - onRollback: { _ in self?.completeSilenty() } + onCommit: { _ in self?.completeSilenty(handledNotification: true) }, + onRollback: { _ in self?.completeSilenty(handledNotification: false) } ) } catch { @@ -222,9 +237,22 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension // the error outside of the database let handleError = { switch error { - case MessageReceiverError.invalidGroupPublicKey, MessageReceiverError.noGroupKeyPair, - MessageReceiverError.outdatedMessage, NotificationError.ignorableMessage: - self?.completeSilenty() + case MessageReceiverError.noGroupKeyPair: + Log.warn("Failed due to having no legacy group decryption keys.") + self?.completeSilenty(handledNotification: false) + + case MessageReceiverError.outdatedMessage: + Log.info("Ignoring notification for already seen message.") + self?.completeSilenty(handledNotification: false) + + case NotificationError.ignorableMessage: + Log.info("Ignoring message which requires no notification.") + self?.completeSilenty(handledNotification: false) + + case MessageReceiverError.duplicateMessage, MessageReceiverError.duplicateControlMessage, + MessageReceiverError.duplicateMessageNewSnode: + Log.info("Ignoring duplicate message (probably received it just before going to the background).") + self?.completeSilenty(handledNotification: false) case NotificationError.messageProcessing: self?.handleFailure(for: notificationContent, error: .messageProcessing) @@ -282,7 +310,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension switch result { case .failure(let error): Log.error("Failed to complete migrations: \(error).") - self?.completeSilenty() + self?.completeSilenty(handledNotification: false) case .success: // We should never receive a non-voip notification on an app that doesn't support @@ -292,7 +320,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension // and don't disturb the user. Messages will be processed when they open the app. guard Storage.shared[.isReadyForAppExtensions] else { Log.error("Not ready for extensions.") - self?.completeSilenty() + self?.completeSilenty(handledNotification: false) return } @@ -317,7 +345,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension // App isn't ready until storage is ready AND all version migrations are complete. guard Storage.shared.isValid else { Log.error("Storage invalid.") - return self.completeSilenty() + return self.completeSilenty(handledNotification: false) } // If the app wasn't ready then mark it as ready now @@ -363,10 +391,10 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension // Called just before the extension will be terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. Log.warn("Execution time expired.") - completeSilenty() + completeSilenty(handledNotification: false) } - private func completeSilenty(isMainAppAndActive: Bool = false) { + private func completeSilenty(handledNotification: Bool, isMainAppAndActive: Bool = false) { // Ensure we only run this once guard hasCompleted.mutate({ hasCompleted in @@ -382,7 +410,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension .map { NSNumber(value: $0) } .defaulting(to: NSNumber(value: 0)) - Log.info("Complete silently.") + Log.info(handledNotification ? "Completed after handling notification." : "Completed silently.") if !isMainAppAndActive { Storage.suspendDatabaseAccess() } @@ -412,7 +440,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension else { Log.info("Successfully notified main app of call message.") UserDefaults.sharedLokiProject?[.lastCallPreOffer] = Date() - self?.completeSilenty() + self?.completeSilenty(handledNotification: true) } } } @@ -457,8 +485,8 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension Log.info("Add remote notification request.") db.afterNextTransaction( - onCommit: { [weak self] _ in self?.completeSilenty() }, - onRollback: { [weak self] _ in self?.completeSilenty() } + onCommit: { [weak self] _ in self?.completeSilenty(handledNotification: true) }, + onRollback: { [weak self] _ in self?.completeSilenty(handledNotification: false) } ) } diff --git a/SessionUtilitiesKit/General/Logging.swift b/SessionUtilitiesKit/General/Logging.swift index 6ad692b68..aec96fd3f 100644 --- a/SessionUtilitiesKit/General/Logging.swift +++ b/SessionUtilitiesKit/General/Logging.swift @@ -377,11 +377,11 @@ public class Logger { #if DEBUG print(logMessage) - #endif - + #else if forceNSLog { NSLog(message) } + #endif } } @@ -416,7 +416,7 @@ private extension DispatchQueue { } } -// FIXME: Remove this once everything has been updated to use the new `Log.x()` methods +// FIXME: Remove this once everything has been updated to use the new `Log.x()` methods. public func SNLog(_ message: String, forceNSLog: Bool = false) { Log.info(message) } diff --git a/SessionUtilitiesKit/General/String+Utilities.swift b/SessionUtilitiesKit/General/String+Utilities.swift index fe7d66cc2..18c55bf3f 100644 --- a/SessionUtilitiesKit/General/String+Utilities.swift +++ b/SessionUtilitiesKit/General/String+Utilities.swift @@ -77,6 +77,14 @@ public extension String { // MARK: - Formatting +public extension String { + var noPeriod: String { + guard self.hasSuffix(".") && !self.hasSuffix("...") else { return self } + + return String(self.prefix(count - 1)) + } +} + public extension String.StringInterpolation { mutating func appendInterpolation(plural value: Int) { appendInterpolation(value == 1 ? "" : "s") // stringlint:disable diff --git a/SessionUtilitiesKit/JobRunner/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift index ba75a639b..7bc83c3c6 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -1633,7 +1633,7 @@ public final class JobQueue: Hashable { // immediately (in this case we don't trigger any job callbacks because the // job isn't actually done, it's going to try again immediately) if self.type == .blocking && job.shouldBlock { - SNLog("[JobRunner] \(queueContext) \(job.variant) job failed due to error: \(error ?? JobRunnerError.unknown); retrying immediately") + SNLog("[JobRunner] \(queueContext) \(job.variant) job failed due to error: \("\(error ?? JobRunnerError.unknown)".noPeriod); retrying immediately") // If it was a possible deferral loop then we don't actually want to // retry the job (even if it's a blocking one, this gives a small chance