2021-04-25 06:48:29 +02:00
|
|
|
|
//
|
|
|
|
|
// NotificationService.swift
|
|
|
|
|
// NotificationService
|
|
|
|
|
//
|
|
|
|
|
// Created by MainasuK Cirno on 2021-4-23.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import UserNotifications
|
|
|
|
|
import CommonOSLog
|
|
|
|
|
import CryptoKit
|
|
|
|
|
import AlamofireImage
|
2022-09-30 13:28:09 +02:00
|
|
|
|
import MastodonCore
|
2021-04-25 06:48:29 +02:00
|
|
|
|
|
|
|
|
|
class NotificationService: UNNotificationServiceExtension {
|
|
|
|
|
|
|
|
|
|
var contentHandler: ((UNNotificationContent) -> Void)?
|
|
|
|
|
var bestAttemptContent: UNMutableNotificationContent?
|
|
|
|
|
|
|
|
|
|
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
|
|
|
|
self.contentHandler = contentHandler
|
|
|
|
|
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
|
|
|
|
|
|
|
|
|
|
if let bestAttemptContent = bestAttemptContent {
|
|
|
|
|
// Modify the notification content here...
|
|
|
|
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
|
|
|
|
|
2021-04-27 10:26:59 +02:00
|
|
|
|
let privateKey = AppSecret.default.notificationPrivateKey
|
|
|
|
|
let auth = AppSecret.default.notificationAuth
|
2021-04-25 06:48:29 +02:00
|
|
|
|
|
2021-06-18 12:57:02 +02:00
|
|
|
|
guard let encodedPayload = bestAttemptContent.userInfo["p"] as? String else {
|
2021-04-25 06:48:29 +02:00
|
|
|
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: invalid payload", ((#file as NSString).lastPathComponent), #line, #function)
|
|
|
|
|
contentHandler(bestAttemptContent)
|
|
|
|
|
return
|
|
|
|
|
}
|
2021-06-18 12:57:02 +02:00
|
|
|
|
let payload = encodedPayload.decode85()
|
2021-04-25 06:48:29 +02:00
|
|
|
|
|
|
|
|
|
guard let encodedPublicKey = bestAttemptContent.userInfo["k"] as? String,
|
|
|
|
|
let publicKey = NotificationService.publicKey(encodedPublicKey: encodedPublicKey) else {
|
|
|
|
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: invalid public key", ((#file as NSString).lastPathComponent), #line, #function)
|
|
|
|
|
contentHandler(bestAttemptContent)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-18 12:57:02 +02:00
|
|
|
|
guard let encodedSalt = bestAttemptContent.userInfo["s"] as? String else {
|
2021-04-25 06:48:29 +02:00
|
|
|
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: invalid salt", ((#file as NSString).lastPathComponent), #line, #function)
|
|
|
|
|
contentHandler(bestAttemptContent)
|
|
|
|
|
return
|
|
|
|
|
}
|
2021-06-18 12:57:02 +02:00
|
|
|
|
let salt = encodedSalt.decode85()
|
|
|
|
|
|
2021-04-25 06:48:29 +02:00
|
|
|
|
guard let plaintextData = NotificationService.decrypt(payload: payload, salt: salt, auth: auth, privateKey: privateKey, publicKey: publicKey),
|
2021-04-27 10:26:59 +02:00
|
|
|
|
let notification = try? JSONDecoder().decode(MastodonPushNotification.self, from: plaintextData) else {
|
2021-04-25 06:48:29 +02:00
|
|
|
|
contentHandler(bestAttemptContent)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bestAttemptContent.title = notification.title
|
|
|
|
|
bestAttemptContent.subtitle = ""
|
2021-07-07 13:16:30 +02:00
|
|
|
|
bestAttemptContent.body = notification.body.escape()
|
2021-06-19 00:14:41 +02:00
|
|
|
|
bestAttemptContent.sound = UNNotificationSound.init(named: UNNotificationSoundName(rawValue: "BoopSound.caf"))
|
2021-04-27 10:26:59 +02:00
|
|
|
|
bestAttemptContent.userInfo["plaintext"] = plaintextData
|
2021-04-25 06:48:29 +02:00
|
|
|
|
|
2021-09-16 10:30:21 +02:00
|
|
|
|
let accessToken = notification.accessToken
|
|
|
|
|
UserDefaults.shared.increaseNotificationCount(accessToken: accessToken)
|
|
|
|
|
|
2021-04-26 12:19:20 +02:00
|
|
|
|
UserDefaults.shared.notificationBadgeCount += 1
|
|
|
|
|
bestAttemptContent.badge = NSNumber(integerLiteral: UserDefaults.shared.notificationBadgeCount)
|
|
|
|
|
|
2021-04-25 06:48:29 +02:00
|
|
|
|
if let urlString = notification.icon, let url = URL(string: urlString) {
|
|
|
|
|
let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("notification-attachments")
|
|
|
|
|
try? FileManager.default.createDirectory(at: temporaryDirectoryURL, withIntermediateDirectories: true, attributes: nil)
|
|
|
|
|
let filename = url.lastPathComponent
|
|
|
|
|
let fileURL = temporaryDirectoryURL.appendingPathComponent(filename)
|
|
|
|
|
|
|
|
|
|
ImageDownloader.default.download(URLRequest(url: url), completion: { [weak self] response in
|
|
|
|
|
guard let _ = self else { return }
|
|
|
|
|
switch response.result {
|
|
|
|
|
case .failure(let error):
|
|
|
|
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: download image %s fail: %s", ((#file as NSString).lastPathComponent), #line, #function, url.debugDescription, error.localizedDescription)
|
|
|
|
|
case .success(let image):
|
|
|
|
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: download image %s success", ((#file as NSString).lastPathComponent), #line, #function, url.debugDescription)
|
|
|
|
|
try? image.pngData()?.write(to: fileURL)
|
|
|
|
|
if let attachment = try? UNNotificationAttachment(identifier: filename, url: fileURL, options: nil) {
|
|
|
|
|
bestAttemptContent.attachments = [attachment]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
contentHandler(bestAttemptContent)
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
contentHandler(bestAttemptContent)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
contentHandler(bestAttemptContent)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extension NotificationService {
|
|
|
|
|
static func publicKey(encodedPublicKey: String) -> P256.KeyAgreement.PublicKey? {
|
2021-06-18 12:57:02 +02:00
|
|
|
|
let publicKeyData = encodedPublicKey.decode85()
|
2021-04-25 06:48:29 +02:00
|
|
|
|
return try? P256.KeyAgreement.PublicKey(x963Representation: publicKeyData)
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-07-07 13:16:30 +02:00
|
|
|
|
|
|
|
|
|
extension String {
|
|
|
|
|
func escape() -> String {
|
|
|
|
|
return self
|
|
|
|
|
.replacingOccurrences(of: "&", with: "&")
|
|
|
|
|
.replacingOccurrences(of: "<", with: "<")
|
|
|
|
|
.replacingOccurrences(of: ">", with: ">")
|
|
|
|
|
.replacingOccurrences(of: """, with: "\"")
|
|
|
|
|
.replacingOccurrences(of: "'", with: "'")
|
2021-07-07 14:12:55 +02:00
|
|
|
|
.replacingOccurrences(of: "'", with: "’")
|
|
|
|
|
|
2021-07-07 13:16:30 +02:00
|
|
|
|
}
|
|
|
|
|
}
|