@ -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 ( ) {
// I g n o r e P N s i f t h e t h r e a d i s m u t e d o r t h e t h r e a d i s a m e s s a g e r e q u e s t
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 { // S h o u l d a l w a y s b e t r u e b e c a u s e w e d o n ' t g e t P N s f o r o p e n g r o u p s
senderDisplayName = String ( format : NotificationStrings . incomingGroupMessageTitleFormat , senderDisplayName , group . groupModel . groupName ? ? MessageStrings . newGroupDefaultTitle )
if group . isOnlyNotifyingForMentions && ! tsIncomingMessage . isUserMentioned {
// I g n o r e P N s i f t h e g r o u p i s s e t t o o n l y n o t i f y f o r m e n t i o n s
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 {
// I g n o r e P N s i f t h e t h r e a d i s m u t e d
if thread . isMuted { return self . completeSilenty ( ) }
if let thread = TSThread . fetch ( uniqueId : threadID , transaction : transaction ) , let group = thread as ? TSGroupThread ,
group . groupModel . groupType = = . closedGroup { // S h o u l d a l w a y s b e t r u e b e c a u s e w e d o n ' t g e t P N s f o r o p e n g r o u p s
senderDisplayName = String ( format : NotificationStrings . incomingGroupMessageTitleFormat , senderDisplayName , group . groupModel . groupName ? ? MessageStrings . newGroupDefaultTitle )
if group . isOnlyNotifyingForMentions && ! tsIncomingMessage . isUserMentioned {
// I g n o r e P N s i f t h e g r o u p i s s e t t o o n l y n o t i f y f o r m e n t i o n s
return self . completeSilenty ( )
}
}
// I f t h e t h r e a d i s a m e s s a g e r e q u e s t a n d t h e u s e r h a s n ' t h i d d e n m e s s a g e r e q u e s t s t h e n w e n e e d
// t o c h e c k i f t h i s i s t h e o n l y m e s s a g e r e q u e s t t h r e a d ( g r o u p t h r e a d s c a n ' t b e m e s s a g e r e q u e s t s
// s o j u s t i g n o r e t h o s e a n d i f t h e u s e r h a s h i d d e n m e s s a g e r e q u e s t s t h e n w e w a n t t o s h o w t h e
// n o t i f i c a t i o n r e g a r d l e s s o f h o w m a n y m e s s a g e r e q u e s t s t h e r e a r e )
if ! thread . isGroupThread ( ) && thread . isMessageRequest ( ) && ! CurrentAppContext ( ) . appUserDefaults ( ) [ . hasHiddenMessageRequests ] {
let dbConnection : YapDatabaseConnection = OWSPrimaryStorage . shared ( ) . newDatabaseConnection ( )
dbConnection . objectCacheLimit = 2
dbConnection . beginLongLivedReadTransaction ( ) // F r e e z e t h e c o n n e c t i o n f o r u s e o n t h e m a i n t h r e a d ( t h i s g i v e s u s a s t a b l e d a t a s o u r c e t h a t d o e s n ' t c h a n g e u n t i l w e t e l l i t t o )
let threads : YapDatabaseViewMappings = YapDatabaseViewMappings ( groups : [ TSMessageRequestGroup ] , view : TSThreadDatabaseViewExtensionName )
dbConnection . read { transaction in
threads . update ( with : transaction ) // P e r f o r m t h e i n i t i a l u p d a t e
}
let numMessageRequests = threads . numberOfItems ( inGroup : TSMessageRequestGroup )
dbConnection . endLongLivedReadTransaction ( )
// A l l o w t h i s t o s h o w a n o t i f i c a t i o n i f t h e r e a r e n o m e s s a g e r e q u e s t s ( i e . t h i s i s t h e f i r s t o n e )
guard numMessageRequests = = 0 else { return self . completeSilenty ( ) }
}
else if thread . isMessageRequest ( ) && CurrentAppContext ( ) . appUserDefaults ( ) [ . hasHiddenMessageRequests ] {
CurrentAppContext ( ) . appUserDefaults ( ) [ . hasHiddenMessageRequests ] = false
}
isMessageRequest = thread . isMessageRequest ( )
// S t o r e t h e n o t i f i c a t i o n I D f o r u n s e n d r e q u e s t s t o l a t e r c a n c e l t h i s n o t i f i c a t i o n
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 } ) )
// H a c k : r e m o v e D e l i v e r e d N o t i f i c a t i o n s s e e m s t o b e a s y n c , n e e d t o w a i t f o r s o m e t i m e b e f o r e t h e d e l i v e r e d n o t i f i c a t i o n s c a n b e r e m o v e d .
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 0.1 ) { semaphore . signal ( ) }
}
semaphore . wait ( )
}
// S t o r e t h e n o t i f i c a t i o n I D f o r u n s e n d r e q u e s t s t o l a t e r c a n c e l t h i s n o t i f i c a t i o n
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 } ) )
// H a c k : r e m o v e D e l i v e r e d N o t i f i c a t i o n s s e e m s t o b e a s y n c , n e e d t o w a i t f o r s o m e t i m e b e f o r e t h e d e l i v e r e d n o t i f i c a t i o n s c a n b e r e m o v e d .
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: W e c o u l d c o n s i d e r a c t u a l l y h a n d l i n g t h e u p d a t e h e r e . N o t s u r e i f t h e r e ' s e n o u g h t i m e t h o u g h , s e e i n g a s t h o u g h
// i n s o m e c a s e s w e n e e d t o s e n d m e s s a g e s ( e . g . o u r s e n d e r k e y ) t o a n u m b e r o f o t h e r u s e r s .
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: W e c o u l d c o n s i d e r a c t u a l l y h a n d l i n g t h e u p d a t e h e r e . N o t s u r e i f t h e r e ' s e n o u g h t i m e t h o u g h , s e e i n g a s t h o u g h
// i n s o m e c a s e s w e n e e d t o s e n d m e s s a g e s ( e . g . o u r s e n d e r k e y ) t o a n u m b e r o f o t h e r u s e r s .
switch closedGroupControlMessage . kind {
case . new ( _ , let name , _ , _ , _ , _ ) : snippet = " \( senderDisplayName ) added you to \( name ) "
default : return self . completeSilenty ( )
}
default : return self . completeSilenty ( )
}
if ( senderPublicKey = = userPublicKey ) {
// I g n o r e P N s f o r m e s s a g e s s e n t b y t h e c u r r e n t u s e r
// a f t e r h a n d l i n g t h e m e s s a g e . O t h e r w i s e t h e c l o s e d
@ -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
}
// I f i t ' s a m e s s a g e r e q u e s t t h e n o v e r w r i t e t h e b o d y t o b e s o m e t h i n g g e n e r i c ( o n l y s h o w a n o t i f i c a t i o n
// w h e n r e c e i v i n g a n e w m e s s a g e r e q u e s t i f t h e r e a r e n ' t a n y o t h e r s o r t h e u s e r h a d h i d d e n t h e m )
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 )
}