NCPushNotification.swift 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. //
  2. // NCPushNotification.swift
  3. // Nextcloud
  4. //
  5. // Created by Marino Faggiana on 26/06/24.
  6. // Copyright © 2024 Marino Faggiana. All rights reserved.
  7. //
  8. import Foundation
  9. import UIKit
  10. import UserNotifications
  11. import NextcloudKit
  12. class NCPushNotification {
  13. static let shared = NCPushNotification()
  14. let keychain = NCKeychain()
  15. var pushKitToken: String = ""
  16. func pushNotification() {
  17. if pushKitToken.isEmpty { return }
  18. for tblAccount in NCManageDatabase.shared.getAllTableAccount() {
  19. let token = keychain.getPushNotificationToken(account: tblAccount.account)
  20. if token != pushKitToken {
  21. if token != nil {
  22. unsubscribingNextcloudServerPushNotification(account: tblAccount.account, urlBase: tblAccount.urlBase, user: tblAccount.user, withSubscribing: true)
  23. } else {
  24. subscribingNextcloudServerPushNotification(account: tblAccount.account, urlBase: tblAccount.urlBase, user: tblAccount.user)
  25. }
  26. }
  27. }
  28. }
  29. func applicationdidReceiveRemoteNotification(userInfo: [AnyHashable: Any], completion: @escaping (_ result: UIBackgroundFetchResult) -> Void) {
  30. if let message = userInfo["subject"] as? String {
  31. for tblAccount in NCManageDatabase.shared.getAllTableAccount() {
  32. if let privateKey = keychain.getPushNotificationPrivateKey(account: tblAccount.account),
  33. let decryptedMessage = NCPushNotificationEncryption.shared().decryptPushNotification(message, withDevicePrivateKey: privateKey),
  34. let jsonData = decryptedMessage.data(using: .utf8) {
  35. do {
  36. if let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] {
  37. let nid = jsonObject["nid"] as? Int
  38. let delete = jsonObject["delete"] as? Bool
  39. let deleteAll = jsonObject["delete-all"] as? Bool
  40. if let delete, delete, let nid {
  41. removeNotificationWithNotificationId(nid, usingDecryptionKey: privateKey)
  42. } else if let deleteAll, deleteAll {
  43. cleanAllNotifications()
  44. }
  45. } else {
  46. print("Failed to convert JSON data to dictionary.")
  47. }
  48. } catch {
  49. print("Error parsing")
  50. }
  51. }
  52. }
  53. }
  54. completion(UIBackgroundFetchResult.noData)
  55. }
  56. func subscribingNextcloudServerPushNotification(account: String, urlBase: String, user: String) {
  57. if pushKitToken.isEmpty { return }
  58. NCPushNotificationEncryption.shared().generatePushNotificationsKeyPair(account)
  59. guard let pushTokenHash = NCEndToEndEncryption.shared().createSHA512(pushKitToken),
  60. let pushPublicKey = keychain.getPushNotificationPublicKey(account: account),
  61. let pushDevicePublicKey = String(data: pushPublicKey, encoding: .utf8) else { return }
  62. let proxyServerPath = NCBrandOptions.shared.pushNotificationServerProxy
  63. NextcloudKit.shared.subscribingPushNotification(serverUrl: urlBase, pushTokenHash: pushTokenHash, devicePublicKey: pushDevicePublicKey, proxyServerUrl: proxyServerPath, account: account) { account, deviceIdentifier, signature, publicKey, _, error in
  64. if error == .success, let deviceIdentifier, let signature, let publicKey {
  65. let userAgent = String(format: "%@ (Strict VoIP)", NCBrandOptions.shared.getUserAgent())
  66. let options = NKRequestOptions(customUserAgent: userAgent)
  67. NextcloudKit.shared.subscribingPushProxy(proxyServerUrl: proxyServerPath, pushToken: self.pushKitToken, deviceIdentifier: deviceIdentifier, signature: signature, publicKey: publicKey, account: account, options: options) { account, _, error in
  68. if error == .success {
  69. NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Subscribed to Push Notification server & proxy successfully")
  70. self.keychain.setPushNotificationToken(account: account, token: self.pushKitToken)
  71. self.keychain.setPushNotificationDeviceIdentifier(account: account, deviceIdentifier: deviceIdentifier)
  72. self.keychain.setPushNotificationDeviceIdentifierSignature(account: account, deviceIdentifierSignature: signature)
  73. self.keychain.setPushNotificationSubscribingPublicKey(account: account, publicKey: publicKey)
  74. }
  75. }
  76. }
  77. }
  78. }
  79. func unsubscribingNextcloudServerPushNotification(account: String, urlBase: String, user: String, withSubscribing subscribing: Bool) {
  80. guard let deviceIdentifier = keychain.getPushNotificationDeviceIdentifier(account: account),
  81. let signature = keychain.getPushNotificationDeviceIdentifierSignature(account: account),
  82. let publicKey = keychain.getPushNotificationSubscribingPublicKey(account: account) else { return }
  83. NextcloudKit.shared.unsubscribingPushNotification(serverUrl: urlBase, account: account) { _, _, error in
  84. if error == .success {
  85. let proxyServerPath = NCBrandOptions.shared.pushNotificationServerProxy
  86. let userAgent = String(format: "%@ (Strict VoIP)", NCBrandOptions.shared.getUserAgent())
  87. let options = NKRequestOptions(customUserAgent: userAgent)
  88. NextcloudKit.shared.unsubscribingPushProxy(proxyServerUrl: proxyServerPath, deviceIdentifier: deviceIdentifier, signature: signature, publicKey: publicKey, account: account, options: options) { account, _, error in
  89. if error == .success {
  90. NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Unsubscribed to Push Notification server & proxy successfully")
  91. self.keychain.setPushNotificationPublicKey(account: account, data: nil)
  92. self.keychain.setPushNotificationSubscribingPublicKey(account: account, publicKey: nil)
  93. self.keychain.setPushNotificationPrivateKey(account: account, data: nil)
  94. self.keychain.setPushNotificationToken(account: account, token: nil)
  95. self.keychain.setPushNotificationDeviceIdentifier(account: account, deviceIdentifier: nil)
  96. self.keychain.setPushNotificationDeviceIdentifierSignature(account: account, deviceIdentifierSignature: nil)
  97. if !self.pushKitToken.isEmpty && subscribing {
  98. self.subscribingNextcloudServerPushNotification(account: account, urlBase: urlBase, user: user)
  99. }
  100. }
  101. }
  102. }
  103. }
  104. }
  105. func removeNotificationWithNotificationId(_ notificationId: Int, usingDecryptionKey key: Data) {
  106. // Check in pending notifications
  107. UNUserNotificationCenter.current().getPendingNotificationRequests { requests in
  108. for request in requests {
  109. if let message = request.content.userInfo["subject"] as? String,
  110. let decryptedMessage = NCPushNotificationEncryption.shared().decryptPushNotification(message, withDevicePrivateKey: key),
  111. let jsonData = decryptedMessage.data(using: .utf8) {
  112. do {
  113. if let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] {
  114. let nid = jsonObject["nid"] as? Int
  115. if nid == notificationId {
  116. UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [request.identifier])
  117. }
  118. } else {
  119. print("Failed to convert JSON data to dictionary.")
  120. }
  121. } catch {
  122. print("Error parsing")
  123. }
  124. }
  125. }
  126. }
  127. // Check in delivered notifications
  128. UNUserNotificationCenter.current().getDeliveredNotifications { notifications in
  129. for notification in notifications {
  130. if let message = notification.request.content.userInfo["subject"] as? String,
  131. let decryptedMessage = NCPushNotificationEncryption.shared().decryptPushNotification(message, withDevicePrivateKey: key),
  132. let jsonData = decryptedMessage.data(using: .utf8) {
  133. do {
  134. if let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] {
  135. let nid = jsonObject["nid"] as? Int
  136. if nid == notificationId {
  137. UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [notification.request.identifier])
  138. }
  139. } else {
  140. print("Failed to convert JSON data to dictionary.")
  141. }
  142. } catch {
  143. print("Error parsing")
  144. }
  145. }
  146. }
  147. }
  148. }
  149. func registerForRemoteNotificationsWithDeviceToken(_ deviceToken: Data) {
  150. self.pushKitToken = NCPushNotificationEncryption.shared().string(withDeviceToken: deviceToken)
  151. pushNotification()
  152. }
  153. func cleanAllNotifications() {
  154. UNUserNotificationCenter.current().removeAllDeliveredNotifications()
  155. }
  156. }