import Foundation import os /** * Deep Link Router — Swift * Handles routing from push notification deep links to app screens */ public struct BLDeepLinkRoute { public let screen: String public let params: [String: String] public init(screen: String, params: [String: String] = [:]) { self.screen = screen self.params = params } } public typealias BLDeepLinkHandler = (BLDeepLinkRoute) -> Void /** * Deep Link Router class */ @available(iOS 15.0, *) public class BLDeepLinkRouter { private var handlers: [String: BLDeepLinkHandler] = [:] private var fallbackHandler: BLDeepLinkHandler? public init() {} /** * Register a handler for a specific screen */ public func register(screen: String, handler: @escaping BLDeepLinkHandler) { handlers[screen] = handler } /** * Set a fallback handler for unregistered screens */ public func setFallback(handler: @escaping BLDeepLinkHandler) { fallbackHandler = handler } /** * Parse a deep link URL and extract route */ public func parseDeepLink(_ urlString: String) -> BLDeepLinkRoute? { guard let url = URL(string: urlString) else { return nil } // Handle app-specific URLs: myapp://screen/params if url.scheme != "http" && url.scheme != "https" { let pathComponents = url.pathComponents.filter { $0 != "/" && !$0.isEmpty } let screen = pathComponents.first ?? "home" var params: [String: String] = [:] if let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems { for item in queryItems { if let value = item.value { params[item.name] = value } } } return BLDeepLinkRoute(screen: screen, params: params) } // Handle web URLs with deep link params if let components = URLComponents(url: url, resolvingAgainstBaseURL: false), let dlParam = components.queryItems?.first(where: { $0.name == "dl" })?.value { return parseDeepLink(dlParam) } // Handle path-based routing: /screen/params let pathComponents = url.pathComponents.filter { $0 != "/" && !$0.isEmpty } if !pathComponents.isEmpty { let screen = pathComponents[0] var params: [String: String] = [:] if let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems { for item in queryItems { if let value = item.value { params[item.name] = value } } } return BLDeepLinkRoute(screen: screen, params: params) } return nil } /** * Handle a deep link route */ @discardableResult public func handle(_ route: BLDeepLinkRoute) -> Bool { if let handler = handlers[route.screen] { handler(route) return true } if let fallback = fallbackHandler { fallback(route) return true } Logger.deepLink.warning("No handler for screen: \(route.screen)") return false } /** * Process a deep link URL end-to-end */ @discardableResult public func process(_ urlString: String) -> Bool { guard let route = parseDeepLink(urlString) else { Logger.deepLink.warning("Failed to parse deep link: \(urlString)") return false } return handle(route) } } /** * Create a broadcast deep link URL */ public func createBroadcastDeepLink( baseURL: String, screen: String, params: [String: String] = [:], broadcastId: String? = nil ) -> String? { guard var components = URLComponents(string: baseURL) else { return nil } components.path = "/\(screen)" var queryItems: [URLQueryItem] = params.map { URLQueryItem(name: $0.key, value: $0.value) } if let broadcastId = broadcastId { queryItems.append(URLQueryItem(name: "broadcastId", value: broadcastId)) } if !queryItems.isEmpty { components.queryItems = queryItems } return components.string } /** * Common deep link screens */ public struct BLDeepLinkScreens { // Broadcasts public static let broadcast = "broadcast" public static let announcements = "announcements" // Surveys public static let survey = "survey" public static let surveyList = "surveys" // Product-specific public static let settings = "settings" public static let profile = "profile" public static let upgrade = "upgrade" public static let support = "support" // Fallback public static let home = "home" } // Logger extension @available(iOS 15.0, *) extension Logger { static let deepLink = Logger(subsystem: "com.bytelyst.platform", category: "DeepLink") }