@ -12,6 +12,7 @@ import SignalCoreKit
import SessionUtilitiesKit
import SessionUtilitiesKit
public final class NotificationServiceExtension : UNNotificationServiceExtension {
public final class NotificationServiceExtension : UNNotificationServiceExtension {
private let dependencies : Dependencies = Dependencies ( )
private var didPerformSetup = false
private var didPerformSetup = false
private var contentHandler : ( ( UNNotificationContent ) -> Void ) ?
private var contentHandler : ( ( UNNotificationContent ) -> Void ) ?
private var request : UNNotificationRequest ?
private var request : UNNotificationRequest ?
@ -29,6 +30,9 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
self . contentHandler = contentHandler
self . contentHandler = contentHandler
self . request = request
self . request = request
// I t ' s t e c h n i c a l l y p o s s i b l e f o r ' c o m p l e t e S i l e n t l y ' t o b e c a l l e d t w i c e d u e t o t h e N S E t i m e o u t s o
self . hasCompleted . mutate { $0 = false }
// A b o r t i f t h e m a i n a p p i s r u n n i n g
// A b o r t i f t h e m a i n a p p i s r u n n i n g
guard ! ( UserDefaults . sharedLokiProject ? [ . isMainAppActive ] ) . defaulting ( to : false ) else {
guard ! ( UserDefaults . sharedLokiProject ? [ . isMainAppActive ] ) . defaulting ( to : false ) else {
Log . info ( " didReceive called while main app running. " )
Log . info ( " didReceive called while main app running. " )
@ -47,19 +51,31 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
Singleton . setup ( appContext : NotificationServiceExtensionContext ( ) )
Singleton . setup ( appContext : NotificationServiceExtensionContext ( ) )
}
}
let isCallOngoing : Bool = ( UserDefaults . sharedLokiProject ? [ . isCallOngoing ] )
. defaulting ( to : false )
// P e r f o r m m a i n s e t u p
// P e r f o r m m a i n s e t u p
Storage . resumeDatabaseAccess ( )
Storage . resumeDatabaseAccess ( )
DispatchQueue . main . sync { self . setUpIfNecessary ( ) { } }
DispatchQueue . main . sync {
self . setUpIfNecessary ( ) { [ weak self ] in
self ? . handleNotification ( notificationContent , isPerformingResetup : false )
}
}
}
// H a n d l e t h e p u s h n o t i f i c a t i o n
private func handleNotification ( _ notificationContent : UNMutableNotificationContent , isPerformingResetup : Bool ) {
Singleton . appReadiness . runNowOrWhenAppDidBecomeReady {
let ( maybeData , metadata , result ) = PushNotificationAPI . processNotification (
let ( maybeData , metadata , result ) = PushNotificationAPI . processNotification (
notificationContent : notificationContent
notificationContent : notificationContent ,
using : dependencies
)
)
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 ( )
}
Log . warn ( " Received notification for an accountId that isn't the current user, attempting to resetup. " )
return self . forceResetup ( notificationContent )
}
guard
guard
( result = = . success || result = = . legacySuccess ) ,
( result = = . success || result = = . legacySuccess ) ,
let data : Data = maybeData
let data : Data = maybeData
@ -73,19 +89,22 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
// J u s t l o g i f t h e n o t i f i c a t i o n w a s t o o l o n g ( a ~ 2 k m e s s a g e s h o u l d b e a b l e t o f i t s o
// J u s t l o g i f t h e n o t i f i c a t i o n w a s t o o l o n g ( a ~ 2 k m e s s a g e s h o u l d b e a b l e t o f i t s o
// t h e s e w i l l m o s t c o m m o n l y b e c a l l o r c o n f i g m e s s a g e s )
// t h e s e w i l l m o s t c o m m o n l y b e c a l l o r c o n f i g m e s s a g e s )
case . successTooLong :
case . successTooLong :
Log . info ( " Received too long notification for namespace: \( metadata . namespace ) . " )
Log . info ( " Received too long notification for namespace: \( metadata . namespace ) , dataLength: \( metadata . dataLength ) . " )
return self . completeSilenty ( )
return self . completeSilenty ( )
case . legacyForceSilent , . failureNoContent : return self . completeSilenty ( )
case . legacyForceSilent , . failureNoContent : return self . completeSilenty ( )
}
}
}
}
let isCallOngoing : Bool = ( UserDefaults . sharedLokiProject ? [ . isCallOngoing ] )
. defaulting ( to : false )
// H A C K : I t i s i m p o r t a n t t o u s e w r i t e s y n c h r o n o u s l y h e r e t o a v o i d a r a c e c o n d i t i o n
// H A C K : I t i s i m p o r t a n t t o u s e w r i t e s y n c h r o n o u s l y h e r e t o a v o i d a r a c e c o n d i t i o n
// w h e r e t h e c o m p l e t e S i l e n t y ( ) i s c a l l e d b e f o r e t h e l o c a l n o t i f i c a t i o n r e q u e s t
// w h e r e t h e c o m p l e t e S i l e n t y ( ) i s c a l l e d b e f o r e t h e l o c a l n o t i f i c a t i o n r e q u e s t
// i s a d d e d t o n o t i f i c a t i o n c e n t e r
// i s a d d e d t o n o t i f i c a t i o n c e n t e r
Storage . shared . write { [ weak self ] db in
dependencies . storage . write { [ weak self , dependencies ] db in
do {
do {
guard let processedMessage : ProcessedMessage = try Message . processRawReceivedMessageAsNotification ( db , data : data , metadata : metadata ) else {
guard let processedMessage : ProcessedMessage = try Message . processRawReceivedMessageAsNotification ( db , data : data , metadata : metadata , using : dependencies ) else {
throw NotificationError . messageProcessing
throw NotificationError . messageProcessing
}
}
@ -117,7 +136,8 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
db ,
db ,
message : messageInfo . message ,
message : messageInfo . message ,
threadId : threadId ,
threadId : threadId ,
threadVariant : threadVariant
threadVariant : threadVariant ,
using : dependencies
)
)
try MessageReceiver . handleCallMessage (
try MessageReceiver . handleCallMessage (
@ -187,7 +207,8 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
threadVariant : threadVariant ,
threadVariant : threadVariant ,
message : messageInfo . message ,
message : messageInfo . message ,
serverExpirationTimestamp : messageInfo . serverExpirationTimestamp ,
serverExpirationTimestamp : messageInfo . serverExpirationTimestamp ,
associatedWithProto : proto
associatedWithProto : proto ,
using : dependencies
)
)
}
}
@ -215,7 +236,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
}
}
}
}
db . afterNextTransaction Nested (
db . afterNextTransaction (
onCommit : { _ in handleError ( ) } ,
onCommit : { _ in handleError ( ) } ,
onRollback : { _ in handleError ( ) }
onRollback : { _ in handleError ( ) }
)
)
@ -223,7 +244,6 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
}
}
}
}
}
}
}
// MARK: S e t u p
// MARK: S e t u p
@ -233,7 +253,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
// T h e N S E w i l l o f t e n r e - u s e t h e s a m e p r o c e s s , s o i f w e ' r e
// T h e N S E w i l l o f t e n r e - u s e t h e s a m e p r o c e s s , s o i f w e ' r e
// a l r e a d y s e t u p w e w a n t t o d o n o t h i n g ; w e ' r e a l r e a d y r e a d y
// a l r e a d y s e t u p w e w a n t t o d o n o t h i n g ; w e ' r e a l r e a d y r e a d y
// t o p r o c e s s n e w m e s s a g e s .
// t o p r o c e s s n e w m e s s a g e s .
guard ! didPerformSetup else { return }
guard ! didPerformSetup else { return completion ( ) }
Log . info ( " Performing setup. " )
Log . info ( " Performing setup. " )
didPerformSetup = true
didPerformSetup = true
@ -257,7 +277,6 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
// S e t u p L i b S e s s i o n
// S e t u p L i b S e s s i o n
LibSession . addLogger ( )
LibSession . addLogger ( )
LibSession . createNetworkIfNeeded ( )
} ,
} ,
migrationsCompletion : { [ weak self ] result , needsConfigSync in
migrationsCompletion : { [ weak self ] result , needsConfigSync in
switch result {
switch result {
@ -278,16 +297,14 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
}
}
DispatchQueue . main . async {
DispatchQueue . main . async {
self ? . versionMigrationsDidComplete ( needsConfigSync : needsConfigSync )
self ? . versionMigrationsDidComplete ( needsConfigSync : needsConfigSync , completion : completion )
}
}
}
}
completion ( )
}
}
)
)
}
}
private func versionMigrationsDidComplete ( needsConfigSync : Bool ) {
private func versionMigrationsDidComplete ( needsConfigSync : Bool , completion : @ escaping ( ) -> Void ) {
AssertIsOnMainThread ( )
AssertIsOnMainThread ( )
// I f w e n e e d a c o n f i g s y n c t h e n t r i g g e r i t n o w
// I f w e n e e d a c o n f i g s y n c t h e n t r i g g e r i t n o w
@ -297,28 +314,49 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
}
}
}
}
checkIsAppReady ( migrationsCompleted : true )
}
private func checkIsAppReady ( migrationsCompleted : Bool ) {
AssertIsOnMainThread ( )
// O n l y m a r k t h e a p p a s r e a d y o n c e .
guard ! Singleton . appReadiness . isAppReady else { return }
// A p p i s n ' t r e a d y u n t i l s t o r a g e i s r e a d y A N D a l l v e r s i o n m i g r a t i o n s a r e c o m p l e t e .
// A p p i s n ' t r e a d y u n t i l s t o r a g e i s r e a d y A N D a l l v e r s i o n m i g r a t i o n s a r e c o m p l e t e .
guard Storage . shared . isValid && migrationsCompleted else {
guard Storage . shared . isValid else {
Log . error ( " Storage invalid. " )
Log . error ( " Storage invalid. " )
self . completeSilenty ( )
return self . completeSilenty ( )
return
}
}
SignalUtilitiesKit . Configuration . performMainSetup ( )
// I f t h e a p p w a s n ' t r e a d y t h e n m a r k i t a s r e a d y n o w
if ! Singleton . appReadiness . isAppReady {
// N o t e t h a t t h i s d o e s m u c h m o r e t h a n s e t a f l a g ; i t w i l l a l s o r u n a l l d e f e r r e d b l o c k s .
// N o t e t h a t t h i s d o e s m u c h m o r e t h a n s e t a f l a g ; i t w i l l a l s o r u n a l l d e f e r r e d b l o c k s .
Singleton . appReadiness . setAppReady ( )
Singleton . appReadiness . setAppReady ( )
}
}
completion ( )
}
// / I t ' s p o s s i b l e f o r t h e N o t i f i c a t i o n E x t e n s i o n t o s t i l l h a v e s o m e k i n d o f c a c h e d d a t a f r o m t h e o l d d a t a b a s e a f t e r i t ' s b e e n d e l e t e d
// / w h e n a n e w a c c o u n t i s c r e a t e d s h o r t l y a f t e r , t h i s r e s u l t s i n w e i r d e r r o r s w h e n r e c e i v i n g P N s f o r t h e n e w a c c o u n t
// /
// / I n o r d e r t o a v o i d t h i s s i t u a t i o n w e c h e c k t o s e e w h e t h e r t h e r e c e i v e d P N i s t a r g e t t i n g t h e c u r r e n t u s e r a n d , i f n o t , w e c a l l t h i s
// / m e t h o d t o f o r c e a r e s e t u p o f t h e n o t i f i c a t i o n e x t e n s i o n
// /
// / * * N o t e : * * W e n e e d t o r e c o n f i g u r e t h e d a t a b a s e h e r e b e c a u s e i f t h e d a t a b a s e w a s d e l e t e d i t ' s p o s s i b l e f o r t h e N o t i f i c a t i o n E x t e n s i o n
// / t o s o m e h o w s t i l l h a v e s o m e f o r m o f a c c e s s t o t h e o l d o n e
private func forceResetup ( _ notificationContent : UNMutableNotificationContent ) {
Storage . reconfigureDatabase ( )
LibSession . clearMemoryState ( )
dependencies . caches . mutate ( cache : . general ) { $0 . clearCachedUserPublicKey ( ) }
self . setUpIfNecessary ( ) { [ weak self ] in
// I f w e h a d a l r e a d y d o n e a s e t u p t h e n ` l i b S e s s i o n ` w o n ' t h a v e b e e n r e - s e t u p s o
// w e n e e d t o d o s o n o w ( t h i s e n s u r e s i t h a s t h e c o r r e c t u s e r k e y s a s w e l l )
Storage . shared . read { db in
LibSession . loadState (
db ,
userPublicKey : getUserHexEncodedPublicKey ( db ) ,
ed25519SecretKey : Identity . fetchUserEd25519KeyPair ( db ) ? . secretKey
)
}
self ? . handleNotification ( notificationContent , isPerformingResetup : true )
}
}
// MARK: H a n d l e c o m p l e t i o n
// MARK: H a n d l e c o m p l e t i o n
override public func serviceExtensionTimeWillExpire ( ) {
override public func serviceExtensionTimeWillExpire ( ) {
@ -434,5 +472,6 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
let userInfo : [ String : Any ] = [ NotificationServiceExtension . isFromRemoteKey : true ]
let userInfo : [ String : Any ] = [ NotificationServiceExtension . isFromRemoteKey : true ]
content . userInfo = userInfo
content . userInfo = userInfo
contentHandler ! ( content )
contentHandler ! ( content )
hasCompleted . mutate { $0 = true }
}
}
}
}