✨ Add iOS notification services
🐛 Fix didn't request notification permission
This commit is contained in:
143
ios/SolarNotifyService/NotificationService.swift
Normal file
143
ios/SolarNotifyService/NotificationService.swift
Normal file
@ -0,0 +1,143 @@
|
||||
//
|
||||
// NotificationService.swift
|
||||
// SolarNotifyService
|
||||
//
|
||||
// Created by LittleSheep on 2024/12/8.
|
||||
//
|
||||
|
||||
import UserNotifications
|
||||
import Intents
|
||||
|
||||
enum ParseNotificationPayloadError: Error {
|
||||
case missingMetadata(String)
|
||||
case missingAvatarUrl(String)
|
||||
}
|
||||
|
||||
class NotificationService: UNNotificationServiceExtension {
|
||||
|
||||
private var contentHandler: ((UNNotificationContent) -> Void)?
|
||||
private var bestAttemptContent: UNMutableNotificationContent?
|
||||
private let serverBaseUrl = "https://api.sn.solsynth.dev"
|
||||
|
||||
private func getAttachmentUrl(for identifier: String) -> String {
|
||||
identifier.starts(with: "http") ? identifier : "\(serverBaseUrl)/cgi/uc/attachments/\(identifier)"
|
||||
}
|
||||
|
||||
override func didReceive(
|
||||
_ request: UNNotificationRequest,
|
||||
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
|
||||
) {
|
||||
self.contentHandler = contentHandler
|
||||
guard let bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent else {
|
||||
contentHandler(request.content)
|
||||
return
|
||||
}
|
||||
self.bestAttemptContent = bestAttemptContent
|
||||
|
||||
do {
|
||||
try processNotification(request: request, content: bestAttemptContent)
|
||||
} catch {
|
||||
contentHandler(bestAttemptContent)
|
||||
}
|
||||
}
|
||||
|
||||
override func serviceExtensionTimeWillExpire() {
|
||||
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
|
||||
contentHandler(bestAttemptContent)
|
||||
}
|
||||
}
|
||||
|
||||
private func processNotification(request: UNNotificationRequest, content: UNMutableNotificationContent) throws {
|
||||
switch content.categoryIdentifier {
|
||||
case "messaging.message", "messaging.callStart":
|
||||
try handleMessagingNotification(request: request, content: content)
|
||||
default:
|
||||
try handleDefaultNotification(content: content)
|
||||
}
|
||||
}
|
||||
|
||||
private func handleMessagingNotification(request: UNNotificationRequest, content: UNMutableNotificationContent) throws {
|
||||
guard let metadata = content.userInfo["metadata"] as? [AnyHashable: Any] else {
|
||||
throw ParseNotificationPayloadError.missingMetadata("The notification has no metadata.")
|
||||
}
|
||||
|
||||
guard var avatarUrl = metadata["avatar"] as? String else {
|
||||
throw ParseNotificationPayloadError.missingAvatarUrl("The notification has no avatar.")
|
||||
}
|
||||
avatarUrl = getAttachmentUrl(for: avatarUrl)
|
||||
|
||||
let handle = INPersonHandle(value: "\(metadata["user_id"] ?? "")", type: .unknown)
|
||||
let avatar = INImage(url: URL(string: avatarUrl)!)
|
||||
let sender = INPerson(
|
||||
personHandle: handle,
|
||||
nameComponents: nil,
|
||||
displayName: content.title,
|
||||
image: avatar,
|
||||
contactIdentifier: nil,
|
||||
customIdentifier: nil
|
||||
)
|
||||
|
||||
if content.categoryIdentifier == "messaging.callStart" {
|
||||
let intent = createCallIntent(with: sender)
|
||||
donateInteraction(for: intent)
|
||||
let updatedContent = try request.content.updating(from: intent)
|
||||
contentHandler?(updatedContent)
|
||||
} else {
|
||||
let intent = createMessageIntent(with: sender, metadata: metadata, body: content.body)
|
||||
donateInteraction(for: intent)
|
||||
let updatedContent = try request.content.updating(from: intent)
|
||||
contentHandler?(updatedContent)
|
||||
}
|
||||
}
|
||||
|
||||
private func handleDefaultNotification(content: UNMutableNotificationContent) throws {
|
||||
guard let metadata = content.userInfo["metadata"] as? [AnyHashable: Any] else {
|
||||
throw ParseNotificationPayloadError.missingMetadata("The notification has no metadata.")
|
||||
}
|
||||
|
||||
if let imageIdentifier = metadata["image"] as? String {
|
||||
attachMedia(to: content, withIdentifier: imageIdentifier)
|
||||
} else if let avatarIdentifier = metadata["avatar"] as? String {
|
||||
attachMedia(to: content, withIdentifier: avatarIdentifier)
|
||||
}
|
||||
|
||||
contentHandler?(content)
|
||||
}
|
||||
|
||||
private func attachMedia(to content: UNMutableNotificationContent, withIdentifier identifier: String) {
|
||||
let attachmentUrl = getAttachmentUrl(for: identifier)
|
||||
if let url = URL(string: attachmentUrl), let attachment = try? UNNotificationAttachment(identifier: identifier, url: url) {
|
||||
content.attachments = [attachment]
|
||||
}
|
||||
}
|
||||
|
||||
private func createCallIntent(with sender: INPerson) -> INStartCallIntent {
|
||||
INStartCallIntent(
|
||||
callRecordFilter: nil,
|
||||
callRecordToCallBack: nil,
|
||||
audioRoute: .unknown,
|
||||
destinationType: .normal,
|
||||
contacts: [sender],
|
||||
callCapability: .unknown
|
||||
)
|
||||
}
|
||||
|
||||
private func createMessageIntent(with sender: INPerson, metadata: [AnyHashable: Any], body: String) -> INSendMessageIntent {
|
||||
INSendMessageIntent(
|
||||
recipients: nil,
|
||||
outgoingMessageType: .outgoingMessageText,
|
||||
content: body,
|
||||
speakableGroupName: nil,
|
||||
conversationIdentifier: "\(metadata["channel_id"] ?? "")",
|
||||
serviceName: nil,
|
||||
sender: sender,
|
||||
attachments: nil
|
||||
)
|
||||
}
|
||||
|
||||
private func donateInteraction(for intent: INIntent) {
|
||||
let interaction = INInteraction(intent: intent, response: nil)
|
||||
interaction.direction = .incoming
|
||||
interaction.donate(completion: nil)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user