183 lines
5.1 KiB
Swift
183 lines
5.1 KiB
Swift
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")
|
|
}
|