talk-ios/NotificationServiceExtension/NotificationService.m

191 строка
11 KiB
Mathematica
Исходник Обычный вид История

/**
* @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/>.
*
*/
#import "NotificationService.h"
#import "NCAPISessionManager.h"
#import "NCAppBranding.h"
#import "NCDatabaseManager.h"
#import "NCKeyChainController.h"
#import "NCNotification.h"
#import "NCPushNotification.h"
#import "NCPushNotificationsUtils.h"
@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];
self.bestAttemptContent.title = @"";
self.bestAttemptContent.body = NSLocalizedString(@"You received a new notification", nil);
// Configure database
NSString *path = [[[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:groupIdentifier] URLByAppendingPathComponent:kTalkDatabaseFolder] path];
NSURL *databaseURL = [[NSURL fileURLWithPath:path] URLByAppendingPathComponent:kTalkDatabaseFileName];
if ([[NSFileManager defaultManager] fileExistsAtPath:databaseURL.path]) {
@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);
self.contentHandler(self.bestAttemptContent);
return;
}
} else {
NSLog(@"Database does not exist -> main app needs to run before extension.");
self.contentHandler(self.bestAttemptContent);
return;
}
RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
configuration.fileURL = databaseURL;
configuration.schemaVersion= kTalkDatabaseSchemaVersion;
configuration.objectClasses = @[TalkAccount.class];
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
};
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];
NSData *pushNotificationPrivateKey = [[NCKeyChainController sharedInstance] pushNotificationPrivateKeyForAccountId:account.accountId];
if (message && pushNotificationPrivateKey) {
@try {
NSString *decryptedMessage = [NCPushNotificationsUtils decryptPushNotification:message withDevicePrivateKey:pushNotificationPrivateKey];
if (decryptedMessage) {
NCPushNotification *pushNotification = [NCPushNotification pushNotificationFromDecryptedString:decryptedMessage withAccountId:account.accountId];
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;
}
// Update unread notifications counter for push notification account
[realm beginWriteTransaction];
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", account.accountId];
TalkAccount *managedAccount = [TalkAccount objectsInRealm:realm withPredicate:query].firstObject;
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;
}
self.bestAttemptContent.body = pushNotification.bodyForRemoteAlerts;
self.bestAttemptContent.threadIdentifier = pushNotification.roomToken;
self.bestAttemptContent.sound = [UNNotificationSound defaultSound];
self.bestAttemptContent.badge = @(unreadNotifications);
if (pushNotification.type == NCPushNotificationTypeChat) {
// Set category for chat messages to allow interactive notifications
self.bestAttemptContent.categoryIdentifier = @"CATEGORY_CHAT";
}
NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init];
[userInfo setObject:pushNotification.jsonString forKey:@"pushNotification"];
[userInfo setObject:pushNotification.accountId forKey:@"accountId"];
self.bestAttemptContent.userInfo = userInfo;
// 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;
}
// Try to get the notification from the server
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;
}
}
self.contentHandler(self.bestAttemptContent);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
self.contentHandler(self.bestAttemptContent);
}];
}
} @catch (NSException *exception) {
continue;
NSLog(@"An error ocurred decrypting the message. %@", exception);
}
}
}
}
- (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