forked from customerio/customerio-ios
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: universal links deep links open host app (customerio#268)
- Loading branch information
1 parent
de16dc7
commit 29c95b5
Showing
9 changed files
with
258 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import Foundation | ||
#if canImport(UIKit) | ||
import UIKit | ||
#endif | ||
|
||
/* | ||
Because our codebase uses `#if canImport(UIKit)` to avoid being tightly coupled to iOS, this utility class exists to | ||
encapsulate all UIKit operations so our codebase doesn't need to have error-prone and boilerplate `#if ...UIKit...` code in many places. | ||
*/ | ||
public protocol UIKitWrapper: AutoMockable { | ||
func open(url: URL) | ||
func continueNSUserActivity(webpageURL: URL) -> Bool | ||
} | ||
|
||
// sourcery: InjectRegister = "UIKitWrapper" | ||
public class UIKitWrapperImpl: UIKitWrapper { | ||
public func open(url: URL) { | ||
#if canImport(UIKit) | ||
UIApplication.shared.open(url: url) | ||
#endif | ||
} | ||
|
||
public func continueNSUserActivity(webpageURL: URL) -> Bool { | ||
#if canImport(UIKit) | ||
guard isLinkValidNSUserActivityLink(webpageURL) else { | ||
return false | ||
} | ||
|
||
let openLinkInHostAppActivity = NSUserActivity(activityType: NSUserActivityTypeBrowsingWeb) | ||
openLinkInHostAppActivity.webpageURL = webpageURL | ||
|
||
let didHostAppHandleLink = UIApplication.shared.delegate?.application?(UIApplication.shared, continue: openLinkInHostAppActivity, restorationHandler: { _ in }) ?? false | ||
|
||
return didHostAppHandleLink | ||
#else | ||
return false | ||
#endif | ||
} | ||
} | ||
|
||
internal extension UIKitWrapperImpl { | ||
// When using `NSUserActivity.webpageURL`, only certain URL schemes are allowed. An exception will be thrown otherwise which is why we have this function. | ||
func isLinkValidNSUserActivityLink(_ url: URL) -> Bool { | ||
guard let schemeOfUrl = url.scheme else { | ||
return false | ||
} | ||
|
||
// All allowed schemes in docs: https://developer.apple.com/documentation/foundation/nsuseractivity/1418086-webpageurl | ||
let allowedSchemes = ["http", "https"] | ||
|
||
return allowedSchemes.contains(schemeOfUrl) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,48 @@ | ||
import Common | ||
import Foundation | ||
#if canImport(UIKit) | ||
import UIKit | ||
#endif | ||
|
||
protocol DeepLinkUtil: AutoMockable { | ||
func isLinkValidNSUserActivityLink(_ url: URL) -> Bool | ||
func handleDeepLink(_ deepLinkUrl: URL) | ||
} | ||
|
||
// sourcery: InjectRegister = "DeepLinkUtil" | ||
class DeepLinkUtilImpl: DeepLinkUtil { | ||
// When using `NSUserActivity.webpageURL`, only certain URL schemes are allowed. An exception will be thrown otherwise which is why we have this function. | ||
func isLinkValidNSUserActivityLink(_ url: URL) -> Bool { | ||
guard let schemeOfUrl = url.scheme else { | ||
return false | ||
} | ||
private let logger: Logger | ||
private let uiKit: UIKitWrapper | ||
|
||
init(logger: Logger, uiKitWrapper: UIKitWrapper) { | ||
self.logger = logger | ||
self.uiKit = uiKitWrapper | ||
} | ||
|
||
func handleDeepLink(_ deepLinkUrl: URL) { | ||
logger.info("Found a deep link inside of a push notification.") | ||
logger.debug("deep link found in push: \(deepLinkUrl)") | ||
|
||
// All allowed schemes in docs: https://developer.apple.com/documentation/foundation/nsuseractivity/1418086-webpageurl | ||
let allowedSchemes = ["http", "https"] | ||
/* | ||
There are 2 types of deep links: | ||
1. Universal Links which give URL format of a webpage using `http://` or `https://` | ||
2. App scheme which give URL format using a prototol other then `http://` or `https://`. | ||
return allowedSchemes.contains(schemeOfUrl) | ||
First, try to open the link inside of the host app. This is to keep compatability with Universal Links. | ||
Learn more of edge case: https://github.com/customerio/customerio-ios/issues/262 | ||
Fallback to opening the URL through a sytem call if: | ||
1. deep link is an app scheme URL | ||
2. Customer has not implemented the correct function in their host app to handle universal link: | ||
``` | ||
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool | ||
``` | ||
3. Customer returned `false` from ^^^ function. | ||
*/ | ||
let ifHandled = uiKit.continueNSUserActivity(webpageURL: deepLinkUrl) | ||
|
||
if !ifHandled { | ||
logger.debug("Opening deep link through system call. Deep link: \(deepLinkUrl)") | ||
uiKit.open(url: deepLinkUrl) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
@testable import Common | ||
import Foundation | ||
import SharedTests | ||
import XCTest | ||
|
||
class UIKitWrpperTest: UnitTest { | ||
private var uiKit: UIKitWrapperImpl! | ||
|
||
override func setUp() { | ||
super.setUp() | ||
|
||
uiKit = UIKitWrapperImpl() | ||
} | ||
|
||
// MARK: continueNSUserActivity | ||
|
||
func test_continueNSUserActivity_givenAppSchemeUrl_expectFalse() { | ||
let given = URL(string: "remote-habits://switch_workspace?site_id=AAA&api_key=BBB")! | ||
|
||
XCTAssertFalse(uiKit.isLinkValidNSUserActivityLink(given)) | ||
} | ||
|
||
func test_continueNSUserActivity_givenUniversalLinkUrl_expectTrue() { | ||
let given = URL(string: "https://remotehabits.com/switch_workspace?site_id=AAA&api_key=BBB")! | ||
|
||
XCTAssertTrue(uiKit.isLinkValidNSUserActivityLink(given)) | ||
} | ||
} |
Oops, something went wrong.