Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 85 additions & 41 deletions Notification Service Extension/NotificationService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@

import UIKit
import UserNotifications
import os
import NextcloudKit

class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
var request: UNNotificationRequest?
private let deliveryLock = OSAllocatedUnfairLock()

override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
Expand All @@ -35,59 +37,101 @@ class NotificationService: UNNotificationServiceExtension {

NextcloudKit.configureLogger(logLevel: .verbose)

if let bestAttemptContent = bestAttemptContent {
bestAttemptContent.title = ""
bestAttemptContent.body = "Nextcloud notification"
do {
if let message = bestAttemptContent.userInfo["subject"] as? String {
for tableAccount in NCManageDatabase.shared.getAllTableAccount() {
guard let privateKey = NCPreferences().getPushNotificationPrivateKey(account: tableAccount.account) else {
bestAttemptContent.body = "Error retrieving private key for \(tableAccount.account)"
continue
}
guard let bestAttemptContent else { return }

let prefixData = Data(privateKey.prefix(8))
let prefixBase64 = prefixData.base64EncodedString()
nkLog(debug: "🔑 Loaded private key for \(tableAccount.account): prefix(Base64)=\(prefixBase64)")
bestAttemptContent.title = ""
bestAttemptContent.body = "Nextcloud notification"

guard let decryptedMessage = NCPushNotificationEncryption.shared().decryptPushNotification(message, withDevicePrivateKey: privateKey) else {
bestAttemptContent.body = "Error decryption for \(tableAccount.account)"
continue
}
guard let data = decryptedMessage.data(using: .utf8) else {
bestAttemptContent.body = "Error decryption data utf8 for \(tableAccount.account)"
continue
}
var matchedAccount: tableAccount?
var payload: [String: AnyObject]?

do {
if let message = bestAttemptContent.userInfo["subject"] as? String {
for tableAccount in NCManageDatabase.shared.getAllTableAccount() {
guard let privateKey = NCPreferences().getPushNotificationPrivateKey(account: tableAccount.account) else {
bestAttemptContent.body = "Error retrieving private key for \(tableAccount.account)"
continue
}

let prefixData = Data(privateKey.prefix(8))
let prefixBase64 = prefixData.base64EncodedString()
nkLog(debug: "🔑 Loaded private key for \(tableAccount.account): prefix(Base64)=\(prefixBase64)")
Comment thread
mpivchev marked this conversation as resolved.

if var json = try JSONSerialization.jsonObject(with: data) as? [String: AnyObject],
let subject = json["subject"] as? String {
bestAttemptContent.body = subject
if let pref = UserDefaults(suiteName: NCBrandOptions.shared.capabilitiesGroup) {
json["account"] = tableAccount.account as AnyObject
pref.set(json, forKey: "NOTIFICATION_DATA")
pref.synchronize()
}
} else {
bestAttemptContent.body = "Error JSON Serialization for \(tableAccount.account)"
guard let decryptedMessage = NCPushNotificationEncryption.shared().decryptPushNotification(message, withDevicePrivateKey: privateKey) else {
bestAttemptContent.body = "Error decrypting notification for \(tableAccount.account)"
continue
}
guard let data = decryptedMessage.data(using: .utf8) else {
bestAttemptContent.body = "Error decrypting UTF8 notification data for \(tableAccount.account)"
continue
}

if var json = try JSONSerialization.jsonObject(with: data) as? [String: AnyObject],
let subject = json["subject"] as? String {
bestAttemptContent.body = subject
if let pref = UserDefaults(suiteName: NCBrandOptions.shared.capabilitiesGroup) {
json["account"] = tableAccount.account as AnyObject
pref.set(json, forKey: "NOTIFICATION_DATA")
pref.synchronize()
}
break

matchedAccount = tableAccount
payload = json
} else {
bestAttemptContent.body = "Error with notification JSON for \(tableAccount.account)"
}
break
Comment thread
mpivchev marked this conversation as resolved.
}
Comment thread
mpivchev marked this conversation as resolved.
} catch let error as NSError {
nkLog(error: "Failed : \(error.localizedDescription)")
}
} catch let error as NSError {
nkLog(error: "Failed : \(error.localizedDescription)")
}

contentHandler(bestAttemptContent)
guard let tblAccount = matchedAccount,
let nid = payload?["nid"] as? Int,
payload?["delete"] as? Bool != true,
payload?["delete-all"] as? Bool != true else {
deliver()
return
}

// NextcloudKit Session
NextcloudKit.shared.setup(groupIdentifier: NCBrandOptions.shared.capabilitiesGroup, delegate: NCNetworking.shared)
NextcloudKit.shared.appendSession(account: tblAccount.account,
urlBase: tblAccount.urlBase,
user: tblAccount.user,
userId: tblAccount.userId,
password: NCPreferences().getPassword(account: tblAccount.account),
userAgent: userAgent,
groupIdentifier: NCBrandOptions.shared.capabilitiesGroup)

Task {
let results = await NextcloudKit.shared.getNotificationsAsync(idNotification: nid,
account: tblAccount.account,
options: NKRequestOptions(timeout: 20))

if results.error == .success, let notification = results.notifications?.first {
bestAttemptContent.title = notification.message.isEmpty ? NCBrandOptions.shared.brand : notification.subject
bestAttemptContent.body = notification.message.isEmpty ? notification.subject : notification.message
}
Comment thread
mpivchev marked this conversation as resolved.

deliver()
}
}

override func 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 let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
bestAttemptContent.title = ""
bestAttemptContent.body = "Nextcloud Notification Time Will Expire"
contentHandler(bestAttemptContent)
deliver()
}

private func deliver() {
let handler = deliveryLock.withLock {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let handler = contentHandler
contentHandler = nil
return handler
}

if let handler, let bestAttemptContent {
handler(bestAttemptContent)
}
}
}
Comment thread
mpivchev marked this conversation as resolved.
Loading