From 71e3b2f10c93dc4ca3f43ae3896fa7eccc20a3a0 Mon Sep 17 00:00:00 2001 From: Ankmara <118185796+Ankmara@users.noreply.github.com> Date: Tue, 30 Jan 2024 16:52:01 +0100 Subject: [PATCH] Added data action type --- Documentation/APP_INBOX.md | 23 ++++++++++ Documentation/IN_APP_CONTENT_BLOCKS.md | 35 ++++++++++++++ Documentation/IN_APP_MESSAGES.md | 35 ++++++++++++++ .../Example/Resources/beefree-actiontype.png | Bin 0 -> 15277 bytes .../ExponeaSDK.xcodeproj/project.pbxproj | 4 ++ .../StaticInAppContentBlockView.swift | 17 +++++-- .../View/InAppMessageWebView.swift | 17 +++++-- .../InAppMessage/InAppMessagePayload.swift | 2 +- .../Classes/Others/HtmlNormalizer.swift | 43 ++++++++++++++++-- .../Classes/Others/WebActionsManager.swift | 35 +++++++------- .../Specs/Other/HtmlNormalizerSpec.swift | 42 +++++++++++++++++ 11 files changed, 221 insertions(+), 32 deletions(-) create mode 100644 ExponeaSDK/Example/Resources/beefree-actiontype.png diff --git a/Documentation/APP_INBOX.md b/Documentation/APP_INBOX.md index 25048b7b..c7e8af81 100644 --- a/Documentation/APP_INBOX.md +++ b/Documentation/APP_INBOX.md @@ -222,3 +222,26 @@ If you want to avoid to consider tracking, you may use `Exponea.shared.trackAppI To track an invoking of action, you should use method `Exponea.shared.trackAppInboxClick(MessageItemAction, MessageItem)` with clicked message action and data. The behaviour of `trackAppInboxClick` may be affected by the tracking consent feature, which in enabled mode considers the requirement of explicit consent for tracking. Read more in [tracking consent documentation](./TRACKING_CONSENT.md). If you want to avoid to consider tracking, you may use `Exponea.shared.trackAppInboxClickWithoutTrackingConsent` instead. This method will do track event ignoring tracking consent state. + +## Determine button action URL handling behaviour for HTML message + +Button action URLs are automatically processed by SDK based on URL like: if URL starts with `http` or `https`, action type is set to `browser`, else is set to `deep-link` value. To force behaviour based on your expectation, you can specify optional attribude `data-actiontype` with following values: + +* `browser` - for Web URL to open browser +* `deep-link` - for custom URL scheme and Universal Link to process it + +You can do it in HTML builder by inserting the param to specific action button as described in example below: + +```html +
+
Action
+
+``` + +> This atrribute is also supported for ` + Click me + +``` diff --git a/Documentation/IN_APP_CONTENT_BLOCKS.md b/Documentation/IN_APP_CONTENT_BLOCKS.md index 03d6bd80..791c1d59 100644 --- a/Documentation/IN_APP_CONTENT_BLOCKS.md +++ b/Documentation/IN_APP_CONTENT_BLOCKS.md @@ -266,3 +266,38 @@ class CustomView: UIViewController, InAppCbViewDelegate { ``` That is all, now your CustomView will receive all In-app Content Block data. + +## Determine button action URL handling behaviour for HTML message + +Button action URLs are automatically processed by SDK based on URL like: if URL starts with `http` or `https`, action type is set to `browser`, else is set to `deep-link` value. To force behaviour based on your expectation, you can specify optional attribude `data-actiontype` with following values: + +* `browser` - for Web URL to open browser +* `deep-link` - for custom URL scheme and Universal Link to process it + +You can do it in HTML builder by inserting the param to specific action button as described in example below: + +```html +
+
Action
+
+``` + +> This atrribute is also supported for ` + Click me + +``` + +You can do it in Visual builder as well as described in example below: + +Steps: + +1) Click on the button you want to setup a URL +2) On the right side in editor scroll down +3) Under "Attributes" section click on `ADD NEW ATTRIBUTE` +4) Select `data-actiontype` +5) Insert a value ( `browser` or `deep-link`) + +![Screenshot](/ExponeaSDK/Example/Resources/beefree-actiontype.png) diff --git a/Documentation/IN_APP_MESSAGES.md b/Documentation/IN_APP_MESSAGES.md index f10ffc95..83434d33 100644 --- a/Documentation/IN_APP_MESSAGES.md +++ b/Documentation/IN_APP_MESSAGES.md @@ -117,3 +117,38 @@ Method `trackInAppMessageClose` will track a 'close' event with 'interaction' fi > The behaviour of `trackInAppMessageClick` and `trackInAppMessageClose` may be affected by the tracking consent feature, which in enabled mode considers the requirement of explicit consent for tracking. Read more in [tracking consent documentation](./TRACKING_CONSENT.md). > Note: Invoking of `Exponea.anonymize` does fetch In-apps immediately but `Exponea.identifyCustomer` needs to be sent to backend successfully. The reason is to register customer IDs on backend properly to correctly assign an In-app messages. If you have set other then `Exponea.flushMode = FlushMode.IMMEDIATE` you need to call `Exponea.flushData()` to finalize `identifyCustomer` process and trigger a In-app messages fetch. + +## Determine button action URL handling behaviour for HTML message + +Button action URLs are automatically processed by SDK based on URL like: if URL starts with `http` or `https`, action type is set to `browser`, else is set to `deep-link` value. To force behaviour based on your expectation, you can specify optional attribude `data-actiontype` with following values: + +* `browser` - for Web URL to open browser +* `deep-link` - for custom URL scheme and Universal Link to process it + +You can do it in HTML builder by inserting the param to specific action button as described in example below: + +```html +
+
Action
+
+``` + +> This atrribute is also supported for ` + Click me + +``` + +You can do it in Visual builder as well as described in example below: + +Steps: + +1) Click on the button you want to setup a URL +2) On the right side in editor scroll down +3) Under "Attributes" section click on `ADD NEW ATTRIBUTE` +4) Select `data-actiontype` +5) Insert a value ( `browser` or `deep-link`) + +![Screenshot](/ExponeaSDK/Example/Resources/beefree-actiontype.png) diff --git a/ExponeaSDK/Example/Resources/beefree-actiontype.png b/ExponeaSDK/Example/Resources/beefree-actiontype.png new file mode 100644 index 0000000000000000000000000000000000000000..5f6e67fc921774b3d0f08c84c492a7c38fffb719 GIT binary patch literal 15277 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 InAppContentBlockActionType { - if action.actionUrl == "https://exponea.com/close_action" { - return .close - } - if action.actionUrl.starts(with: "http://") || action.actionUrl.starts(with: "https://") { + switch action.actionType { + case .browser: return .browser + case .deeplink: + return .deeplink + case .unknown: + if action.actionUrl == "https://exponea.com/close_action" { + return .close + } + if action.actionUrl.starts(with: "http://") || action.actionUrl.starts(with: "https://") { + return .browser + } + return .deeplink } - return .deeplink } // directly calls `contentReadyCompletion` with given contentReady flag diff --git a/ExponeaSDK/ExponeaSDK/Classes/InAppMessages/View/InAppMessageWebView.swift b/ExponeaSDK/ExponeaSDK/Classes/InAppMessages/View/InAppMessageWebView.swift index fddda075..5a4ae367 100644 --- a/ExponeaSDK/ExponeaSDK/Classes/InAppMessages/View/InAppMessageWebView.swift +++ b/ExponeaSDK/ExponeaSDK/Classes/InAppMessages/View/InAppMessageWebView.swift @@ -11,7 +11,7 @@ final class InAppMessageWebView: UIView, InAppMessageView { private var inAppContentBlocksManager: InAppContentBlocksManagerType = InAppContentBlocksManager.manager var normalizedPayload: NormalizedResult? - + var actionManager: WebActionManager? required init( @@ -133,17 +133,24 @@ final class InAppMessageWebView: UIView, InAppMessageView { private func toPayloadButton(_ action: ActionInfo) -> InAppMessagePayloadButton { InAppMessagePayloadButton( buttonText: action.buttonText, - rawButtonType: detectActionType(action.actionUrl.cleanedURL()!).rawValue, + rawButtonType: detectActionType(action).rawValue, buttonLink: action.actionUrl, buttonTextColor: nil, buttonBackgroundColor: nil ) } - private func detectActionType(_ url: URL) -> InAppMessageButtonType { - if url.scheme == "http" || url.scheme == "https" { + private func detectActionType(_ action: ActionInfo) -> InAppMessageButtonType { + switch action.actionType { + case .browser: return .browser + case .deeplink: + return .deeplink + case .unknown: + if action.actionUrl.cleanedURL()!.scheme == "http" || action.actionUrl.cleanedURL()!.scheme == "https" { + return .browser + } + return .deeplink } - return .deeplink } } diff --git a/ExponeaSDK/ExponeaSDK/Classes/Models/InAppMessage/InAppMessagePayload.swift b/ExponeaSDK/ExponeaSDK/Classes/Models/InAppMessage/InAppMessagePayload.swift index 584abbf7..a12e26f4 100644 --- a/ExponeaSDK/ExponeaSDK/Classes/Models/InAppMessage/InAppMessagePayload.swift +++ b/ExponeaSDK/ExponeaSDK/Classes/Models/InAppMessage/InAppMessagePayload.swift @@ -86,5 +86,5 @@ public struct InAppMessagePayloadButton: Codable, Equatable { public enum InAppMessageButtonType: String { case cancel case deeplink = "deep-link" - case browser = "browser" + case browser } diff --git a/ExponeaSDK/ExponeaSDK/Classes/Others/HtmlNormalizer.swift b/ExponeaSDK/ExponeaSDK/Classes/Others/HtmlNormalizer.swift index 6aeca644..fed5c8f8 100644 --- a/ExponeaSDK/ExponeaSDK/Classes/Others/HtmlNormalizer.swift +++ b/ExponeaSDK/ExponeaSDK/Classes/Others/HtmlNormalizer.swift @@ -12,6 +12,7 @@ public class HtmlNormalizer { private let closeButtonAttrDef = "data-actiontype='close'" private let closeButtonSelector = "[data-actiontype='close']" private let actionButtonAttr = "data-link" + private let dataLinkTypeAttr = "data-actiontype" private let datalinkButtonSelector = "[data-link]" private let anchorlinkButtonSelector = "a[href]" @@ -132,16 +133,31 @@ public class HtmlNormalizer { private func ensureActionButtons() throws -> [ActionInfo] { var result: [String: ActionInfo] = [:] + guard let document = document else { Exponea.logger.log(.warning, message: "[HTML] Document has not been initialized, no Action buttons") return [] } // collect 'data-link' first as it may update href try collectDataLinkButtons(document).forEach { action in - result[action.actionUrl] = action + var existingResult = result[action.actionUrl] + if existingResult == nil { + result[action.actionUrl] = ActionInfo(buttonText: action.buttonText, actionUrl: action.actionUrl, actionType: action.actionType) + } else { + existingResult?.actionUrl = action.actionUrl + existingResult?.buttonText = action.buttonText + result[action.actionUrl] = existingResult + } } try collectAnchorLinkButtons(document).forEach { action in - result[action.actionUrl] = action + var existingResult = result[action.actionUrl] + if existingResult == nil { + result[action.actionUrl] = ActionInfo(buttonText: action.buttonText, actionUrl: action.actionUrl, actionType: action.actionType) + } else { + existingResult?.actionUrl = action.actionUrl + existingResult?.buttonText = action.buttonText + result[action.actionUrl] = existingResult + } } return Array(result.values) } @@ -151,11 +167,12 @@ public class HtmlNormalizer { let anchorlinkButtons = try document.select(anchorlinkButtonSelector) for actionButton in anchorlinkButtons.array() { let targetAction = try actionButton.attr(hrefAttr) + let actionType: ActionType = .init(try actionButton.attr(dataLinkTypeAttr)) if targetAction.isEmpty { Exponea.logger.log(.error, message: "[HTML] Action button found but with empty action") continue } - result.append(ActionInfo(buttonText: try actionButton.text(), actionUrl: targetAction)) + result.append(ActionInfo(buttonText: try actionButton.text(), actionUrl: targetAction, actionType: actionType)) } return result } @@ -165,6 +182,7 @@ public class HtmlNormalizer { let datalinkButtons = try document.select(datalinkButtonSelector) for actionButton in datalinkButtons.array() { let targetAction = try actionButton.attr(actionButtonAttr) + let dataLinkType: ActionType = .init(try actionButton.attr(dataLinkTypeAttr)) if targetAction.isEmpty { Exponea.logger.log(.error, message: "[HTML] Action button found but with empty action") continue @@ -184,7 +202,7 @@ public class HtmlNormalizer { """) try actionButton.wrap("") } - result.append(ActionInfo(buttonText: try actionButton.text(), actionUrl: targetAction)) + result.append(ActionInfo(buttonText: try actionButton.text(), actionUrl: targetAction, actionType: dataLinkType)) } return result } @@ -555,6 +573,23 @@ public struct NormalizedResult { public struct ActionInfo { public var buttonText: String public var actionUrl: String + public var actionType: ActionType + + public init(buttonText: String, actionUrl: String, actionType: ActionType = .unknown) { + self.buttonText = buttonText + self.actionUrl = actionUrl + self.actionType = actionType + } +} + +public enum ActionType: String { + case unknown + case deeplink = "deep-link" + case browser + + init(_ type: String) { + self = .init(rawValue: type) ?? .unknown + } } public struct HtmlNormalizerConfig { diff --git a/ExponeaSDK/ExponeaSDK/Classes/Others/WebActionsManager.swift b/ExponeaSDK/ExponeaSDK/Classes/Others/WebActionsManager.swift index dee47ae8..a091c012 100644 --- a/ExponeaSDK/ExponeaSDK/Classes/Others/WebActionsManager.swift +++ b/ExponeaSDK/ExponeaSDK/Classes/Others/WebActionsManager.swift @@ -10,17 +10,18 @@ import Foundation import WebKit final class WebActionManager: NSObject, WKNavigationDelegate { - + // MARK: - Properties - + var htmlPayload: NormalizedResult? - + private var onCloseCallback: (() -> Void)? private var onActionCallback: ((ActionInfo) -> Void)? private var onErrorCallback: ((ExponeaError) -> Void)? - + private var onActionTypeCallback: ((String) -> Void)? + // MARK: - Init - + init( onCloseCallback: (() -> Void)? = nil, onActionCallback: ((ActionInfo) -> Void)? = nil, @@ -30,9 +31,9 @@ final class WebActionManager: NSObject, WKNavigationDelegate { self.onActionCallback = onActionCallback self.onErrorCallback = onErrorCallback } - + // MARK: - Methods - + func webView( _ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, @@ -55,7 +56,7 @@ final class WebActionManager: NSObject, WKNavigationDelegate { return true } else if isActionUrl(url) { guard let url = url, - let action = findActionByUrl(url) else { + var action = findActionByUrl(url) else { Exponea.logger.log(.error, message: "[HTML] Action URL \(url?.absoluteString ?? "") cannot be found as action") onErrorCallback?(ExponeaError.unknownError("Invalid Action URL - not found")) // anyway we define it as Action, so URL opening has to be prevented @@ -75,7 +76,7 @@ final class WebActionManager: NSObject, WKNavigationDelegate { } } - private extension WebActionManager { +private extension WebActionManager { func isBlankNav(_ url: URL?) -> Bool { url?.absoluteString == "about:blank" } @@ -86,14 +87,14 @@ final class WebActionManager: NSObject, WKNavigationDelegate { } return !isCloseAction(url) && findActionByUrl(url) != nil } - + func isCloseAction(_ url: URL?) -> Bool { guard let htmlPayload = htmlPayload else { return false } return url?.absoluteString == htmlPayload.closeActionUrl } - + func findActionByUrl(_ url: URL?) -> ActionInfo? { guard let url = url, let htmlPayload = htmlPayload else { @@ -103,7 +104,7 @@ final class WebActionManager: NSObject, WKNavigationDelegate { areEqualAsURLs(action.actionUrl, url.absoluteString) }) } - + /** Put URL().absoluteString here. WKWebView is returning a slash at the end of URL, so we need to compare it properly @@ -120,11 +121,11 @@ final class WebActionManager: NSObject, WKNavigationDelegate { let path2 = url2?.path == "/" ? "" : url2?.path let query2 = url2?.query return ( - scheme1 == scheme2 - && host1 == host2 - && path1 == path2 - && query1 == query2 + scheme1 == scheme2 + && host1 == host2 + && path1 == path2 + && query1 == query2 ) } - + } diff --git a/ExponeaSDK/ExponeaSDKTests/Specs/Other/HtmlNormalizerSpec.swift b/ExponeaSDK/ExponeaSDKTests/Specs/Other/HtmlNormalizerSpec.swift index 25667a47..07da7955 100644 --- a/ExponeaSDK/ExponeaSDKTests/Specs/Other/HtmlNormalizerSpec.swift +++ b/ExponeaSDK/ExponeaSDKTests/Specs/Other/HtmlNormalizerSpec.swift @@ -15,6 +15,48 @@ import ExponeaSDKShared final class HtmlNormalizerSpec: QuickSpec { override func spec() { + it("should find data link type - deeplink") { + let rawHtml = "" + + "
Action 1
" + + "" + let result = HtmlNormalizer(rawHtml).normalize() + expect(result.actions.contains(where: { $0.actionType == .deeplink })).to(beTrue()) + } + + it("should find data link type - browser") { + let rawHtml = "" + + "
Action 1
" + + "" + let result = HtmlNormalizer(rawHtml).normalize() + expect(result.actions.contains(where: { $0.actionType == .browser })).to(beTrue()) + } + + it("should find data link type - unknown") { + let rawHtml = "" + + "
Action 1
" + + "" + let result = HtmlNormalizer(rawHtml).normalize() + expect(result.actions.contains(where: { $0.actionType == .unknown })).to(beTrue()) + } + + it("should find data link type - unknown") { + let rawHtml = "" + + "
Action 1
" + + "Action 1" + + "" + let result = HtmlNormalizer(rawHtml).normalize() + expect(result.actions.contains(where: { $0.actionType == .deeplink })).to(beTrue()) + } + + it("should find data link type - browser and deep-link") { + let rawHtml = "" + + "
Action 1
" + + "
Action 1" + + "" + let result = HtmlNormalizer(rawHtml).normalize() + expect(result.actions.filter({ $0.actionType == .deeplink || $0.actionType == .browser }).count).to(equal(2)) + } + it("should find Close and Action url") { let rawHtml = "" + "
Close
" +