Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3369939
D8795.id30209.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
13 KB
Referenced Files
None
Subscribers
None
D8795.id30209.diff
View Options
diff --git a/native/ios/NotificationService/NotificationService.mm b/native/ios/NotificationService/NotificationService.mm
--- a/native/ios/NotificationService/NotificationService.mm
+++ b/native/ios/NotificationService/NotificationService.mm
@@ -37,9 +37,8 @@
@interface NotificationService ()
-@property(nonatomic, strong) void (^contentHandler)
- (UNNotificationContent *contentToDeliver);
-@property(nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
+@property(strong) NSMutableDictionary *contentHandlers;
+@property(strong) NSMutableDictionary *contents;
@end
@@ -49,17 +48,20 @@
withContentHandler:
(void (^)(UNNotificationContent *_Nonnull))
contentHandler {
+ // Set-up methods are idempotent
[NotificationService setUpNSEProcess];
+ [self setUpNSEInstance];
- self.contentHandler = contentHandler;
- self.bestAttemptContent = [request.content mutableCopy];
+ NSString *contentHandlerKey = [request.identifier copy];
+ UNMutableNotificationContent *content = [request.content mutableCopy];
+ [self putContent:content withHandler:contentHandler forKey:contentHandlerKey];
// Step 1: notification decryption.
- if ([self shouldBeDecrypted:self.bestAttemptContent.userInfo]) {
+ if ([self shouldBeDecrypted:content.userInfo]) {
std::string decryptErrorMessage;
try {
@try {
- [self decryptBestAttemptContent];
+ content = [self decryptContent:content];
} @catch (NSException *e) {
decryptErrorMessage = "NSE: Received Obj-C exception: " +
std::string([e.name UTF8String]) +
@@ -74,11 +76,11 @@
if (decryptErrorMessage.size()) {
NSString *errorMessage =
[NSString stringWithUTF8String:decryptErrorMessage.c_str()];
- [self callContentHandlerOnErrorMessage:errorMessage];
+ [self callContentHandlerForKey:contentHandlerKey
+ onErrorMessage:errorMessage];
return;
}
- } else if ([self shouldAlertUnencryptedNotification:self.bestAttemptContent
- .userInfo]) {
+ } else if ([self shouldAlertUnencryptedNotification:content.userInfo]) {
// In future this will be replaced by notification content
// modification for DEV environment and staff members
comm::Logger::log("NSE: Received erroneously unencrypted notitication.");
@@ -88,7 +90,7 @@
std::string persistErrorMessage;
try {
@try {
- [self persistMessagePayload:self.bestAttemptContent.userInfo];
+ [self persistMessagePayload:content.userInfo];
} @catch (NSException *e) {
persistErrorMessage =
"NSE: Received Obj-C exception: " + std::string([e.name UTF8String]) +
@@ -103,7 +105,8 @@
if (persistErrorMessage.size()) {
NSString *errorMessage =
[NSString stringWithUTF8String:persistErrorMessage.c_str()];
- [self callContentHandlerOnErrorMessage:errorMessage];
+ [self callContentHandlerForKey:contentHandlerKey
+ onErrorMessage:errorMessage];
return;
}
@@ -111,12 +114,12 @@
// Message payload persistence is a higher priority task, so it has
// to happen prior to potential notification center clearing.
- if ([self isRescind:self.bestAttemptContent.userInfo]) {
+ if ([self isRescind:content.userInfo]) {
std::string rescindErrorMessage;
try {
@try {
[self
- removeNotificationWithIdentifier:self.bestAttemptContent
+ removeNotificationWithIdentifier:content
.userInfo[@"notificationId"]];
} @catch (NSException *e) {
rescindErrorMessage = "NSE: Received Obj-C exception: " +
@@ -131,55 +134,97 @@
if (rescindErrorMessage.size()) {
NSString *errorMessage =
[NSString stringWithUTF8String:rescindErrorMessage.c_str()];
- [self callContentHandlerOnErrorMessage:errorMessage];
+ [self callContentHandlerForKey:contentHandlerKey
+ onErrorMessage:errorMessage];
return;
}
- self.contentHandler([[UNNotificationContent alloc] init]);
+ [self callContentHandlerForKey:contentHandlerKey
+ withContent:[[UNNotificationContent alloc] init]];
return;
}
- if ([self isBadgeOnly:self.bestAttemptContent.userInfo]) {
- UNMutableNotificationContent *badgeOnlyContent =
- [[UNMutableNotificationContent alloc] init];
- badgeOnlyContent.badge = self.bestAttemptContent.badge;
- self.contentHandler(badgeOnlyContent);
+ // Step 4: (optional) update badge count for badge only notif
+ if ([self isBadgeOnly:content.userInfo]) {
+ UNNotificationContent *badgeOnlyContent =
+ [self getBadgeOnlyContentFor:content];
+ [self callContentHandlerForKey:contentHandlerKey
+ withContent:badgeOnlyContent];
return;
}
+ // Step 5: notify main app that there is data
+ // to transfer to SQLite and redux.
[self sendNewMessageInfosNotification];
if (NSString *currentMemoryEventMessage =
[NotificationService getAndSetMemoryEventMessage:nil]) {
- [self callContentHandlerOnErrorMessage:currentMemoryEventMessage];
+ [self callContentHandlerForKey:contentHandlerKey
+ onErrorMessage:currentMemoryEventMessage];
return;
}
- self.contentHandler(self.bestAttemptContent);
+ [self callContentHandlerForKey:contentHandlerKey withContent:content];
}
- (void)serviceExtensionTimeWillExpire {
// 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.
- if ([self isRescind:self.bestAttemptContent.userInfo]) {
- // If we get to this place it means we were unable to
- // remove relevant notification from notification center in
- // in time given to NSE to process notification.
- // It is an extremely unlikely to happen.
- NSString *errorMessage =
- @"NSE: Exceeded time limit to rescind a notification.";
- [self callContentHandlerOnErrorMessage:errorMessage];
- return;
+ NSMutableArray<void (^)(UNNotificationContent *_Nonnull)> *allHandlers =
+ [[NSMutableArray alloc] init];
+ NSMutableArray<UNNotificationContent *> *allContents =
+ [[NSMutableArray alloc] init];
+
+ @synchronized(self.contentHandlers) {
+ for (NSString *key in self.contentHandlers) {
+ [allHandlers addObject:self.contentHandlers[key]];
+ [allContents addObject:self.contents[key]];
+ }
+
+ [self.contentHandlers removeAllObjects];
+ [self.contents removeAllObjects];
}
- if ([self shouldBeDecrypted:self.bestAttemptContent.userInfo] &&
- !self.bestAttemptContent.userInfo[@"succesfullyDecrypted"]) {
- // If we get to this place it means we were unable to
- // decrypt encrypted notification content in time
- // given to NSE to process notification.
- NSString *errorMessage =
- @"NSE: Exceeded time limit to decrypt a notification.";
- [self callContentHandlerOnErrorMessage:errorMessage];
- return;
+
+ for (int i = 0; i < allContents.count; i++) {
+ UNNotificationContent *content = allContents[i];
+ void (^handler)(UNNotificationContent *_Nonnull) = allHandlers[i];
+
+ if ([self isRescind:content.userInfo]) {
+ // If we get to this place it means we were unable to
+ // remove relevant notification from notification center in
+ // in time given to NSE to process notification.
+ // It is an extremely unlikely to happen.
+ NSString *errorMessage =
+ @"NSE: Exceeded time limit to rescind a notification.";
+ UNNotificationContent *errorContent =
+ [self buildContentForError:errorMessage];
+ handler(errorContent);
+ continue;
+ }
+
+ if ([self shouldBeDecrypted:content.userInfo] &&
+ !content.userInfo[@"succesfullyDecrypted"]) {
+ // If we get to this place it means we were unable to
+ // decrypt encrypted notification content in time
+ // given to NSE to process notification.
+ NSString *errorMessage =
+ @"NSE: Exceeded time limit to decrypt a notification.";
+ UNNotificationContent *errorContent =
+ [self buildContentForError:errorMessage];
+ handler(errorContent);
+ continue;
+ }
+
+ // At this point we know that the content is at least
+ // correctly decrypted so we can display it to the user.
+ // Another operation, like persistence, had failed.
+ if ([self isBadgeOnly:content.userInfo]) {
+ UNNotificationContent *badgeOnlyContent =
+ [self getBadgeOnlyContentFor:content];
+ handler(badgeOnlyContent);
+ continue;
+ }
+
+ handler(content);
}
- self.contentHandler(self.bestAttemptContent);
}
- (void)removeNotificationWithIdentifier:(NSString *)identifier {
@@ -254,6 +299,14 @@
return !payload[@"threadID"];
}
+- (UNNotificationContent *)getBadgeOnlyContentFor:
+ (UNNotificationContent *)content {
+ UNMutableNotificationContent *badgeOnlyContent =
+ [[UNMutableNotificationContent alloc] init];
+ badgeOnlyContent.badge = content.badge;
+ return badgeOnlyContent;
+}
+
- (void)sendNewMessageInfosNotification {
CFNotificationCenterPostNotification(
CFNotificationCenterGetDarwinNotifyCenter(),
@@ -283,17 +336,17 @@
.c_str()];
}
-- (void)decryptBestAttemptContent {
- NSString *decryptedSerializedPayload = [self
- singleDecrypt:self.bestAttemptContent.userInfo[encryptedPayloadKey]];
+- (UNMutableNotificationContent *)decryptContent:
+ (UNMutableNotificationContent *)content {
+ NSString *decryptedSerializedPayload =
+ [self singleDecrypt:content.userInfo[encryptedPayloadKey]];
NSDictionary *decryptedPayload = [NSJSONSerialization
JSONObjectWithData:[decryptedSerializedPayload
dataUsingEncoding:NSUTF8StringEncoding]
options:0
error:nil];
- NSMutableDictionary *mutableUserInfo =
- [self.bestAttemptContent.userInfo mutableCopy];
+ NSMutableDictionary *mutableUserInfo = [content.userInfo mutableCopy];
NSMutableDictionary *mutableAps = nil;
if (mutableUserInfo[@"aps"]) {
@@ -302,7 +355,7 @@
NSString *body = decryptedPayload[@"merged"];
if (body) {
- self.bestAttemptContent.body = body;
+ content.body = body;
if (mutableAps && mutableAps[@"alert"]) {
mutableAps[@"alert"] = body;
}
@@ -310,7 +363,7 @@
NSString *threadID = decryptedPayload[@"threadID"];
if (threadID) {
- self.bestAttemptContent.threadIdentifier = threadID;
+ content.threadIdentifier = threadID;
mutableUserInfo[@"threadID"] = threadID;
if (mutableAps) {
mutableAps[@"thread-id"] = threadID;
@@ -320,7 +373,7 @@
NSString *badgeStr = decryptedPayload[@"badge"];
if (badgeStr) {
NSNumber *badge = @([badgeStr intValue]);
- self.bestAttemptContent.badge = badge;
+ content.badge = badge;
if (mutableAps) {
mutableAps[@"badge"] = badge;
}
@@ -341,9 +394,52 @@
mutableUserInfo[@"aps"] = mutableAps;
}
[mutableUserInfo removeObjectForKey:encryptedPayloadKey];
- self.bestAttemptContent.userInfo = mutableUserInfo;
+ content.userInfo = mutableUserInfo;
+ return content;
+}
+
+// Apple documentation for NSE does not explicitely state
+// that single NSE instance will be used by only one thread
+// at a time. Even though UNNotificationServiceExtension API
+// suggests that it could be the case we don't trust it
+// and keep a synchronized collection of handlers and contents.
+
+- (void)setUpNSEInstance {
+ @synchronized(self) {
+ if (self.contentHandlers) {
+ return;
+ }
+ self.contentHandlers = [[NSMutableDictionary alloc] init];
+ self.contents = [[NSMutableDictionary alloc] init];
+ }
+}
+
+- (void)putContent:(UNNotificationContent *)content
+ withHandler:(void (^)(UNNotificationContent *_Nonnull))handler
+ forKey:(NSString *)key {
+ @synchronized(self.contentHandlers) {
+ [self.contentHandlers setObject:handler forKey:key];
+ [self.contents setObject:content forKey:key];
+ }
+}
+
+- (void)callContentHandlerForKey:(NSString *)key
+ withContent:(UNNotificationContent *)content {
+ void (^handler)(UNNotificationContent *_Nonnull);
+
+ @synchronized(self.contentHandlers) {
+ handler = [self.contentHandlers objectForKey:key];
+ [self.contentHandlers removeObjectForKey:key];
+ [self.contents removeObjectForKey:key];
+ }
+
+ if (!handler) {
+ return;
+ }
+ handler(content);
}
+
- (UNNotificationContent *)buildContentForError:(NSString *)error {
UNMutableNotificationContent *content =
[[UNMutableNotificationContent alloc] init];
@@ -351,16 +447,18 @@
return content;
}
-- (void)callContentHandlerOnErrorMessage:(NSString *)errorMessage {
+- (void)callContentHandlerForKey:(NSString *)key
+ onErrorMessage:(NSString *)errorMessage {
comm::Logger::log(std::string([errorMessage UTF8String]));
if (!comm::StaffUtils::isStaffRelease()) {
- self.contentHandler([[UNNotificationContent alloc] init]);
+ [self callContentHandlerForKey:key
+ withContent:[[UNNotificationContent alloc] init]];
return;
}
UNNotificationContent *content = [self buildContentForError:errorMessage];
- self.contentHandler(content);
+ [self callContentHandlerForKey:key withContent:content];
}
// Monitor memory usage
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Nov 27, 12:13 AM (12 h, 19 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2587017
Default Alt Text
D8795.id30209.diff (13 KB)
Attached To
Mode
D8795: Implement synchronization mechanisms to address process, thread and class level concurrency.
Attached
Detach File
Event Timeline
Log In to Comment