Added a notification to indicate the user has a new message request

Fixed a bug where the notification count could be increased for message requests
Fixed a bug where an approved contact could be 'unapproved' due to an order of execution issue when generating the config sync message
Fixed a check to avoid registering for push notifications when on the simulator (old check didn't cater for M1 Macs)
Moved the 'hasHiddenMessageRequests' into the group user defaults so it can be accessed within the notification extension
Added code to handle an edge case where an old client could incorrectly un-approve a contact via a legacy configuration message
pull/559/head
Morgan Pretty 3 years ago
parent dfbee2a520
commit 47314bd639

@ -65,7 +65,6 @@
34F308A21ECB469700BB7697 /* OWSBezierPathView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F308A11ECB469700BB7697 /* OWSBezierPathView.m */; };
4503F1BE20470A5B00CEE724 /* classic-quiet.aifc in Resources */ = {isa = PBXBuildFile; fileRef = 4503F1BB20470A5B00CEE724 /* classic-quiet.aifc */; };
4503F1BF20470A5B00CEE724 /* classic.aifc in Resources */ = {isa = PBXBuildFile; fileRef = 4503F1BC20470A5B00CEE724 /* classic.aifc */; };
450DF2051E0D74AC003D14BE /* Platform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 450DF2041E0D74AC003D14BE /* Platform.swift */; };
450DF2091E0DD2C6003D14BE /* UserNotificationsAdaptee.swift in Sources */ = {isa = PBXBuildFile; fileRef = 450DF2081E0DD2C6003D14BE /* UserNotificationsAdaptee.swift */; };
451166C01FD86B98000739BA /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451166BF1FD86B98000739BA /* AccountManager.swift */; };
451A13B11E13DED2000A50FD /* AppNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451A13B01E13DED2000A50FD /* AppNotifications.swift */; };
@ -1052,7 +1051,6 @@
4503F1BB20470A5B00CEE724 /* classic-quiet.aifc */ = {isa = PBXFileReference; lastKnownFileType = file; path = "classic-quiet.aifc"; sourceTree = "<group>"; };
4503F1BC20470A5B00CEE724 /* classic.aifc */ = {isa = PBXFileReference; lastKnownFileType = file; path = classic.aifc; sourceTree = "<group>"; };
4509E7991DD653700025A59F /* WebRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebRTC.framework; path = ThirdParty/WebRTC/Build/WebRTC.framework; sourceTree = "<group>"; };
450DF2041E0D74AC003D14BE /* Platform.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Platform.swift; sourceTree = "<group>"; };
450DF2081E0DD2C6003D14BE /* UserNotificationsAdaptee.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UserNotificationsAdaptee.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
451166BF1FD86B98000739BA /* AccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = "<group>"; };
451A13B01E13DED2000A50FD /* AppNotifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AppNotifications.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
@ -2063,7 +2061,6 @@
45B5360D206DD8BB00D61655 /* UIResponder+OWS.swift */,
4C586924224FAB83003FD070 /* AVAudioSession+OWS.h */,
4C586925224FAB83003FD070 /* AVAudioSession+OWS.m */,
450DF2041E0D74AC003D14BE /* Platform.swift */,
4521C3BF1F59F3BA00B4C582 /* TextFieldHelper.swift */,
34D1F0BF1F8EC1760066283D /* MessageRecipientStatusUtils.swift */,
B8544E3223D50E4900299F14 /* SNAppearance.swift */,
@ -4916,7 +4913,6 @@
34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */,
B8D0A26925E4A2C200C1835E /* Onboarding.swift in Sources */,
34D1F0521F7E8EA30066283D /* GiphyDownloader.swift in Sources */,
450DF2051E0D74AC003D14BE /* Platform.swift in Sources */,
4CC613362227A00400E21A3A /* ConversationSearch.swift in Sources */,
B82149B825D60393009C0F2A /* BlockedModal.swift in Sources */,
B82B408C239A068800A248E7 /* RegisterVC.swift in Sources */,

