From b6ef6b17e3a022b314df950f35b1e840ac4155e7 Mon Sep 17 00:00:00 2001 From: James Ellis Date: Fri, 12 Jan 2024 16:32:37 -0500 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Include=20query=20params=20on=20dee?= =?UTF-8?q?p=20links=20for=20preview=20or=20content=20on=20subsequent=20ap?= =?UTF-8?q?i=20requests=20to=20load=20handles=20localized=20preview=20with?= =?UTF-8?q?=20locale=5Fid=20param?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/AppcuesKit/Appcues.swift | 2 +- .../AppcuesKit/Data/Networking/Endpoint.swift | 10 ++++---- .../AppcuesLaunchExperienceAction.swift | 2 +- .../Presentation/DeepLinkHandler.swift | 21 ++++++++++------ .../Presentation/ExperienceLoader.swift | 24 +++++++++++++++---- 5 files changed, 41 insertions(+), 18 deletions(-) diff --git a/Sources/AppcuesKit/Appcues.swift b/Sources/AppcuesKit/Appcues.swift index c5759c147..746fce7f1 100644 --- a/Sources/AppcuesKit/Appcues.swift +++ b/Sources/AppcuesKit/Appcues.swift @@ -218,7 +218,7 @@ public class Appcues: NSObject { return } - experienceLoader.load(experienceID: experienceID, published: true, trigger: .showCall) { result in + experienceLoader.load(experienceID: experienceID, published: true, queryItems: [], trigger: .showCall) { result in switch result { case .success: completion?(true, nil) diff --git a/Sources/AppcuesKit/Data/Networking/Endpoint.swift b/Sources/AppcuesKit/Data/Networking/Endpoint.swift index f250b3888..9392cfc05 100644 --- a/Sources/AppcuesKit/Data/Networking/Endpoint.swift +++ b/Sources/AppcuesKit/Data/Networking/Endpoint.swift @@ -12,8 +12,8 @@ import Foundation internal enum APIEndpoint: Endpoint { case activity(userID: String) case qualify(userID: String) - case content(experienceID: String) - case preview(experienceID: String) + case content(experienceID: String, queryItems: [URLQueryItem]) + case preview(experienceID: String, queryItems: [URLQueryItem]) case health /// URL fragments that that are appended to the `Config.apiHost` to make the URL for a network request. @@ -25,15 +25,17 @@ internal enum APIEndpoint: Endpoint { components.path = "/v1/accounts/\(config.accountID)/users/\(userID)/activity" case let .qualify(userID): components.path = "/v1/accounts/\(config.accountID)/users/\(userID)/qualify" - case let .content(experienceID): + case let .content(experienceID, queryItems): components.path = "/v1/accounts/\(config.accountID)/users/\(storage.userID)/experience_content/\(experienceID)" - case let .preview(experienceID): + components.queryItems = queryItems + case let .preview(experienceID, queryItems): // optionally include the userID, if one exists, to allow for a personalized preview capability if storage.userID.isEmpty { components.path = "/v1/accounts/\(config.accountID)/experience_preview/\(experienceID)" } else { components.path = "/v1/accounts/\(config.accountID)/users/\(storage.userID)/experience_preview/\(experienceID)" } + components.queryItems = queryItems case .health: components.path = "/healthz" } diff --git a/Sources/AppcuesKit/Presentation/Actions/Appcues/AppcuesLaunchExperienceAction.swift b/Sources/AppcuesKit/Presentation/Actions/Appcues/AppcuesLaunchExperienceAction.swift index 488859373..3de83357a 100644 --- a/Sources/AppcuesKit/Presentation/Actions/Appcues/AppcuesLaunchExperienceAction.swift +++ b/Sources/AppcuesKit/Presentation/Actions/Appcues/AppcuesLaunchExperienceAction.swift @@ -53,7 +53,7 @@ internal class AppcuesLaunchExperienceAction: AppcuesExperienceAction { // that launches another flow from a button, for example. let trigger = self.trigger ?? launchExperienceTrigger(appcues) - experienceLoading.load(experienceID: experienceID, published: true, trigger: trigger) { _ in + experienceLoading.load(experienceID: experienceID, published: true, queryItems: [], trigger: trigger) { _ in completion() } } diff --git a/Sources/AppcuesKit/Presentation/DeepLinkHandler.swift b/Sources/AppcuesKit/Presentation/DeepLinkHandler.swift index 7c9c0e9ff..24f4e54c3 100644 --- a/Sources/AppcuesKit/Presentation/DeepLinkHandler.swift +++ b/Sources/AppcuesKit/Presentation/DeepLinkHandler.swift @@ -17,8 +17,8 @@ internal protocol DeepLinkHandling: AnyObject { internal class DeepLinkHandler: DeepLinkHandling { enum Action: Hashable { - case preview(experienceID: String) // preview for draft content - case show(experienceID: String) // published content + case preview(experienceID: String, queryItems: [URLQueryItem]) // preview for draft content + case show(experienceID: String, queryItems: [URLQueryItem]) // published content case debugger(destination: DebugDestination?) case verifyInstall(id: String) case captureScreen(token: String) @@ -28,7 +28,7 @@ internal class DeepLinkHandler: DeepLinkHandling { guard isValidScheme, url.host == "sdk" else { return nil } // supported paths: - // appcues-{app_id}://sdk/experience_preview/{experience_id} + // appcues-{app_id}://sdk/experience_preview/{experience_id}?locale_id={localeID} // appcues-{app_id}://sdk/experience_content/{experience_id} // appcues-{app_id}://sdk/debugger // appcues-{app_id}://sdk/debugger/fonts @@ -38,10 +38,10 @@ internal class DeepLinkHandler: DeepLinkHandling { let pathTokens = url.path.split(separator: "/").map { String($0) } if pathTokens.count == 2, pathTokens[0] == "experience_preview" { - self = .preview(experienceID: pathTokens[1]) + self = .preview(experienceID: pathTokens[1], queryItems: url.queryItems) } else if pathTokens.count == 2, pathTokens[0] == "experience_content", isSessionActive { // can only show content via deep link when a session is active - self = .show(experienceID: pathTokens[1]) + self = .show(experienceID: pathTokens[1], queryItems: url.queryItems) } else if pathTokens.count >= 1, pathTokens[0] == "debugger" { self = .debugger(destination: DebugDestination(pathToken: pathTokens[safe: 1])) } else if pathTokens.count == 2, pathTokens[0] == "verify" { @@ -108,17 +108,19 @@ internal class DeepLinkHandler: DeepLinkHandling { private func handle(action: Action) { switch action { - case .preview(let experienceID): + case let .preview(experienceID, queryItems): container?.resolve(ExperienceLoading.self).load( experienceID: experienceID, published: false, + queryItems: queryItems, trigger: .preview, completion: previewCompletion ) - case .show(let experienceID): + case let .show(experienceID, queryItems): container?.resolve(ExperienceLoading.self).load( experienceID: experienceID, published: true, + queryItems: queryItems, trigger: .deepLink, completion: nil ) @@ -188,6 +190,11 @@ public extension Appcues { } private extension URL { + var queryItems: [URLQueryItem] { + URLComponents(url: self, resolvingAgainstBaseURL: false)? + .queryItems ?? [] + } + func queryValue(for name: String) -> String? { URLComponents(url: self, resolvingAgainstBaseURL: false)? .queryItems? diff --git a/Sources/AppcuesKit/Presentation/ExperienceLoader.swift b/Sources/AppcuesKit/Presentation/ExperienceLoader.swift index b33eb1f5e..127a2ddc6 100644 --- a/Sources/AppcuesKit/Presentation/ExperienceLoader.swift +++ b/Sources/AppcuesKit/Presentation/ExperienceLoader.swift @@ -10,7 +10,13 @@ import Foundation @available(iOS 13.0, *) internal protocol ExperienceLoading: AnyObject { - func load(experienceID: String, published: Bool, trigger: ExperienceTrigger, completion: ((Result) -> Void)?) + func load( + experienceID: String, + published: Bool, + queryItems: [URLQueryItem], + trigger: ExperienceTrigger, + completion: ((Result) -> Void)? + ) } @available(iOS 13.0, *) @@ -24,6 +30,7 @@ internal class ExperienceLoader: ExperienceLoading { /// Store the experience ID loaded (only if previewing) so that it can be refreshed. private var lastPreviewExperienceID: String? + private var lastPreviewQueryItems: [URLQueryItem]? init(container: DIContainer) { self.config = container.resolve(Appcues.Config.self) @@ -35,11 +42,17 @@ internal class ExperienceLoader: ExperienceLoading { notificationCenter.addObserver(self, selector: #selector(refreshPreview), name: .shakeToRefresh, object: nil) } - func load(experienceID: String, published: Bool, trigger: ExperienceTrigger, completion: ((Result) -> Void)?) { + func load( + experienceID: String, + published: Bool, + queryItems: [URLQueryItem], + trigger: ExperienceTrigger, + completion: ((Result) -> Void)? + ) { let endpoint = published ? - APIEndpoint.content(experienceID: experienceID) : - APIEndpoint.preview(experienceID: experienceID) + APIEndpoint.content(experienceID: experienceID, queryItems: queryItems) : + APIEndpoint.preview(experienceID: experienceID, queryItems: queryItems) networking.get( from: endpoint, @@ -57,6 +70,7 @@ internal class ExperienceLoader: ExperienceLoading { } self?.lastPreviewExperienceID = published ? nil : experienceID + self?.lastPreviewQueryItems = published ? nil : queryItems } } @@ -64,6 +78,6 @@ internal class ExperienceLoader: ExperienceLoading { private func refreshPreview(notification: Notification) { guard let experienceID = lastPreviewExperienceID else { return } - load(experienceID: experienceID, published: false, trigger: .preview, completion: nil) + load(experienceID: experienceID, published: false, queryItems: lastPreviewQueryItems ?? [], trigger: .preview, completion: nil) } }