2020-10-14 17:18:58 +03:00
|
|
|
/**
|
|
|
|
* @copyright Copyright (c) 2020 Ivan Sein <ivan@nextcloud.com>
|
|
|
|
*
|
|
|
|
* @author Ivan Sein <ivan@nextcloud.com>
|
|
|
|
*
|
|
|
|
* @license GNU GPL version 3 or any later version
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*
|
|
|
|
*/
|
2020-06-23 18:11:47 +03:00
|
|
|
|
|
|
|
#import "NotificationService.h"
|
|
|
|
|
2021-07-14 20:13:26 +03:00
|
|
|
#import "NCAPISessionManager.h"
|
2020-10-27 18:23:50 +03:00
|
|
|
#import "NCAppBranding.h"
|
2020-06-24 19:11:02 +03:00
|
|
|
#import "NCDatabaseManager.h"
|
2021-05-26 22:58:50 +03:00
|
|
|
#import "NCKeyChainController.h"
|
2020-07-01 10:32:01 +03:00
|
|
|
#import "NCNotification.h"
|
2020-06-24 19:11:02 +03:00
|
|
|
#import "NCPushNotification.h"
|
2021-05-27 15:14:45 +03:00
|
|
|
#import "NCPushNotificationsUtils.h"
|
2020-06-24 19:11:02 +03:00
|
|
|
|
2020-06-23 18:11:47 +03:00
|
|
|
@interface NotificationService ()
|
|
|
|
|
|
|
|
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
|
|
|
|
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation NotificationService
|
|
|
|
|
|
|
|
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
|
|
|
|
self.contentHandler = contentHandler;
|
|
|
|
self.bestAttemptContent = [request.content mutableCopy];
|
|
|
|
|
2020-06-24 19:11:02 +03:00
|
|
|
self.bestAttemptContent.title = @"";
|
2020-10-13 20:33:09 +03:00
|
|
|
self.bestAttemptContent.body = NSLocalizedString(@"You received a new notification", nil);
|
2020-06-24 19:11:02 +03:00
|
|
|
|
|
|
|
// Configure database
|
2020-10-27 18:23:50 +03:00
|
|
|
NSString *path = [[[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:groupIdentifier] URLByAppendingPathComponent:kTalkDatabaseFolder] path];
|
2020-06-24 19:11:02 +03:00
|
|
|
NSURL *databaseURL = [[NSURL fileURLWithPath:path] URLByAppendingPathComponent:kTalkDatabaseFileName];
|
2021-01-06 18:33:55 +03:00
|
|
|
|
|
|
|
if ([[NSFileManager defaultManager] fileExistsAtPath:databaseURL.path]) {
|
2021-01-07 13:06:15 +03:00
|
|
|
@try {
|
|
|
|
NSError *error = nil;
|
|
|
|
|
|
|
|
// schemaVersionAtURL throws an exception when file is not readable
|
|
|
|
uint64_t currentSchemaVersion = [RLMRealm schemaVersionAtURL:databaseURL encryptionKey:nil error:&error];
|
|
|
|
|
|
|
|
if (error || currentSchemaVersion != kTalkDatabaseSchemaVersion) {
|
|
|
|
NSLog(@"Current schemaVersion is %llu app schemaVersion is %llu", currentSchemaVersion, kTalkDatabaseSchemaVersion);
|
|
|
|
NSLog(@"Database needs migration -> don't open database from extension");
|
|
|
|
|
|
|
|
self.contentHandler(self.bestAttemptContent);
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
NSLog(@"Current schemaVersion is %llu app schemaVersion is %llu", currentSchemaVersion, kTalkDatabaseSchemaVersion);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@catch (NSException *exception) {
|
|
|
|
NSLog(@"Reading schemaVersion failed: %@", exception.reason);
|
2021-01-06 18:33:55 +03:00
|
|
|
self.contentHandler(self.bestAttemptContent);
|
|
|
|
return;
|
|
|
|
}
|
2021-01-07 18:19:22 +03:00
|
|
|
} else {
|
|
|
|
NSLog(@"Database does not exist -> main app needs to run before extension.");
|
|
|
|
self.contentHandler(self.bestAttemptContent);
|
|
|
|
return;
|
2021-01-06 18:33:55 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
|
2020-06-24 19:11:02 +03:00
|
|
|
configuration.fileURL = databaseURL;
|
|
|
|
configuration.schemaVersion= kTalkDatabaseSchemaVersion;
|
|
|
|
configuration.objectClasses = @[TalkAccount.class];
|
2020-11-27 00:07:00 +03:00
|
|
|
configuration.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
|
|
|
|
// At the very minimum we need to update the version with an empty block to indicate that the schema has been upgraded (automatically) by Realm
|
|
|
|
};
|
2020-06-24 19:11:02 +03:00
|
|
|
NSError *error = nil;
|
|
|
|
RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:&error];
|
|
|
|
|
|
|
|
// Decrypt message
|
|
|
|
NSString *message = [self.bestAttemptContent.userInfo objectForKey:@"subject"];
|
|
|
|
for (TalkAccount *talkAccount in [TalkAccount allObjectsInRealm:realm]) {
|
|
|
|
TalkAccount *account = [[TalkAccount alloc] initWithValue:talkAccount];
|
2021-05-26 22:58:50 +03:00
|
|
|
NSData *pushNotificationPrivateKey = [[NCKeyChainController sharedInstance] pushNotificationPrivateKeyForAccountId:account.accountId];
|
2020-06-24 19:11:02 +03:00
|
|
|
if (message && pushNotificationPrivateKey) {
|
|
|
|
@try {
|
2021-05-27 15:14:45 +03:00
|
|
|
NSString *decryptedMessage = [NCPushNotificationsUtils decryptPushNotification:message withDevicePrivateKey:pushNotificationPrivateKey];
|
2020-06-24 19:11:02 +03:00
|
|
|
if (decryptedMessage) {
|
|
|
|
NCPushNotification *pushNotification = [NCPushNotification pushNotificationFromDecryptedString:decryptedMessage withAccountId:account.accountId];
|
2020-07-02 15:46:43 +03:00
|
|
|
|
2021-12-17 19:47:49 +03:00
|
|
|
if (pushNotification.type == NCPushNotificationTypeAdminNotification) {
|
|
|
|
// Test notification send through "occ notification:test-push --talk <userid>"
|
|
|
|
// No need to increase the badge or query the server about it
|
|
|
|
|
|
|
|
self.bestAttemptContent.body = pushNotification.subject;
|
|
|
|
self.contentHandler(self.bestAttemptContent);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-02 15:46:43 +03:00
|
|
|
// Update unread notifications counter for push notification account
|
|
|
|
[realm beginWriteTransaction];
|
|
|
|
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", account.accountId];
|
2021-06-29 22:06:25 +03:00
|
|
|
TalkAccount *managedAccount = [TalkAccount objectsInRealm:realm withPredicate:query].firstObject;
|
2020-07-02 15:46:43 +03:00
|
|
|
managedAccount.unreadBadgeNumber += 1;
|
|
|
|
managedAccount.unreadNotification = (managedAccount.active) ? NO : YES;
|
|
|
|
[realm commitWriteTransaction];
|
|
|
|
|
|
|
|
// Get the total number of unread notifications
|
|
|
|
NSInteger unreadNotifications = 0;
|
|
|
|
for (TalkAccount *user in [TalkAccount allObjectsInRealm:realm]) {
|
|
|
|
unreadNotifications += user.unreadBadgeNumber;
|
|
|
|
}
|
|
|
|
|
2020-07-01 10:34:47 +03:00
|
|
|
self.bestAttemptContent.body = pushNotification.bodyForRemoteAlerts;
|
2020-07-01 10:48:06 +03:00
|
|
|
self.bestAttemptContent.threadIdentifier = pushNotification.roomToken;
|
|
|
|
self.bestAttemptContent.sound = [UNNotificationSound defaultSound];
|
2020-07-02 15:46:43 +03:00
|
|
|
self.bestAttemptContent.badge = @(unreadNotifications);
|
2020-11-14 20:25:37 +03:00
|
|
|
|
|
|
|
if (pushNotification.type == NCPushNotificationTypeChat) {
|
|
|
|
// Set category for chat messages to allow interactive notifications
|
|
|
|
self.bestAttemptContent.categoryIdentifier = @"CATEGORY_CHAT";
|
|
|
|
}
|
|
|
|
|
2020-07-01 10:48:06 +03:00
|
|
|
NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init];
|
|
|
|
[userInfo setObject:pushNotification.jsonString forKey:@"pushNotification"];
|
|
|
|
[userInfo setObject:pushNotification.accountId forKey:@"accountId"];
|
|
|
|
self.bestAttemptContent.userInfo = userInfo;
|
2020-06-26 19:24:38 +03:00
|
|
|
// Create title and body structure if there is a new line in the subject
|
|
|
|
NSArray* components = [pushNotification.subject componentsSeparatedByString:@"\n"];
|
|
|
|
if (components.count > 1) {
|
|
|
|
NSString *title = [components objectAtIndex:0];
|
|
|
|
NSMutableArray *mutableComponents = [[NSMutableArray alloc] initWithArray:components];
|
|
|
|
[mutableComponents removeObjectAtIndex:0];
|
|
|
|
NSString *body = [mutableComponents componentsJoinedByString:@"\n"];
|
|
|
|
self.bestAttemptContent.title = title;
|
|
|
|
self.bestAttemptContent.body = body;
|
|
|
|
}
|
2020-07-01 10:32:01 +03:00
|
|
|
// Try to get the notification from the server
|
2021-07-14 20:13:26 +03:00
|
|
|
NSString *URLString = [NSString stringWithFormat:@"%@/ocs/v2.php/apps/notifications/api/v2/notifications/%ld", account.server, (long)pushNotification.notificationId];
|
|
|
|
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
|
|
|
|
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:account.accountId];
|
|
|
|
configuration.HTTPCookieStorage = cookieStorage;
|
|
|
|
NCAPISessionManager *apiSessionManager = [[NCAPISessionManager alloc] initWithSessionConfiguration:configuration];
|
|
|
|
|
|
|
|
NSString *userTokenString = [NSString stringWithFormat:@"%@:%@", account.user, [[NCKeyChainController sharedInstance] tokenForAccountId:account.accountId]];
|
|
|
|
NSData *data = [userTokenString dataUsingEncoding:NSUTF8StringEncoding];
|
|
|
|
NSString *base64Encoded = [data base64EncodedStringWithOptions:0];
|
|
|
|
[apiSessionManager.requestSerializer setValue:[[NSString alloc]initWithFormat:@"Basic %@",base64Encoded] forHTTPHeaderField:@"Authorization"];
|
|
|
|
|
|
|
|
[apiSessionManager GET:URLString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
|
|
|
|
NSDictionary *notification = [[responseObject objectForKey:@"ocs"] objectForKey:@"data"];
|
|
|
|
NCNotification *serverNotification = [NCNotification notificationWithDictionary:notification];
|
|
|
|
if (serverNotification && serverNotification.notificationType == kNCNotificationTypeChat) {
|
|
|
|
self.bestAttemptContent.title = serverNotification.chatMessageTitle;
|
|
|
|
self.bestAttemptContent.body = serverNotification.message;
|
|
|
|
if (@available(iOS 12.0, *)) {
|
|
|
|
self.bestAttemptContent.summaryArgument = serverNotification.chatMessageAuthor;
|
2020-07-01 10:32:01 +03:00
|
|
|
}
|
|
|
|
}
|
2021-07-14 21:11:47 +03:00
|
|
|
self.contentHandler(self.bestAttemptContent);
|
2021-07-14 20:13:26 +03:00
|
|
|
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
|
2020-07-01 10:32:01 +03:00
|
|
|
self.contentHandler(self.bestAttemptContent);
|
|
|
|
}];
|
2020-06-24 19:11:02 +03:00
|
|
|
}
|
|
|
|
} @catch (NSException *exception) {
|
|
|
|
continue;
|
|
|
|
NSLog(@"An error ocurred decrypting the message. %@", exception);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-06-23 18:11:47 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
- (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.
|
|
|
|
self.contentHandler(self.bestAttemptContent);
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|