// // UniversalLinks.swift // KeymanEngine // // Created by Joshua Horton on 7/27/20. // Copyright © 2020 SIL International. All rights reserved. // import Foundation public class UniversalLinks { public struct ParsedKeyboardInstallLink { public let keyboard_id: String public let lang_id: String? public var packageKey: KeymanPackage.Key { return KeymanPackage.Key(id: keyboard_id, type: .keyboard) } } // The standard technique for launching URLs externally is dependent upon being // within an app, not in an app-extension. Furthermore, that distinction is made // at bundle compile-time... so we simply CAN'T condition on that within the framework. // It's thus up to the app to define this method-field when desired. public static var externalLinkLauncher: ((URL) -> Void)? = nil // e.g. https://keyman.com/keyboards/install/foo private static let KEYBOARD_INSTALL_LINK_REGEX = try! NSRegularExpression(pattern: "^http(?:s)?://keyman(?:-staging)?\\.com(?:\\.local)?/keyboards/install/([^?/]+)(?:\\?(.+))?$") // e.g. http://keyman.com.local/keyboards/foo private static let KEYBOARD_MATCH_ROOT_REGEX = try! NSRegularExpression(pattern: "^http(?:s)?://keyman(?:-staging)?\\.com(?:\\.local)?/keyboards([/?].*)?$"); // e.g. https://keyman-staging.com/go/windows/14.0/download-keyboards?version=14.0.146.0 private static let KEYBOARD_MATCH_GO_REGEX = try! NSRegularExpression(pattern: "^http(?:s)?://keyman(?:-staging)?\\.com(?:\\.local)?/go/windows/[^/]+/download-keyboards") public static func tryParseKeyboardInstallLink(_ link: URL) -> ParsedKeyboardInstallLink? { let linkString = link.absoluteString // If it matches the format for the Keyboard Universal Link URL Pattern... // (see https://docs.google.com/document/d/1rhgMeJlCdXCi6ohPb_CuyZd0PZMoSzMqGpv1A8cMFHY/edit?ts=5f11cb13#heading=h.qw7pas2adckj) if let match = KEYBOARD_INSTALL_LINK_REGEX.firstMatch(in: linkString, options: [], range: NSRange(location: 0, length: linkString.utf16.count)) { let keyboard_id_range = Range(match.range(at: 1), in: linkString)! let keyboard_id = String(linkString[keyboard_id_range]) var lang_id: String? = nil let urlComponents = URLComponents(string: linkString)! if let lang_id_component = urlComponents.queryItems?.first(where: { $0.name == "bcp47" }) { lang_id = lang_id_component.value } return ParsedKeyboardInstallLink(keyboard_id: keyboard_id, lang_id: lang_id) } else { return nil } } /** * Returns `true` for links that should be opened externally, rather than in an app-hosted WebView. * `false` is returned for links we consider to be 'internal' within the Keyman ecosystem. */ public static func isExternalLink(_ link: URL) -> Bool { let linkString = link.absoluteString let linkRange = NSRange(location: 0, length: linkString.utf16.count) // Case 1: the link captured for keyboard search if let _ = tryParseKeyboardInstallLink(link) { return false } else if let _ = KEYBOARD_MATCH_ROOT_REGEX.firstMatch(in: linkString, options: [], range: linkRange) { return false } else if let _ = KEYBOARD_MATCH_GO_REGEX.firstMatch(in: linkString, options: [], range: linkRange) { return false } else if linkString.starts(with: "keyman:") { return false } return true } }