Skip to content

Commit

Permalink
✨ Add foreground hander verification to PushVerifier
Browse files Browse the repository at this point in the history
  • Loading branch information
mmaatttt committed May 6, 2024
1 parent 08598b0 commit 369de6e
Showing 1 changed file with 88 additions and 17 deletions.
105 changes: 88 additions & 17 deletions Sources/AppcuesKit/Presentation/Debugger/PushVerifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@
import UIKit
import Combine

// For mock objects
final class KeyedArchiver: NSKeyedArchiver {
override func decodeObject(forKey _: String) -> Any { "" }

deinit {
// Avoid a console warning
finishEncoding()
}
}

@available(iOS 13.0, *)
internal class PushVerifier {
enum ErrorMessage: Equatable, CustomStringConvertible {
Expand All @@ -20,6 +30,8 @@ internal class PushVerifier {
case noReceiveHandler
case multipleCompletions
case noSDKResponse
case noForegroundPresentationHandler
case noForegroundPresentationOption

case serverError(String)

Expand All @@ -45,14 +57,22 @@ internal class PushVerifier {
return "Error 7: Receive completion called too many times"
case .noSDKResponse:
return "Error 8: Receive response not passed to SDK"
case .noForegroundPresentationHandler:
return "Error 9: Foreground presentation handler not implemented"
case .noForegroundPresentationOption:
return "Note: Application is not configured to display foreground notifications"
case .serverError:
return "Error 9: Server Error"
return "Error 500: Server Error"
case .tokenMismatch:
return "Error 10: Unexpected result"
return "Error 100: Unexpected result"
case .responseInitFail:
return "Error 11: Unexpected result"
return "Error 101: Unexpected result"
}
}

var isWarning: Bool {
self == .noForegroundPresentationOption
}
}

static let title = "Push Notifications Configured"
Expand All @@ -68,8 +88,9 @@ internal class PushVerifier {
private var errors: [ErrorMessage] = [] {
didSet {
if !errors.isEmpty {
let status = errors.allSatisfy { $0.isWarning } ? StatusItem.Status.info : .unverified
subject.send(
StatusItem(status: .unverified, title: PushVerifier.title, subtitle: errors.map(\.description).joined(separator: "\n"))
StatusItem(status: status, title: PushVerifier.title, subtitle: errors.map(\.description).joined(separator: "\n"))
)
}
}
Expand Down Expand Up @@ -149,6 +170,24 @@ internal class PushVerifier {
return
}

verifyReceiveHandler(
token: token,
notificationCenter: notificationCenter,
notificationDelegate: notificationDelegate
)

verifyForegroundPresentationHandler(
token: token,
notificationCenter: notificationCenter,
notificationDelegate: notificationDelegate
)
}

private func verifyReceiveHandler(
token: String,
notificationCenter: UNUserNotificationCenter,
notificationDelegate: UNUserNotificationCenterDelegate
) {
guard let receiveHandler = notificationDelegate.userNotificationCenter(_:didReceive:withCompletionHandler:) else {
errors.append(.noReceiveHandler)
return
Expand Down Expand Up @@ -178,6 +217,34 @@ internal class PushVerifier {
}
}

private func verifyForegroundPresentationHandler(
token: String,
notificationCenter: UNUserNotificationCenter,
notificationDelegate: UNUserNotificationCenterDelegate
) {
guard let presentationHandler = notificationDelegate.userNotificationCenter(_:willPresent:withCompletionHandler:) else {
errors.append(.noForegroundPresentationHandler)
return
}

guard let mockNotification = UNNotification.mock(token: token) else {
errors.append(.responseInitFail)
return
}

presentationHandler(notificationCenter, mockNotification) { [weak self] options in
if #available(iOS 14.0, *) {
if !options.contains(.banner) {
self?.errors.append(.noForegroundPresentationOption)
}
} else {
if !options.contains(.alert) {
self?.errors.append(.noForegroundPresentationOption)
}
}
}
}

private func verifyServerComponents(token: String) {
let body = PushTest(
deviceID: storage.deviceID
Expand Down Expand Up @@ -226,21 +293,28 @@ private extension PushVerifier {
}

private extension UNNotificationResponse {
final class KeyedArchiver: NSKeyedArchiver {
override func decodeObject(forKey _: String) -> Any { "" }

deinit {
// Avoid a console warning
finishEncoding()
static func mock(
token: String,
actionIdentifier: String = UNNotificationDefaultActionIdentifier
) -> UNNotificationResponse? {
guard let response = UNNotificationResponse(coder: KeyedArchiver()),
let notification = UNNotification.mock(token: token) else {
return nil
}

response.setValue(notification, forKey: "notification")
response.setValue(actionIdentifier, forKey: "actionIdentifier")

return response
}
}

private extension UNNotification {
static func mock(
token: String,
actionIdentifier: String = UNNotificationDefaultActionIdentifier
) -> UNNotificationResponse? {
guard let response = UNNotificationResponse(coder: KeyedArchiver()),
let notification = UNNotification(coder: KeyedArchiver()) else {
) -> UNNotification? {
guard let notification = UNNotification(coder: KeyedArchiver()) else {
return nil
}

Expand All @@ -259,9 +333,6 @@ private extension UNNotificationResponse {
)
notification.setValue(request, forKey: "request")

response.setValue(notification, forKey: "notification")
response.setValue(actionIdentifier, forKey: "actionIdentifier")

return response
return notification
}
}

0 comments on commit 369de6e

Please sign in to comment.