Changeset View
Changeset View
Standalone View
Standalone View
native/ios/NotificationService/NotificationService.mm
#import "NotificationService.h" | #import "NotificationService.h" | ||||
#import "AESCryptoModuleObjCCompat.h" | |||||
#import "CommIOSBlobClient.h" | |||||
#import "CommMMKV.h" | #import "CommMMKV.h" | ||||
#import "Logger.h" | #import "Logger.h" | ||||
#import "NotificationsCryptoModule.h" | #import "NotificationsCryptoModule.h" | ||||
#import "StaffUtils.h" | #import "StaffUtils.h" | ||||
#import "TemporaryMessageStorage.h" | #import "TemporaryMessageStorage.h" | ||||
#import <mach/mach.h> | #import <mach/mach.h> | ||||
#include <iterator> | #include <iterator> | ||||
#include <sstream> | #include <sstream> | ||||
NSString *const backgroundNotificationTypeKey = @"backgroundNotifType"; | NSString *const backgroundNotificationTypeKey = @"backgroundNotifType"; | ||||
NSString *const messageInfosKey = @"messageInfos"; | NSString *const messageInfosKey = @"messageInfos"; | ||||
NSString *const encryptedPayloadKey = @"encryptedPayload"; | NSString *const encryptedPayloadKey = @"encryptedPayload"; | ||||
NSString *const encryptionFailureKey = @"encryptionFailure"; | NSString *const encryptionFailureKey = @"encryptionFailure"; | ||||
NSString *const collapseIDKey = @"collapseID"; | NSString *const collapseIDKey = @"collapseID"; | ||||
NSString *const keyserverIDKey = @"keyserverID"; | NSString *const keyserverIDKey = @"keyserverID"; | ||||
NSString *const blobHashKey = @"blobHash"; | |||||
NSString *const encryptionKeyLabel = @"encryptionKey"; | |||||
// Those and future MMKV-related constants should match | // Those and future MMKV-related constants should match | ||||
// similar constants in CommNotificationsHandler.java | // similar constants in CommNotificationsHandler.java | ||||
const std::string mmkvKeySeparator = "."; | const std::string mmkvKeySeparator = "."; | ||||
const std::string mmkvKeyserverPrefix = "KEYSERVER"; | const std::string mmkvKeyserverPrefix = "KEYSERVER"; | ||||
const std::string mmkvUnreadCountSuffix = "UNREAD_COUNT"; | const std::string mmkvUnreadCountSuffix = "UNREAD_COUNT"; | ||||
// The context for this constant can be found here: | // The context for this constant can be found here: | ||||
▲ Show 20 Lines • Show All 227 Lines • ▼ Show 20 Lines | - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request | ||||
// only provides badge count. | // only provides badge count. | ||||
if ([self needsSilentBadgeUpdate:content.userInfo]) { | if ([self needsSilentBadgeUpdate:content.userInfo]) { | ||||
UNMutableNotificationContent *badgeOnlyContent = | UNMutableNotificationContent *badgeOnlyContent = | ||||
[[UNMutableNotificationContent alloc] init]; | [[UNMutableNotificationContent alloc] init]; | ||||
badgeOnlyContent.badge = content.badge; | badgeOnlyContent.badge = content.badge; | ||||
publicUserContent = badgeOnlyContent; | publicUserContent = badgeOnlyContent; | ||||
} | } | ||||
// Step 7: notify main app that there is data | // Step 7: (optional) download notification paylaod | ||||
// from blob service in case it is large notification | |||||
if ([self isLargeNotification:content.userInfo]) { | |||||
std::string processLargeNotificationError; | |||||
try { | |||||
@try { | |||||
[self fetchAndPersistLargeNotifPayload:content]; | |||||
} @catch (NSException *e) { | |||||
processLargeNotificationError = | |||||
"Obj-C exception: " + std::string([e.name UTF8String]) + | |||||
" during large notification processing."; | |||||
} | |||||
} catch (const std::exception &e) { | |||||
processLargeNotificationError = | |||||
"C++ exception: " + std::string(e.what()) + | |||||
" during large notification processing."; | |||||
} | |||||
if (processLargeNotificationError.size()) { | |||||
[errorMessages | |||||
addObject:[NSString stringWithUTF8String:processLargeNotificationError | |||||
.c_str()]]; | |||||
} | |||||
} | |||||
// Step 8: notify main app that there is data | |||||
// to transfer to SQLite and redux. | // to transfer to SQLite and redux. | ||||
[self sendNewMessageInfosNotification]; | [self sendNewMessageInfosNotification]; | ||||
if (NSString *currentMemoryEventMessage = | if (NSString *currentMemoryEventMessage = | ||||
[NotificationService getAndSetMemoryEventMessage:nil]) { | [NotificationService getAndSetMemoryEventMessage:nil]) { | ||||
[errorMessages addObject:currentMemoryEventMessage]; | [errorMessages addObject:currentMemoryEventMessage]; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 226 Lines • ▼ Show 20 Lines | if (!unreadCount.has_value()) { | ||||
continue; | continue; | ||||
} | } | ||||
totalUnreadCount += unreadCount.value(); | totalUnreadCount += unreadCount.value(); | ||||
} | } | ||||
content.badge = @(totalUnreadCount); | content.badge = @(totalUnreadCount); | ||||
} | } | ||||
- (void)fetchAndPersistLargeNotifPayload: | |||||
(UNMutableNotificationContent *)content { | |||||
NSString *blobHash = content.userInfo[blobHashKey]; | |||||
NSData *encryptionKey = [[NSData alloc] | |||||
initWithBase64EncodedString:content.userInfo[encryptionKeyLabel] | |||||
options:0]; | |||||
__block NSError *fetchError = nil; | |||||
NSData *largePayloadBinary = | |||||
[CommIOSBlobClient.sharedInstance getBlobSync:blobHash | |||||
orSetError:&fetchError]; | |||||
if (fetchError) { | |||||
comm::Logger::log( | |||||
"Failed to fetch notif payload from blob service. Details: " + | |||||
std::string([fetchError.localizedDescription UTF8String])); | |||||
return; | |||||
} | |||||
NSDictionary *largePayload = | |||||
[NotificationService aesDecryptAndParse:largePayloadBinary | |||||
withKey:encryptionKey]; | |||||
[self persistMessagePayload:largePayload]; | |||||
} | |||||
- (BOOL)needsSilentBadgeUpdate:(NSDictionary *)payload { | - (BOOL)needsSilentBadgeUpdate:(NSDictionary *)payload { | ||||
// TODO: refactor this check by introducing | // TODO: refactor this check by introducing | ||||
// badgeOnly property in iOS notification payload | // badgeOnly property in iOS notification payload | ||||
if (!payload[@"threadID"]) { | if (!payload[@"threadID"]) { | ||||
// This notif only contains a badge update. We could let it go through | // This notif only contains a badge update. We could let it go through | ||||
// normally, but for internal builds we set the BODY to "ENCRYPTED" for | // normally, but for internal builds we set the BODY to "ENCRYPTED" for | ||||
// debugging purposes. So instead of letting the badge-only notif go | // debugging purposes. So instead of letting the badge-only notif go | ||||
// through, we construct another notif that doesn't have a body. | // through, we construct another notif that doesn't have a body. | ||||
return true; | return true; | ||||
} | } | ||||
// If the notif is a rescind, then we'll filter it out. So we need another | // If the notif is a rescind, then we'll filter it out. So we need another | ||||
// notif to update the badge count. | // notif to update the badge count. | ||||
return [self isRescind:payload]; | return [self isRescind:payload]; | ||||
} | } | ||||
- (BOOL)isCollapsible:(NSDictionary *)payload { | - (BOOL)isCollapsible:(NSDictionary *)payload { | ||||
return payload[collapseIDKey]; | return payload[collapseIDKey]; | ||||
} | } | ||||
- (BOOL)isLargeNotification:(NSDictionary *)payload { | |||||
return payload[blobHashKey] && payload[encryptionKeyLabel]; | |||||
} | |||||
- (UNNotificationContent *)getBadgeOnlyContentFor: | - (UNNotificationContent *)getBadgeOnlyContentFor: | ||||
(UNNotificationContent *)content { | (UNNotificationContent *)content { | ||||
UNMutableNotificationContent *badgeOnlyContent = | UNMutableNotificationContent *badgeOnlyContent = | ||||
[[UNMutableNotificationContent alloc] init]; | [[UNMutableNotificationContent alloc] init]; | ||||
badgeOnlyContent.badge = content.badge; | badgeOnlyContent.badge = content.badge; | ||||
return badgeOnlyContent; | return badgeOnlyContent; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 264 Lines • ▼ Show 20 Lines | [NotificationService | ||||
getAndSetMemoryEventMessage:criticalMemoryEventMessage]; | getAndSetMemoryEventMessage:criticalMemoryEventMessage]; | ||||
}; | }; | ||||
dispatch_source_set_event_handler(memorySource, eventHandler); | dispatch_source_set_event_handler(memorySource, eventHandler); | ||||
dispatch_activate(memorySource); | dispatch_activate(memorySource); | ||||
return memorySource; | return memorySource; | ||||
} | } | ||||
// AES Cryptography | |||||
static AESCryptoModuleObjCCompat *_aesCryptoModule = nil; | |||||
+ (AESCryptoModuleObjCCompat *)processLocalAESCryptoModule { | |||||
return _aesCryptoModule; | |||||
} | |||||
+ (NSDictionary *)aesDecryptAndParse:(NSData *)sealedData | |||||
withKey:(NSData *)key { | |||||
NSError *decryptError = nil; | |||||
NSInteger destinationLength = | |||||
[[NotificationService processLocalAESCryptoModule] | |||||
decryptedLength:sealedData]; | |||||
NSMutableData *destination = [NSMutableData dataWithLength:destinationLength]; | |||||
[[NotificationService processLocalAESCryptoModule] | |||||
decryptWithKey:key | |||||
sealedData:sealedData | |||||
destination:destination | |||||
withError:&decryptError]; | |||||
if (decryptError) { | |||||
comm::Logger::log( | |||||
"NSE: Notification aes decryption failure. Details: " + | |||||
std::string([decryptError.localizedDescription UTF8String])); | |||||
return nil; | |||||
} | |||||
NSString *decryptedSerializedPayload = | |||||
[[NSString alloc] initWithData:destination encoding:NSUTF8StringEncoding]; | |||||
return [NSJSONSerialization | |||||
JSONObjectWithData:[decryptedSerializedPayload | |||||
dataUsingEncoding:NSUTF8StringEncoding] | |||||
options:0 | |||||
error:nil]; | |||||
} | |||||
// Process-local initialization code NSE may use different threads and instances | |||||
// of this class to process notifs, but it usually keeps the same process for | |||||
// extended period of time. Objects that can be initialized once and reused on | |||||
// each notif should be declared in a method below to avoid wasting resources | |||||
+ (void)setUpNSEProcess { | + (void)setUpNSEProcess { | ||||
static dispatch_source_t memoryEventSource; | static dispatch_source_t memoryEventSource; | ||||
static dispatch_once_t onceToken; | static dispatch_once_t onceToken; | ||||
dispatch_once(&onceToken, ^{ | dispatch_once(&onceToken, ^{ | ||||
_aesCryptoModule = [[AESCryptoModuleObjCCompat alloc] init]; | |||||
memoryEventSource = [NotificationService registerForMemoryEvents]; | memoryEventSource = [NotificationService registerForMemoryEvents]; | ||||
}); | }); | ||||
} | } | ||||
@end | @end |