From 308d4417f3a9156a52680dacd221f4f01e72a3d3 Mon Sep 17 00:00:00 2001 From: Shahroz Khan Date: Wed, 25 Sep 2024 13:30:59 +0500 Subject: [PATCH 1/4] updated rendering flow --- .../messaginginapp/gist/GistEnvironment.kt | 2 +- .../gist/presentation/GistSdk.kt | 2 +- .../gist/presentation/engine/EngineWebView.kt | 129 +++++++++--------- .../state/InAppMessagingState.kt | 2 +- 4 files changed, 64 insertions(+), 71 deletions(-) diff --git a/messaginginapp/src/main/java/io/customer/messaginginapp/gist/GistEnvironment.kt b/messaginginapp/src/main/java/io/customer/messaginginapp/gist/GistEnvironment.kt index 8cdd7cd91..3ac131ff9 100644 --- a/messaginginapp/src/main/java/io/customer/messaginginapp/gist/GistEnvironment.kt +++ b/messaginginapp/src/main/java/io/customer/messaginginapp/gist/GistEnvironment.kt @@ -10,7 +10,7 @@ enum class GistEnvironment : GistEnvironmentEndpoints { DEV { override fun getGistQueueApiUrl() = "https://gist-queue-consumer-api.cloud.dev.gist.build" override fun getEngineApiUrl() = "https://engine.api.dev.gist.build" - override fun getGistRendererUrl() = "https://renderer.gist.build/2.0" + override fun getGistRendererUrl() = "https://renderer.gist.build/beta" }, LOCAL { diff --git a/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistSdk.kt b/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistSdk.kt index e76c9050d..351776a9b 100644 --- a/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistSdk.kt +++ b/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistSdk.kt @@ -25,7 +25,7 @@ interface GistProvider { class GistSdk( siteId: String, dataCenter: String, - environment: GistEnvironment = GistEnvironment.PROD + environment: GistEnvironment = GistEnvironment.DEV ) : GistProvider { private val inAppMessagingManager = SDKComponent.inAppMessagingManager private val state: InAppMessagingState diff --git a/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/engine/EngineWebView.kt b/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/engine/EngineWebView.kt index a40eb4586..331778990 100644 --- a/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/engine/EngineWebView.kt +++ b/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/engine/EngineWebView.kt @@ -5,7 +5,6 @@ import android.content.Context import android.graphics.Color import android.net.http.SslError import android.util.AttributeSet -import android.util.Base64 import android.webkit.SslErrorHandler import android.webkit.WebResourceError import android.webkit.WebResourceRequest @@ -23,7 +22,6 @@ import io.customer.messaginginapp.gist.data.model.engine.EngineWebConfiguration import io.customer.messaginginapp.gist.utilities.ElapsedTimer import io.customer.messaginginapp.state.InAppMessagingState import io.customer.sdk.core.di.SDKComponent -import java.io.UnsupportedEncodingException import java.util.Timer import java.util.TimerTask @@ -77,83 +75,78 @@ internal class EngineWebView @JvmOverloads constructor( @SuppressLint("SetJavaScriptEnabled") fun setup(configuration: EngineWebConfiguration) { setupTimeout() - val jsonString = Gson().toJson(configuration) - encodeToBase64(jsonString)?.let { options -> - elapsedTimer.start("Engine render for message: ${configuration.messageId}") - val messageUrl = - "${state.environment.getGistRendererUrl()}/index.html?options=$options" - logger.debug("Rendering message with URL: $messageUrl") - webView?.let { - it.loadUrl(messageUrl) - it.settings.javaScriptEnabled = true - it.settings.allowFileAccess = true - it.settings.allowContentAccess = true - it.settings.domStorageEnabled = true - it.settings.textZoom = 100 - it.setBackgroundColor(Color.TRANSPARENT) - - findViewTreeLifecycleOwner()?.lifecycle?.addObserver(this) ?: run { - logger.error("Lifecycle owner not found, attaching interface to WebView manually") - engineWebViewInterface.attach(webView = it) - } + elapsedTimer.start("Engine render for message: ${configuration.messageId}") + val messageData = mapOf("options" to configuration) + val jsonString = Gson().toJson(messageData) + val messageUrl = + "${state.environment.getGistRendererUrl()}/index.html" + logger.debug("Rendering message with URL: $messageUrl") + webView?.let { + it.settings.javaScriptEnabled = true + it.settings.allowFileAccess = true + it.settings.allowContentAccess = true + it.settings.domStorageEnabled = true + it.settings.textZoom = 100 + it.setBackgroundColor(Color.TRANSPARENT) + + findViewTreeLifecycleOwner()?.lifecycle?.addObserver(this) ?: run { + logger.error("Lifecycle owner not found, attaching interface to WebView manually") + engineWebViewInterface.attach(webView = it) + } - it.webViewClient = object : WebViewClient() { - override fun onPageFinished(view: WebView, url: String?) { - view.loadUrl("javascript:window.parent.postMessage = function(message) {window.${EngineWebViewInterface.JAVASCRIPT_INTERFACE_NAME}.postMessage(JSON.stringify(message))}") + it.webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView, url: String?) { + val script = """ + window.postMessage($jsonString, '*'); + """.trim() + view.evaluateJavascript(script) { result -> + logger.debug("JavaScript execution result: $result") } + } - override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { - return !url.startsWith("https://code.gist.build") - } + override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { + return !url.startsWith("https://code.gist.build") + } - override fun onReceivedError( - view: WebView?, - errorCod: Int, - description: String, - failingUrl: String? - ) { - listener?.error() - } + override fun onReceivedError( + view: WebView?, + errorCod: Int, + description: String, + failingUrl: String? + ) { + logger.error("Web resource error: $description") + listener?.error() + } - override fun onReceivedHttpError( - view: WebView?, - request: WebResourceRequest?, - errorResponse: WebResourceResponse? - ) { - listener?.error() - } + override fun onReceivedHttpError( + view: WebView?, + request: WebResourceRequest?, + errorResponse: WebResourceResponse? + ) { + logger.error("HTTP error: ${errorResponse?.reasonPhrase}") + listener?.error() + } - override fun onReceivedError( - view: WebView?, - request: WebResourceRequest?, - error: WebResourceError? - ) { - listener?.error() - } + override fun onReceivedError( + view: WebView?, + request: WebResourceRequest?, + error: WebResourceError? + ) { + logger.error("Web resource error: $error") + listener?.error() + } - override fun onReceivedSslError( - view: WebView?, - handler: SslErrorHandler?, - error: SslError? - ) { - listener?.error() - } + override fun onReceivedSslError( + view: WebView?, + handler: SslErrorHandler?, + error: SslError? + ) { + listener?.error() } } - } ?: run { - listener?.error() - } - } - private fun encodeToBase64(text: String): String? { - val data: ByteArray? - try { - data = text.toByteArray(charset("UTF-8")) - } catch (ex: UnsupportedEncodingException) { - logger.debug("Unsupported encoding exception") - return null + it.loadUrl(messageUrl) } - return Base64.encodeToString(data, Base64.URL_SAFE) } private fun setupTimeout() { diff --git a/messaginginapp/src/main/java/io/customer/messaginginapp/state/InAppMessagingState.kt b/messaginginapp/src/main/java/io/customer/messaginginapp/state/InAppMessagingState.kt index 1cdbe3021..40081db18 100644 --- a/messaginginapp/src/main/java/io/customer/messaginginapp/state/InAppMessagingState.kt +++ b/messaginginapp/src/main/java/io/customer/messaginginapp/state/InAppMessagingState.kt @@ -6,7 +6,7 @@ import io.customer.messaginginapp.gist.data.model.Message data class InAppMessagingState( val siteId: String = "", val dataCenter: String = "", - val environment: GistEnvironment = GistEnvironment.PROD, + val environment: GistEnvironment = GistEnvironment.DEV, val pollInterval: Long = 600_000L, val userId: String? = null, val currentRoute: String? = null, From c35c817a94b7295d437ade6068ac0f0922275a7a Mon Sep 17 00:00:00 2001 From: Shahroz Khan Date: Thu, 24 Oct 2024 12:36:30 +0500 Subject: [PATCH 2/4] added in-app persistence improvements --- messaginginapp/api/messaginginapp.api | 4 ++++ .../gist/presentation/GistView.kt | 6 ++++- .../gist/presentation/engine/EngineWebView.kt | 3 --- .../state/InAppMessageReducer.kt | 23 +++++++++++++++++-- .../state/InAppMessagingAction.kt | 16 +++++++++++++ .../state/InAppMessagingMiddlewares.kt | 17 +++++++++----- 6 files changed, 57 insertions(+), 12 deletions(-) diff --git a/messaginginapp/api/messaginginapp.api b/messaginginapp/api/messaginginapp.api index 0c4ea5402..5727ca994 100644 --- a/messaginginapp/api/messaginginapp.api +++ b/messaginginapp/api/messaginginapp.api @@ -402,6 +402,10 @@ public final class io/customer/messaginginapp/state/InAppMessagingAction$SetUser public fun toString ()Ljava/lang/String; } +public final class io/customer/messaginginapp/state/InAppMessagingActionKt { + public static final fun shouldMarkMessageAsShown (Lio/customer/messaginginapp/state/InAppMessagingAction;)Z +} + public final class io/customer/messaginginapp/state/InAppMessagingManager { public fun ()V public fun (Lio/customer/messaginginapp/gist/presentation/GistListener;)V diff --git a/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistView.kt b/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistView.kt index a35879f7f..d7af070a4 100644 --- a/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistView.kt +++ b/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistView.kt @@ -116,12 +116,16 @@ class GistView @JvmOverloads constructor( try { shouldLogAction = false logger.debug("Dismissing from system action: $action") - inAppMessagingManager.dispatch(InAppMessagingAction.DismissMessage(message = message, shouldLog = false)) val intent = Intent(Intent.ACTION_VIEW) intent.data = Uri.parse(action) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP startActivity(context, intent, null) + + // launch system action first otherwise there is a possibility + // that due to lifecycle change and message still being in queue to be displayed + // the message will be displayed again, putting GistActivity before the system action in stack + inAppMessagingManager.dispatch(InAppMessagingAction.DismissMessage(message = message, shouldLog = false)) } catch (e: ActivityNotFoundException) { logger.debug("System action not handled: $action") } diff --git a/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/engine/EngineWebView.kt b/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/engine/EngineWebView.kt index 331778990..173387177 100644 --- a/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/engine/EngineWebView.kt +++ b/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/engine/EngineWebView.kt @@ -114,7 +114,6 @@ internal class EngineWebView @JvmOverloads constructor( description: String, failingUrl: String? ) { - logger.error("Web resource error: $description") listener?.error() } @@ -123,7 +122,6 @@ internal class EngineWebView @JvmOverloads constructor( request: WebResourceRequest?, errorResponse: WebResourceResponse? ) { - logger.error("HTTP error: ${errorResponse?.reasonPhrase}") listener?.error() } @@ -132,7 +130,6 @@ internal class EngineWebView @JvmOverloads constructor( request: WebResourceRequest?, error: WebResourceError? ) { - logger.error("Web resource error: $error") listener?.error() } diff --git a/messaginginapp/src/main/java/io/customer/messaginginapp/state/InAppMessageReducer.kt b/messaginginapp/src/main/java/io/customer/messaginginapp/state/InAppMessageReducer.kt index f14e82038..ae7bcd138 100644 --- a/messaginginapp/src/main/java/io/customer/messaginginapp/state/InAppMessageReducer.kt +++ b/messaginginapp/src/main/java/io/customer/messaginginapp/state/InAppMessageReducer.kt @@ -12,20 +12,39 @@ val inAppMessagingReducer: Reducer = { state, action -> is InAppMessagingAction.ProcessMessageQueue -> state.copy(messagesInQueue = action.messages.toSet()) is InAppMessagingAction.SetPollingInterval -> state.copy(pollInterval = action.interval) - is InAppMessagingAction.DismissMessage -> state.copy(currentMessageState = MessageState.Dismissed(action.message)) is InAppMessagingAction.EngineAction.MessageLoadingFailed -> state.copy(currentMessageState = MessageState.Dismissed(action.message)) is InAppMessagingAction.LoadMessage -> state.copy(currentMessageState = MessageState.Loading(action.message)) is InAppMessagingAction.Reset -> InAppMessagingState(siteId = state.siteId, dataCenter = state.dataCenter, environment = state.environment) is InAppMessagingAction.DisplayMessage -> { action.message.queueId?.let { queueId -> + // If the message should be tracked shown when it is displayed, add the queueId to shownMessageQueueIds. + val shownMessageQueueIds = if (action.shouldMarkMessageAsShown()) { + state.shownMessageQueueIds + queueId + } else { + state.shownMessageQueueIds + } + state.copy( currentMessageState = MessageState.Displayed(action.message), - shownMessageQueueIds = state.shownMessageQueueIds + queueId, + shownMessageQueueIds = shownMessageQueueIds, messagesInQueue = state.messagesInQueue.filterNot { it.queueId == queueId }.toSet() ) } ?: state } + is InAppMessagingAction.DismissMessage -> { + var shownMessageQueueIds = state.shownMessageQueueIds + // If the message should be tracked shown when it is dismissed, add the queueId to shownMessageQueueIds. + if (action.shouldMarkMessageAsShown() && action.message.queueId != null) { + shownMessageQueueIds = shownMessageQueueIds + action.message.queueId + } + + state.copy( + currentMessageState = MessageState.Dismissed(action.message), + shownMessageQueueIds = shownMessageQueueIds + ) + } + else -> state } val changes = state.diff(newState) diff --git a/messaginginapp/src/main/java/io/customer/messaginginapp/state/InAppMessagingAction.kt b/messaginginapp/src/main/java/io/customer/messaginginapp/state/InAppMessagingAction.kt index 6d7b58dc7..b930c3ee3 100644 --- a/messaginginapp/src/main/java/io/customer/messaginginapp/state/InAppMessagingAction.kt +++ b/messaginginapp/src/main/java/io/customer/messaginginapp/state/InAppMessagingAction.kt @@ -24,3 +24,19 @@ sealed class InAppMessagingAction { object ClearMessageQueue : InAppMessagingAction() object Reset : InAppMessagingAction() } + +fun InAppMessagingAction.shouldMarkMessageAsShown(): Boolean { + return when (this) { + is InAppMessagingAction.DisplayMessage -> { + // Mark the message as shown if it's not persistent + !message.gistProperties.persistent + } + + is InAppMessagingAction.DismissMessage -> { + // Mark the message as shown if it's persistent and should be logged and dismissed via close action only + message.gistProperties.persistent && shouldLog && viaCloseAction + } + + else -> false + } +} diff --git a/messaginginapp/src/main/java/io/customer/messaginginapp/state/InAppMessagingMiddlewares.kt b/messaginginapp/src/main/java/io/customer/messaginginapp/state/InAppMessagingMiddlewares.kt index 0f8cd72a2..20f843882 100644 --- a/messaginginapp/src/main/java/io/customer/messaginginapp/state/InAppMessagingMiddlewares.kt +++ b/messaginginapp/src/main/java/io/customer/messaginginapp/state/InAppMessagingMiddlewares.kt @@ -45,18 +45,23 @@ internal fun gistLoggingMessageMiddleware() = middleware { } private fun handleMessageDismissal(action: InAppMessagingAction.DismissMessage, next: (Any) -> Any) { - if (action.shouldLog) { - if (action.viaCloseAction) { - SDKComponent.gistQueue.logView(action.message) - } + // Log message close only if message should be tracked as shown on dismiss action + if (action.shouldMarkMessageAsShown()) { + SDKComponent.logger.debug("Persistent message dismissed, logging view for message: ${action.message}, shouldLog: ${action.shouldLog}, viaCloseAction: ${action.viaCloseAction}") + SDKComponent.gistQueue.logView(action.message) + } else { + SDKComponent.logger.debug("Message dismissed, not logging view for message: ${action.message}, shouldLog: ${action.shouldLog}, viaCloseAction: ${action.viaCloseAction}") } next(action) } private fun handleMessageDisplay(action: InAppMessagingAction.DisplayMessage, next: (Any) -> Any) { - val gistProperties = action.message.gistProperties - if (!gistProperties.persistent) { + // Log message view only if message should be tracked as shown on display action + if (action.shouldMarkMessageAsShown()) { + SDKComponent.logger.debug("Message shown, logging view for message: ${action.message}") SDKComponent.gistQueue.logView(action.message) + } else { + SDKComponent.logger.debug("Persistent message shown, not logging view for message: ${action.message}") } next(action) } From b5db9da71c7e80bff491f13fd4dbd7563c9aeec4 Mon Sep 17 00:00:00 2001 From: Shahroz Khan Date: Thu, 24 Oct 2024 12:45:24 +0500 Subject: [PATCH 3/4] point to prod --- .../io/customer/messaginginapp/gist/presentation/GistSdk.kt | 2 +- .../io/customer/messaginginapp/state/InAppMessagingState.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistSdk.kt b/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistSdk.kt index 351776a9b..e76c9050d 100644 --- a/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistSdk.kt +++ b/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistSdk.kt @@ -25,7 +25,7 @@ interface GistProvider { class GistSdk( siteId: String, dataCenter: String, - environment: GistEnvironment = GistEnvironment.DEV + environment: GistEnvironment = GistEnvironment.PROD ) : GistProvider { private val inAppMessagingManager = SDKComponent.inAppMessagingManager private val state: InAppMessagingState diff --git a/messaginginapp/src/main/java/io/customer/messaginginapp/state/InAppMessagingState.kt b/messaginginapp/src/main/java/io/customer/messaginginapp/state/InAppMessagingState.kt index 40081db18..1cdbe3021 100644 --- a/messaginginapp/src/main/java/io/customer/messaginginapp/state/InAppMessagingState.kt +++ b/messaginginapp/src/main/java/io/customer/messaginginapp/state/InAppMessagingState.kt @@ -6,7 +6,7 @@ import io.customer.messaginginapp.gist.data.model.Message data class InAppMessagingState( val siteId: String = "", val dataCenter: String = "", - val environment: GistEnvironment = GistEnvironment.DEV, + val environment: GistEnvironment = GistEnvironment.PROD, val pollInterval: Long = 600_000L, val userId: String? = null, val currentRoute: String? = null, From 3208711d93a9e0e64ee8df8266553f46cae299ae Mon Sep 17 00:00:00 2001 From: Shahroz Khan Date: Sat, 26 Oct 2024 03:52:29 +0500 Subject: [PATCH 4/4] update version --- .../java/io/customer/messaginginapp/gist/GistEnvironment.kt | 4 ++-- .../messaginginapp/gist/presentation/engine/EngineWebView.kt | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/messaginginapp/src/main/java/io/customer/messaginginapp/gist/GistEnvironment.kt b/messaginginapp/src/main/java/io/customer/messaginginapp/gist/GistEnvironment.kt index 3ac131ff9..79e6d413f 100644 --- a/messaginginapp/src/main/java/io/customer/messaginginapp/gist/GistEnvironment.kt +++ b/messaginginapp/src/main/java/io/customer/messaginginapp/gist/GistEnvironment.kt @@ -10,7 +10,7 @@ enum class GistEnvironment : GistEnvironmentEndpoints { DEV { override fun getGistQueueApiUrl() = "https://gist-queue-consumer-api.cloud.dev.gist.build" override fun getEngineApiUrl() = "https://engine.api.dev.gist.build" - override fun getGistRendererUrl() = "https://renderer.gist.build/beta" + override fun getGistRendererUrl() = "https://renderer.gist.build/3.0" }, LOCAL { @@ -22,6 +22,6 @@ enum class GistEnvironment : GistEnvironmentEndpoints { PROD { override fun getGistQueueApiUrl() = "https://gist-queue-consumer-api.cloud.gist.build" override fun getEngineApiUrl() = "https://engine.api.gist.build" - override fun getGistRendererUrl() = "https://renderer.gist.build/2.0" + override fun getGistRendererUrl() = "https://renderer.gist.build/3.0" } } diff --git a/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/engine/EngineWebView.kt b/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/engine/EngineWebView.kt index 173387177..3da3185cd 100644 --- a/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/engine/EngineWebView.kt +++ b/messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/engine/EngineWebView.kt @@ -96,6 +96,7 @@ internal class EngineWebView @JvmOverloads constructor( it.webViewClient = object : WebViewClient() { override fun onPageFinished(view: WebView, url: String?) { + // post message to webview with the configuration data so that the message can be rendered val script = """ window.postMessage($jsonString, '*'); """.trim()