Commit
- Loading branch information
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package io.customer.messaginginapp.gist | ||
|
||
interface GistEnvironmentEndpoints { | ||
fun getGistQueueApiUrl(): String | ||
fun getEngineApiUrl(): String | ||
fun getGistRendererUrl(): String | ||
} | ||
|
||
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" | ||
}, | ||
|
||
LOCAL { | ||
override fun getGistQueueApiUrl() = "http://queue.api.local.gist.build:86" | ||
override fun getEngineApiUrl() = "http://engine.api.local.gist.build:82" | ||
override fun getGistRendererUrl() = "https://renderer.gist.build/2.0" | ||
}, | ||
|
||
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" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package io.customer.messaginginapp.gist.data | ||
|
||
class NetworkUtilities { | ||
companion object { | ||
internal const val CIO_SITE_ID_HEADER = "X-CIO-Site-Id" | ||
internal const val USER_TOKEN_HEADER = "X-Gist-Encoded-User-Token" | ||
internal const val CIO_DATACENTER_HEADER = "X-CIO-Datacenter" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
package io.customer.messaginginapp.gist.data.listeners | ||
|
||
import android.util.Base64 | ||
import android.util.Log | ||
import io.customer.messaginginapp.gist.data.NetworkUtilities | ||
import io.customer.messaginginapp.gist.data.model.GistMessageProperties | ||
import io.customer.messaginginapp.gist.data.model.Message | ||
import io.customer.messaginginapp.gist.data.repository.GistQueueService | ||
import io.customer.messaginginapp.gist.presentation.GIST_TAG | ||
import io.customer.messaginginapp.gist.presentation.GistListener | ||
import io.customer.messaginginapp.gist.presentation.GistSdk | ||
import java.util.regex.PatternSyntaxException | ||
import kotlinx.coroutines.GlobalScope | ||
import kotlinx.coroutines.launch | ||
import okhttp3.OkHttpClient | ||
import okhttp3.Request | ||
import retrofit2.Retrofit | ||
import retrofit2.converter.gson.GsonConverterFactory | ||
|
||
class Queue : GistListener { | ||
|
||
private var localMessageStore: MutableList<Message> = mutableListOf() | ||
|
||
init { | ||
GistSdk.addListener(this) | ||
} | ||
|
||
private val gistQueueService by lazy { | ||
val httpClient: OkHttpClient = OkHttpClient.Builder() | ||
.addInterceptor { chain -> | ||
GistSdk.getUserToken()?.let { userToken -> | ||
val request: Request = chain.request().newBuilder() | ||
.addHeader(NetworkUtilities.CIO_SITE_ID_HEADER, GistSdk.siteId) | ||
.addHeader(NetworkUtilities.CIO_DATACENTER_HEADER, GistSdk.dataCenter) | ||
.addHeader( | ||
NetworkUtilities.USER_TOKEN_HEADER, | ||
// The NO_WRAP flag will omit all line terminators (i.e., the output will be on one long line). | ||
Base64.encodeToString(userToken.toByteArray(), Base64.NO_WRAP) | ||
) | ||
.build() | ||
|
||
chain.proceed(request) | ||
} ?: run { | ||
val request: Request = chain.request().newBuilder() | ||
.addHeader(NetworkUtilities.CIO_SITE_ID_HEADER, GistSdk.siteId) | ||
.addHeader(NetworkUtilities.CIO_DATACENTER_HEADER, GistSdk.dataCenter) | ||
.build() | ||
|
||
chain.proceed(request) | ||
} | ||
} | ||
.build() | ||
|
||
Retrofit.Builder() | ||
.baseUrl(GistSdk.gistEnvironment.getGistQueueApiUrl()) | ||
.addConverterFactory(GsonConverterFactory.create()) | ||
.client(httpClient) | ||
.build() | ||
.create(GistQueueService::class.java) | ||
} | ||
|
||
internal fun fetchUserMessagesFromLocalStore() { | ||
handleMessages(localMessageStore) | ||
} | ||
|
||
internal fun clearUserMessagesFromLocalStore() { | ||
localMessageStore.clear() | ||
} | ||
|
||
internal fun fetchUserMessages() { | ||
GlobalScope.launch { | ||
Check warning on line 71 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/listeners/Queue.kt GitHub Actions / Unit tests (messaginginapp)
Check warning on line 71 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/listeners/Queue.kt GitHub Actions / Android Lint (messaginginapp)
Check warning on line 71 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/listeners/Queue.kt GitHub Actions / instrumentation-test (java_layout)
Check warning on line 71 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/listeners/Queue.kt GitHub Actions / instrumentation-test (kotlin_compose)
|
||
try { | ||
Log.i(GIST_TAG, "Fetching user messages") | ||
val latestMessagesResponse = gistQueueService.fetchMessagesForUser() | ||
// If there's no change (304), move on. | ||
if (latestMessagesResponse.code() == 304) { return@launch } | ||
|
||
// To prevent us from showing expired / revoked messages, clear user messages from local queue. | ||
clearUserMessagesFromLocalStore() | ||
if (latestMessagesResponse.code() == 204) { | ||
// No content, don't do anything | ||
Log.i(GIST_TAG, "No messages found for user") | ||
} else if (latestMessagesResponse.isSuccessful) { | ||
Log.i( | ||
GIST_TAG, | ||
"Found ${latestMessagesResponse.body()?.count()} messages for user" | ||
) | ||
latestMessagesResponse.body()?.let { handleMessages(it) } | ||
} | ||
} catch (e: Exception) { | ||
Log.e( | ||
GIST_TAG, | ||
"Error fetching messages: ${e.message}" | ||
) | ||
} | ||
} | ||
} | ||
|
||
private fun handleMessages(messages: List<Message>) { | ||
for (message in messages) { | ||
processMessage(message) | ||
} | ||
} | ||
|
||
private fun processMessage(message: Message) { | ||
val gistProperties = GistMessageProperties.getGistProperties(message) | ||
gistProperties.routeRule?.let { routeRule -> | ||
try { | ||
if (!routeRule.toRegex().matches(GistSdk.currentRoute)) { | ||
Log.i( | ||
GIST_TAG, | ||
"Message route: $routeRule does not match current route: ${GistSdk.currentRoute}" | ||
) | ||
addMessageToLocalStore(message) | ||
return | ||
} | ||
} catch (e: PatternSyntaxException) { | ||
Log.i(GIST_TAG, "Invalid route rule regex: $routeRule") | ||
return | ||
} | ||
} | ||
gistProperties.elementId?.let { elementId -> | ||
Log.i( | ||
GIST_TAG, | ||
"Embedding message from queue with queue id: ${message.queueId}" | ||
) | ||
GistSdk.handleEmbedMessage(message, elementId) | ||
} ?: run { | ||
Log.i( | ||
GIST_TAG, | ||
"Showing message from queue with queue id: ${message.queueId}" | ||
) | ||
GistSdk.showMessage(message) | ||
} | ||
} | ||
|
||
internal fun logView(message: Message) { | ||
GlobalScope.launch { | ||
Check warning on line 138 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/listeners/Queue.kt GitHub Actions / Android Lint (messaginginapp)
|
||
try { | ||
if (message.queueId != null) { | ||
Log.i( | ||
GIST_TAG, | ||
"Logging view for user message: ${message.messageId}, with queue id: ${message.queueId}" | ||
) | ||
removeMessageFromLocalStore(message) | ||
gistQueueService.logUserMessageView(message.queueId) | ||
} else { | ||
Log.i(GIST_TAG, "Logging view for message: ${message.messageId}") | ||
gistQueueService.logMessageView(message.messageId) | ||
} | ||
} catch (e: Exception) { | ||
Log.e(GIST_TAG, "Failed to log message view: ${e.message}", e) | ||
} | ||
} | ||
} | ||
|
||
private fun addMessageToLocalStore(message: Message) { | ||
val localMessage = | ||
localMessageStore.find { localMessage -> localMessage.queueId == message.queueId } | ||
if (localMessage == null) { | ||
localMessageStore.add(message) | ||
} | ||
} | ||
|
||
private fun removeMessageFromLocalStore(message: Message) { | ||
localMessageStore.removeAll { it.queueId == message.queueId } | ||
} | ||
|
||
override fun onMessageShown(message: Message) { | ||
val gistProperties = GistMessageProperties.getGistProperties(message) | ||
if (gistProperties.persistent) { | ||
Log.i(GIST_TAG, "Persistent message shown: ${message.messageId}, skipping logging view") | ||
} else { | ||
logView(message) | ||
} | ||
} | ||
|
||
override fun embedMessage(message: Message, elementId: String) {} | ||
|
||
override fun onMessageDismissed(message: Message) {} | ||
|
||
override fun onError(message: Message) {} | ||
|
||
override fun onAction(message: Message, currentRoute: String, action: String, name: String) {} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package io.customer.messaginginapp.gist.data.model | ||
|
||
data class LogEvent( | ||
val name: String, | ||
val route: String, | ||
val instanceId: String, | ||
val queueId: String?, | ||
val campaignId: String?, | ||
val platform: String = "android" | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package io.customer.messaginginapp.gist.data.model | ||
import java.util.* | ||
|
||
enum class MessagePosition(val position: String) { | ||
TOP("top"), | ||
CENTER("center"), | ||
BOTTOM("bottom") | ||
} | ||
|
||
data class GistProperties( | ||
val routeRule: String?, | ||
val elementId: String?, | ||
val campaignId: String?, | ||
val position: MessagePosition, | ||
val persistent: Boolean | ||
) | ||
|
||
data class Message( | ||
val messageId: String = "", | ||
val instanceId: String = UUID.randomUUID().toString(), | ||
val queueId: String? = null, | ||
val properties: Map<String, Any?>? = null | ||
) | ||
|
||
class GistMessageProperties { | ||
companion object { | ||
fun getGistProperties(message: Message): GistProperties { | ||
var routeRule: String? = null | ||
var elementId: String? = null | ||
var campaignId: String? = null | ||
var position: MessagePosition = MessagePosition.CENTER | ||
var persistent = false | ||
|
||
message.properties?.let { properties -> | ||
properties["gist"]?.let { gistProperties -> | ||
(gistProperties as Map<String, Any?>).let { gistProperties -> | ||
Check warning on line 36 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/model/Message.kt GitHub Actions / Android Lint (messaginginapp)
|
||
gistProperties["routeRuleAndroid"]?.let { rule -> | ||
(rule as String).let { stringRule -> | ||
routeRule = stringRule | ||
} | ||
} | ||
gistProperties["campaignId"]?.let { id -> | ||
(id as String).let { stringId -> | ||
campaignId = stringId | ||
} | ||
} | ||
gistProperties["elementId"]?.let { id -> | ||
(id as String).let { stringId -> | ||
elementId = stringId | ||
} | ||
} | ||
gistProperties["position"]?.let { messagePosition -> | ||
(messagePosition as String).let { stringPosition -> | ||
position = MessagePosition.valueOf(stringPosition.uppercase()) | ||
} | ||
} | ||
gistProperties["persistent"]?.let { id -> | ||
(id as Boolean).let { persistentValue -> | ||
persistent = persistentValue | ||
} | ||
} | ||
} | ||
} | ||
} | ||
return GistProperties(routeRule = routeRule, elementId = elementId, campaignId = campaignId, position = position, persistent = persistent) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package io.customer.messaginginapp.gist.data.model.engine | ||
|
||
internal data class EngineWebConfiguration( | ||
val siteId: String, | ||
val dataCenter: String, | ||
val messageId: String, | ||
val instanceId: String, | ||
val endpoint: String, | ||
val livePreview: Boolean = false, | ||
val properties: Map<String, Any?>? = null | ||
) |