Skip to content

Commit

Permalink
✨ Include query params on deep links for preview or content on subseq…
Browse files Browse the repository at this point in the history
…uent api requests to load

handles localized preview with locale_id param
  • Loading branch information
iujames committed Jan 24, 2024
1 parent 5f0bfa0 commit b6ef6b1
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 18 deletions.
2 changes: 1 addition & 1 deletion Sources/AppcuesKit/Appcues.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 6 additions & 4 deletions Sources/AppcuesKit/Data/Networking/Endpoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
Expand Down
21 changes: 14 additions & 7 deletions Sources/AppcuesKit/Presentation/DeepLinkHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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" {
Expand Down Expand Up @@ -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
)
Expand Down Expand Up @@ -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?
Expand Down
24 changes: 19 additions & 5 deletions Sources/AppcuesKit/Presentation/ExperienceLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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, Error>) -> Void)?)
func load(
experienceID: String,
published: Bool,
queryItems: [URLQueryItem],
trigger: ExperienceTrigger,
completion: ((Result<Void, Error>) -> Void)?
)
}

@available(iOS 13.0, *)
Expand All @@ -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)
Expand All @@ -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, Error>) -> Void)?) {
func load(
experienceID: String,
published: Bool,
queryItems: [URLQueryItem],
trigger: ExperienceTrigger,
completion: ((Result<Void, Error>) -> 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,
Expand All @@ -57,13 +70,14 @@ internal class ExperienceLoader: ExperienceLoading {
}

self?.lastPreviewExperienceID = published ? nil : experienceID
self?.lastPreviewQueryItems = published ? nil : queryItems
}
}

@objc
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)
}
}

0 comments on commit b6ef6b1

Please sign in to comment.