import Foundation /// Single decode target for anything that can deep-link into the app — push /// notification taps today (docs/api/push-notifications-integration-guide.md /// §6), in-app promo/discount banner taps tomorrow (same shape: a string-keyed /// dictionary naming a screen + its params). Adding a new destination means /// adding one case here + one branch in `ContentView.route(to:)` — never a new /// `NotificationCenter` name, `AppState` field, or observer pair. enum DeepLinkDestination: Equatable { /// `type: "order_status"` — the only push type Atomenta fixes today /// (§6), so it gets its own case instead of routing through `targetScreen`. case orderTracking(orderId: String, shortId: String?) /// Everything else, keyed by `targetScreen` — our own convention (§6 /// leaves this to us). Once a real screen exists for a given name (e.g. /// future promo/coupon "list of participating stores/products"), give it /// its own case; until then this carries the raw params so nothing is /// silently dropped. case screen(name: String, params: [String: String]) } /// Resolves the *navigation* half of a tapped push's payload. Pure and /// side-effect-free on purpose: easy to unit test, and reusable as-is for /// in-app banner taps once those exist. Campaign-open reporting (§6a) is a /// separate side effect, not a navigation target, and is not decided here — /// see `PushNotificationCoordinator.handleTap`. struct PushDeepLinkParser { static func parse(_ userInfo: [AnyHashable: Any]) -> DeepLinkDestination? { if let type = userInfo["type"] as? String, type == "order_status" { // Atomenta's real order-status push (PushNotificationService. // notifyOrderStatusUpdate) sends only `shortId`, never `orderId` — // `GET /api/public/orders/:orderId` accepts either as the lookup // key server-side, so `shortId` doubles as the id here too. let shortId = userInfo["shortId"] as? String guard let orderId = (userInfo["orderId"] as? String) ?? shortId else { return nil } return .orderTracking(orderId: orderId, shortId: shortId) } guard let targetScreen = userInfo["targetScreen"] as? String else { return nil } var params: [String: String] = [:] for (key, value) in userInfo { guard let key = key as? String, key != "type", key != "targetScreen" else { continue } if let stringValue = value as? String { params[key] = stringValue } } return .screen(name: targetScreen, params: params) } }