@ -181,7 +181,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
if messageRequestCount > 0 && !UserDefaults.standard[.hasHiddenMessageRequests] {
if messageRequestCount > 0 && !CurrentAppContext().appUserDefaults()[.hasHiddenMessageRequests] {
return 1
}
@ -258,8 +258,8 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
.compactMap { $0 as? YapDatabaseViewRowChange }
.filter { $0.finalGroup == TSMessageRequestGroup && $0.type == .insert }
if !messageRequestInserts.isEmpty && UserDefaults.standard[.hasHiddenMessageRequests] {
UserDefaults.standard[.hasHiddenMessageRequests] = false
if !messageRequestInserts.isEmpty && CurrentAppContext().appUserDefaults()[.hasHiddenMessageRequests] {
CurrentAppContext().appUserDefaults()[.hasHiddenMessageRequests] = false
}
}
@ -284,8 +284,8 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
tableView.beginUpdates()
// If we need to unhide the message request row and then re-insert it
if !messageRequestInserts.isEmpty && UserDefaults.standard[.hasHiddenMessageRequests] {
UserDefaults.standard[.hasHiddenMessageRequests] = false
if !messageRequestInserts.isEmpty && CurrentAppContext().appUserDefaults()[.hasHiddenMessageRequests] {
CurrentAppContext().appUserDefaults()[.hasHiddenMessageRequests] = false
tableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
}
@ -436,7 +436,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
switch indexPath.section {
case 0:
let hide = UITableViewRowAction(style: .destructive, title: NSLocalizedString("TXT_HIDE_TITLE", comment: "")) { [weak self] _, _ in
UserDefaults.standard[.hasHiddenMessageRequests] = true
CurrentAppContext().appUserDefaults()[.hasHiddenMessageRequests] = true
// Animate the row removal
self?.tableView.beginUpdates()

@ -609,5 +609,6 @@
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?";
"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";
"TXT_HIDE_TITLE" = "Hide";
"TXT_DELETE_ACCEPT" = "Accept";

@ -619,5 +619,6 @@
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?";
"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";
"TXT_HIDE_TITLE" = "Hide";
"TXT_DELETE_ACCEPT" = "Accept";

@ -609,5 +609,6 @@
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?";
"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";
"TXT_HIDE_TITLE" = "Hide";
"TXT_DELETE_ACCEPT" = "Accept";

@ -609,5 +609,6 @@
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?";
"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";
"TXT_HIDE_TITLE" = "Hide";
"TXT_DELETE_ACCEPT" = "Accept";

@ -609,5 +609,6 @@
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?";
"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";
"TXT_HIDE_TITLE" = "Hide";
"TXT_DELETE_ACCEPT" = "Accept";

@ -609,5 +609,6 @@
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?";
"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";
"TXT_HIDE_TITLE" = "Hide";
"TXT_DELETE_ACCEPT" = "Accept";

@ -609,5 +609,6 @@
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?";
"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";
"TXT_HIDE_TITLE" = "Hide";
"TXT_DELETE_ACCEPT" = "Accept";

@ -609,5 +609,6 @@
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?";
"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";
"TXT_HIDE_TITLE" = "Hide";
"TXT_DELETE_ACCEPT" = "Accept";

@ -609,5 +609,6 @@
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?";
"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";
"TXT_HIDE_TITLE" = "Hide";
"TXT_DELETE_ACCEPT" = "Accept";

@ -609,5 +609,6 @@
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?";
"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";
"TXT_HIDE_TITLE" = "Hide";
"TXT_DELETE_ACCEPT" = "Accept";

@ -609,5 +609,6 @@
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?";
"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";
"TXT_HIDE_TITLE" = "Hide";
"TXT_DELETE_ACCEPT" = "Accept";

@ -609,5 +609,6 @@
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?";
"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";
"TXT_HIDE_TITLE" = "Hide";
"TXT_DELETE_ACCEPT" = "Accept";

@ -609,5 +609,6 @@
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?";
"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";
"TXT_HIDE_TITLE" = "Hide";
"TXT_DELETE_ACCEPT" = "Accept";

@ -609,5 +609,6 @@
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?";
"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";
"TXT_HIDE_TITLE" = "Hide";
"TXT_DELETE_ACCEPT" = "Accept";

@ -609,5 +609,6 @@
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?";
"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";
"TXT_HIDE_TITLE" = "Hide";
"TXT_DELETE_ACCEPT" = "Accept";

@ -610,5 +610,6 @@
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?";
"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";
"TXT_HIDE_TITLE" = "Hide";
"TXT_DELETE_ACCEPT" = "Accept";

@ -609,5 +609,6 @@
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?";
"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";
"TXT_HIDE_TITLE" = "Hide";
"TXT_DELETE_ACCEPT" = "Accept";

@ -609,5 +609,6 @@
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?";
"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";
"TXT_HIDE_TITLE" = "Hide";
"TXT_DELETE_ACCEPT" = "Accept";

@ -609,5 +609,6 @@
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?";
"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";
"TXT_HIDE_TITLE" = "Hide";
"TXT_DELETE_ACCEPT" = "Accept";

@ -609,5 +609,6 @@
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?";
"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";
"TXT_HIDE_TITLE" = "Hide";
"TXT_DELETE_ACCEPT" = "Accept";

@ -609,5 +609,6 @@
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?";
"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";
"TXT_HIDE_TITLE" = "Hide";
"TXT_DELETE_ACCEPT" = "Accept";

@ -609,5 +609,6 @@
"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?";
"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request.";
"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted.";
"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request";
"TXT_HIDE_TITLE" = "Hide";
"TXT_DELETE_ACCEPT" = "Accept";

@ -4,6 +4,8 @@
import Foundation
import PromiseKit
import SessionMessagingKit
import SignalUtilitiesKit
/// There are two primary components in our system notification integration:
///
@ -88,7 +90,7 @@ let kNotificationDelayForBackgroumdPoll: TimeInterval = 5
let kAudioNotificationsThrottleCount = 2
let kAudioNotificationsThrottleInterval: TimeInterval = 5
protocol NotificationPresenterAdaptee: class {
protocol NotificationPresenterAdaptee: AnyObject {
func registerNotificationSettings() -> Promise<Void>
@ -157,11 +159,32 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
}
public func notifyUser(for incomingMessage: TSIncomingMessage, in thread: TSThread, transaction: YapDatabaseReadTransaction) {
guard !thread.isMuted else { return }
guard thread.isGroupThread() || !thread.isMessageRequest() else { return }
guard let threadId = thread.uniqueId else { return }
// If the thread is a message request and the user hasn't hidden message requests then we need
// to check if this is the only message request thread (group threads can't be message requests
// so just ignore those and if the user has hidden message requests then we want to show the
// notification regardless of how many message requests there are)
if !thread.isGroupThread() && thread.isMessageRequest() && !CurrentAppContext().appUserDefaults()[.hasHiddenMessageRequests] {
let dbConnection: YapDatabaseConnection = OWSPrimaryStorage.shared().newDatabaseConnection()
dbConnection.objectCacheLimit = 2
dbConnection.beginLongLivedReadTransaction() // Freeze the connection for use on the main thread (this gives us a stable data source that doesn't change until we tell it to)
let threads: YapDatabaseViewMappings = YapDatabaseViewMappings(groups: [ TSMessageRequestGroup ], view: TSThreadDatabaseViewExtensionName)
dbConnection.read { transaction in
threads.update(with: transaction) // Perform the initial update
}
let numMessageRequests = threads.numberOfItems(inGroup: TSMessageRequestGroup)
dbConnection.endLongLivedReadTransaction()
// Allow this to show a notification if there are no message requests (ie. this is the first one)
guard numMessageRequests == 0 else { return }
}
else if thread.isMessageRequest() && CurrentAppContext().appUserDefaults()[.hasHiddenMessageRequests] {
CurrentAppContext().appUserDefaults()[.hasHiddenMessageRequests] = false
}
let identifier: String = incomingMessage.notificationIdentifier ?? UUID().uuidString
let isBackgroudPoll = identifier == threadId
@ -186,36 +209,44 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
let senderName = Storage.shared.getContact(with: incomingMessage.authorId, using: transaction)?.displayName(for: context) ?? incomingMessage.authorId
let notificationTitle: String?
var notificationBody: String?
let previewType = preferences.notificationPreviewType(with: transaction)
switch previewType {
case .noNameNoPreview:
notificationTitle = "Session"
case .nameNoPreview, .namePreview:
switch thread {
case is TSContactThread:
notificationTitle = senderName
case is TSGroupThread:
var groupName = thread.name()
if groupName.count < 1 {
groupName = MessageStrings.newGroupDefaultTitle
case .noNameNoPreview:
notificationTitle = "Session"
case .nameNoPreview, .namePreview:
switch thread {
case is TSContactThread:
notificationTitle = (thread.isMessageRequest() ? "Session" : senderName)
case is TSGroupThread:
var groupName = thread.name()
if groupName.count < 1 {
groupName = MessageStrings.newGroupDefaultTitle
}
notificationTitle = isBackgroudPoll ? groupName : String(format: NotificationStrings.incomingGroupMessageTitleFormat, senderName, groupName)
default:
owsFailDebug("unexpected thread: \(thread)")
return
}
notificationTitle = isBackgroudPoll ? groupName : String(format: NotificationStrings.incomingGroupMessageTitleFormat, senderName, groupName)
default:
owsFailDebug("unexpected thread: \(thread)")
return
}
default:
notificationTitle = "Session"
notificationTitle = "Session"
}
var notificationBody: String?
switch previewType {
case .noNameNoPreview, .nameNoPreview:
notificationBody = NotificationStrings.incomingMessageBody
case .namePreview:
notificationBody = messageText
default:
notificationBody = NotificationStrings.incomingMessageBody
case .noNameNoPreview, .nameNoPreview: notificationBody = NotificationStrings.incomingMessageBody
case .namePreview: notificationBody = messageText
default: notificationBody = NotificationStrings.incomingMessageBody
}
// If it's a message request then overwrite the body to be something generic (only show a notification
// when receiving a new message request if there aren't any others or the user had hidden them)
if thread.isMessageRequest() {
notificationBody = NSLocalizedString("MESSAGE_REQUESTS_NOTIFICATION", comment: "")
}
assert((notificationBody ?? notificationTitle) != nil)
@ -231,12 +262,15 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
DispatchQueue.main.async {
notificationBody = MentionUtilities.highlightMentions(in: notificationBody!, threadID: thread.uniqueId!)
let sound = self.requestSound(thread: thread)
self.adaptee.notify(category: category,
title: notificationTitle,
body: notificationBody ?? "",
userInfo: userInfo,
sound: sound,
replacingIdentifier: identifier)
self.adaptee.notify(
category: category,
title: notificationTitle,
body: notificationBody ?? "",
userInfo: userInfo,
sound: sound,
replacingIdentifier: identifier
)
}
}

@ -53,9 +53,9 @@ public enum PushRegistrationError: Error {
return firstly {
self.registerUserNotificationSettings()
}.then { () -> Promise<(pushToken: String, voipToken: String)> in
guard !Platform.isSimulator else {
throw PushRegistrationError.pushNotSupported(description: "Push not supported on simulators")
}
#if targetEnvironment(simulator)
throw PushRegistrationError.pushNotSupported(description: "Push not supported on simulators")
#endif
return self.registerForVanillaPushToken().map { vanillaPushToken -> (pushToken: String, voipToken: String) in
return (pushToken: vanillaPushToken, voipToken: "")

@ -1,14 +0,0 @@
// Created by Michael Kirk on 12/23/16.
// Copyright © 2016 Open Whisper Systems. All rights reserved.
import Foundation
struct Platform {
static let isSimulator: Bool = {
var isSim = false
#if arch(i386) || arch(x86_64)
isSim = true
#endif
return isSim
}()
}

@ -65,6 +65,8 @@ extension ConfigurationMessage {
return
}
// Can just default the 'hasX' values to true as they will be set to this
// when converting to proto anyway
let profilePictureURL = contact.profilePictureURL
let profileKey = contact.profileEncryptionKey?.keyData
let contact = ConfigurationMessage.Contact(
@ -72,8 +74,11 @@ extension ConfigurationMessage {
displayName: (contact.name ?? publicKey),
profilePictureURL: profilePictureURL,
profileKey: profileKey,
hasIsApproved: true,
isApproved: contact.isApproved,
hasIsBlocked: true,
isBlocked: contact.isBlocked,
hasDidApproveMe: true,
didApproveMe: contact.didApproveMe
)

@ -194,19 +194,36 @@ extension ConfigurationMessage {
public var profilePictureURL: String?
public var profileKey: Data?
public var hasIsApproved: Bool
public var isApproved: Bool
public var hasIsBlocked: Bool
public var isBlocked: Bool
public var hasDidApproveMe: Bool
public var didApproveMe: Bool
public var isValid: Bool { publicKey != nil && displayName != nil }
public init(publicKey: String, displayName: String, profilePictureURL: String?, profileKey: Data?, isApproved: Bool, isBlocked: Bool, didApproveMe: Bool) {
public init(
publicKey: String,
displayName: String,
profilePictureURL: String?,
profileKey: Data?,
hasIsApproved: Bool,
isApproved: Bool,
hasIsBlocked: Bool,
isBlocked: Bool,
hasDidApproveMe: Bool,
didApproveMe: Bool
) {
self.publicKey = publicKey
self.displayName = displayName
self.profilePictureURL = profilePictureURL
self.profileKey = profileKey
self.hasIsApproved = hasIsApproved
self.isApproved = isApproved
self.hasIsBlocked = hasIsBlocked
self.isBlocked = isBlocked
self.hasDidApproveMe = hasDidApproveMe
self.didApproveMe = didApproveMe
}
@ -217,8 +234,11 @@ extension ConfigurationMessage {
self.displayName = displayName
self.profilePictureURL = coder.decodeObject(forKey: "profilePictureURL") as! String?
self.profileKey = coder.decodeObject(forKey: "profileKey") as! Data?
self.hasIsApproved = (coder.decodeObject(forKey: "hasIsApproved") as? Bool ?? false)
self.isApproved = (coder.decodeObject(forKey: "isApproved") as? Bool ?? false)
self.hasIsBlocked = (coder.decodeObject(forKey: "hasIsBlocked") as? Bool ?? false)
self.isBlocked = (coder.decodeObject(forKey: "isBlocked") as? Bool ?? false)
self.hasDidApproveMe = (coder.decodeObject(forKey: "hasDidApproveMe") as? Bool ?? false)
self.didApproveMe = (coder.decodeObject(forKey: "didApproveMe") as? Bool ?? false)
}
@ -227,8 +247,11 @@ extension ConfigurationMessage {
coder.encode(displayName, forKey: "displayName")
coder.encode(profilePictureURL, forKey: "profilePictureURL")
coder.encode(profileKey, forKey: "profileKey")
coder.encode(hasIsApproved, forKey: "hasIsApproved")
coder.encode(isApproved, forKey: "isApproved")
coder.encode(hasIsBlocked, forKey: "hasIsBlocked")
coder.encode(isBlocked, forKey: "isBlocked")
coder.encode(hasDidApproveMe, forKey: "hasDidApproveMe")
coder.encode(didApproveMe, forKey: "didApproveMe")
}
@ -238,8 +261,11 @@ extension ConfigurationMessage {
displayName: proto.name,
profilePictureURL: proto.profilePicture,
profileKey: proto.profileKey,
hasIsApproved: proto.hasIsApproved,
isApproved: proto.isApproved,
hasIsBlocked: proto.hasIsBlocked,
isBlocked: proto.isBlocked,
hasDidApproveMe: proto.hasDidApproveMe,
didApproveMe: proto.didApproveMe
)
@ -254,9 +280,9 @@ extension ConfigurationMessage {
if let profilePictureURL = profilePictureURL { result.setProfilePicture(profilePictureURL) }
if let profileKey = profileKey { result.setProfileKey(profileKey) }
result.setIsApproved(isApproved)
result.setIsBlocked(isBlocked)
result.setDidApproveMe(didApproveMe)
if hasIsApproved { result.setIsApproved(isApproved) }
if hasIsBlocked { result.setIsBlocked(isBlocked) }
if hasDidApproveMe { result.setDidApproveMe(didApproveMe) }
do {
return try result.build()

@ -214,9 +214,14 @@ extension MessageReceiver {
if let profileKey = contactInfo.profileKey { contact.profileEncryptionKey = OWSAES256Key(data: profileKey) }
contact.profilePictureURL = contactInfo.profilePictureURL
contact.name = contactInfo.displayName
contact.isApproved = contactInfo.isApproved
contact.isBlocked = contactInfo.isBlocked
contact.didApproveMe = contactInfo.didApproveMe
// Note: We only update these values if the proto actually has values for them (this is to
// prevent an edge case where an old client could override the values with default values
// since they aren't included)
if contactInfo.hasIsApproved { contact.isApproved = contactInfo.isApproved }
if contactInfo.hasIsBlocked { contact.isBlocked = contactInfo.isBlocked }
if contactInfo.hasDidApproveMe { contact.didApproveMe = contactInfo.didApproveMe }
Storage.shared.setContact(contact, using: transaction)
let thread = TSContactThread.getOrCreateThread(withContactSessionID: sessionID, transaction: transaction)
thread.shouldBeVisible = true
@ -800,12 +805,18 @@ extension MessageReceiver {
// Force a config sync to ensure all devices know the contact approval state if desired (Note: This logic
// should match the behaviour in AppDelegate.forceSyncConfigurationNowIfNeeded())
guard forceConfigSync else { return }
guard Storage.shared.getUser()?.name != nil, let configurationMessage = ConfigurationMessage.getCurrent() else {
return
}
let destination: Message.Destination = Message.Destination.contact(publicKey: userPublicKey)
MessageSender.send(configurationMessage, to: destination, using: transaction).retainUntilComplete()
// Note: We MUST run this async as we need to ensure the database `transaction` has finished before we generate
// a new configuration message (otherwise the `contact` will be loaded direct from the database and the
// `didApproveMe` value won't have been updated)
DispatchQueue.global(qos: .background).async {
guard Storage.shared.getUser()?.name != nil, let configurationMessage = ConfigurationMessage.getCurrent() else {
return
}
let destination: Message.Destination = Message.Destination.contact(publicKey: userPublicKey)
MessageSender.send(configurationMessage, to: destination, using: transaction).retainUntilComplete()
}
}
public static func handleMessageRequestResponse(_ message: MessageRequestResponse, using transaction: Any) {

@ -39,57 +39,91 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension
let senderPublicKey = message.sender!
var senderDisplayName = Storage.shared.getContact(with: senderPublicKey)?.displayName(for: .regular) ?? senderPublicKey
let snippet: String
var userInfo: [String:Any] = [ NotificationServiceExtension.isFromRemoteKey : true ]
var userInfo: [String: Any] = [ NotificationServiceExtension.isFromRemoteKey: true ]
var isMessageRequest: Bool = false
switch message {
case let visibleMessage as VisibleMessage:
let tsIncomingMessageID = try MessageReceiver.handleVisibleMessage(visibleMessage, associatedWithProto: proto, openGroupID: nil, isBackgroundPoll: false, using: transaction)
guard let tsMessage = TSMessage.fetch(uniqueId: tsIncomingMessageID, transaction: transaction) else {
return self.completeSilenty()
}
let thread = tsMessage.thread(with: transaction)
let threadID = thread.uniqueId!
userInfo[NotificationServiceExtension.threadIdKey] = threadID
snippet = tsMessage.previewText(with: transaction).filterForDisplay?.replacingMentions(for: threadID, using: transaction)
?? "You've got a new message"
if let tsIncomingMessage = tsMessage as? TSIncomingMessage {
if thread.isMuted || !thread.isMessageRequest() {
// Ignore PNs if the thread is muted or the thread is a message request
case let visibleMessage as VisibleMessage:
let tsIncomingMessageID = try MessageReceiver.handleVisibleMessage(visibleMessage, associatedWithProto: proto, openGroupID: nil, isBackgroundPoll: false, using: transaction)
guard let tsMessage = TSMessage.fetch(uniqueId: tsIncomingMessageID, transaction: transaction) else {
return self.completeSilenty()
}
if let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction), let group = thread as? TSGroupThread,
group.groupModel.groupType == .closedGroup { // Should always be true because we don't get PNs for open groups
senderDisplayName = String(format: NotificationStrings.incomingGroupMessageTitleFormat, senderDisplayName, group.groupModel.groupName ?? MessageStrings.newGroupDefaultTitle)
if group.isOnlyNotifyingForMentions && !tsIncomingMessage.isUserMentioned {
// Ignore PNs if the group is set to only notify for mentions
return self.completeSilenty()
let thread = tsMessage.thread(with: transaction)
let threadID = thread.uniqueId!
userInfo[NotificationServiceExtension.threadIdKey] = threadID
snippet = tsMessage.previewText(with: transaction).filterForDisplay?.replacingMentions(for: threadID, using: transaction)
?? "You've got a new message"
if let tsIncomingMessage = tsMessage as? TSIncomingMessage {
// Ignore PNs if the thread is muted
if thread.isMuted { return self.completeSilenty() }
if let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction), let group = thread as? TSGroupThread,
group.groupModel.groupType == .closedGroup { // Should always be true because we don't get PNs for open groups
senderDisplayName = String(format: NotificationStrings.incomingGroupMessageTitleFormat, senderDisplayName, group.groupModel.groupName ?? MessageStrings.newGroupDefaultTitle)
if group.isOnlyNotifyingForMentions && !tsIncomingMessage.isUserMentioned {
// Ignore PNs if the group is set to only notify for mentions
return self.completeSilenty()
}
}
// If the thread is a message request and the user hasn't hidden message requests then we need
// to check if this is the only message request thread (group threads can't be message requests
// so just ignore those and if the user has hidden message requests then we want to show the
// notification regardless of how many message requests there are)
if !thread.isGroupThread() && thread.isMessageRequest() && !CurrentAppContext().appUserDefaults()[.hasHiddenMessageRequests] {
let dbConnection: YapDatabaseConnection = OWSPrimaryStorage.shared().newDatabaseConnection()
dbConnection.objectCacheLimit = 2
dbConnection.beginLongLivedReadTransaction() // Freeze the connection for use on the main thread (this gives us a stable data source that doesn't change until we tell it to)
let threads: YapDatabaseViewMappings = YapDatabaseViewMappings(groups: [ TSMessageRequestGroup ], view: TSThreadDatabaseViewExtensionName)
dbConnection.read { transaction in
threads.update(with: transaction) // Perform the initial update
}
let numMessageRequests = threads.numberOfItems(inGroup: TSMessageRequestGroup)
dbConnection.endLongLivedReadTransaction()
// Allow this to show a notification if there are no message requests (ie. this is the first one)
guard numMessageRequests == 0 else { return self.completeSilenty() }
}
else if thread.isMessageRequest() && CurrentAppContext().appUserDefaults()[.hasHiddenMessageRequests] {
CurrentAppContext().appUserDefaults()[.hasHiddenMessageRequests] = false
}
isMessageRequest = thread.isMessageRequest()
// Store the notification ID for unsend requests to later cancel this notification
tsIncomingMessage.setNotificationIdentifier(request.identifier, transaction: transaction)
}
else {
let semaphore = DispatchSemaphore(value: 0)
let center = UNUserNotificationCenter.current()
center.getDeliveredNotifications { notifications in
let matchingNotifications = notifications.filter({ $0.request.content.userInfo[NotificationServiceExtension.threadIdKey] as? String == threadID})
center.removeDeliveredNotifications(withIdentifiers: matchingNotifications.map({ $0.request.identifier }))
// Hack: removeDeliveredNotifications seems to be async,need to wait for some time before the delivered notifications can be removed.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { semaphore.signal() }
}
semaphore.wait()
}
// Store the notification ID for unsend requests to later cancel this notification
tsIncomingMessage.setNotificationIdentifier(request.identifier, transaction: transaction)
} else {
let semaphore = DispatchSemaphore(value: 0)
let center = UNUserNotificationCenter.current()
center.getDeliveredNotifications { notifications in
let matchingNotifications = notifications.filter({ $0.request.content.userInfo[NotificationServiceExtension.threadIdKey] as? String == threadID})
center.removeDeliveredNotifications(withIdentifiers: matchingNotifications.map({ $0.request.identifier }))
// Hack: removeDeliveredNotifications seems to be async,need to wait for some time before the delivered notifications can be removed.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { semaphore.signal() }
notificationContent.sound = OWSSounds.notificationSound(for: thread).notificationSound(isQuiet: false)
case let unsendRequest as UnsendRequest:
MessageReceiver.handleUnsendRequest(unsendRequest, using: transaction)
return self.completeSilenty()
case let closedGroupControlMessage as ClosedGroupControlMessage:
// TODO: We could consider actually handling the update here. Not sure if there's enough time though, seeing as though
// in some cases we need to send messages (e.g. our sender key) to a number of other users.
switch closedGroupControlMessage.kind {
case .new(_, let name, _, _, _, _): snippet = "\(senderDisplayName) added you to \(name)"
default: return self.completeSilenty()
}
semaphore.wait()
}
notificationContent.sound = OWSSounds.notificationSound(for: thread).notificationSound(isQuiet: false)
case let unsendRequest as UnsendRequest:
MessageReceiver.handleUnsendRequest(unsendRequest, using: transaction)
return self.completeSilenty()
case let closedGroupControlMessage as ClosedGroupControlMessage:
// TODO: We could consider actually handling the update here. Not sure if there's enough time though, seeing as though
// in some cases we need to send messages (e.g. our sender key) to a number of other users.
switch closedGroupControlMessage.kind {
case .new(_, let name, _, _, _, _): snippet = "\(senderDisplayName) added you to \(name)"
default: return self.completeSilenty()
}
default: return self.completeSilenty()
}
if (senderPublicKey == userPublicKey) {
// Ignore PNs for messages sent by the current user
// after handling the message. Otherwise the closed
@ -98,21 +132,35 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension
}
notificationContent.userInfo = userInfo
notificationContent.badge = 1
let notificationsPreference = Environment.shared.preferences!.notificationPreviewType()
switch notificationsPreference {
case .namePreview:
notificationContent.title = senderDisplayName
notificationContent.body = snippet
case .nameNoPreview:
notificationContent.title = senderDisplayName
notificationContent.body = NotificationStrings.incomingMessageBody
case .noNameNoPreview:
case .namePreview:
notificationContent.title = senderDisplayName
notificationContent.body = snippet
case .nameNoPreview:
notificationContent.title = senderDisplayName
notificationContent.body = NotificationStrings.incomingMessageBody
case .noNameNoPreview:
notificationContent.title = "Session"
notificationContent.body = NotificationStrings.incomingMessageBody
default: break
}
// If it's a message request then overwrite the body to be something generic (only show a notification
// when receiving a new message request if there aren't any others or the user had hidden them)
if isMessageRequest {
notificationContent.title = "Session"
notificationContent.body = NotificationStrings.incomingMessageBody
default: break
notificationContent.body = NSLocalizedString("MESSAGE_REQUESTS_NOTIFICATION", comment: "")
}
self.handleSuccess(for: notificationContent)
} catch {
}
catch {
if let error = error as? MessageReceiver.Error, error.isRetryable {
self.handleFailure(for: notificationContent)
}

@ -76,7 +76,8 @@ NS_ASSUME_NONNULL_BEGIN
for (NSString *groupID in allGroups) {
TSThread *thread = [TSThread fetchObjectWithUniqueID:groupID transaction:transaction];
if (thread.isMuted || !thread.isMessageRequest) { continue; }
// Don't increase the count for muted threads or message requests
if (thread.isMuted || thread.isMessageRequest) { continue; }
BOOL isGroupThread = thread.isGroupThread;

Loading…
Cancel
Save