Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package cloud.mindbox.mobile_sdk.di.modules

import cloud.mindbox.mobile_sdk.annotations.InternalMindboxApi
import cloud.mindbox.mobile_sdk.inapp.data.checkers.MaxInappsPerDayLimitChecker
import cloud.mindbox.mobile_sdk.inapp.data.checkers.MaxInappsPerSessionLimitChecker
import cloud.mindbox.mobile_sdk.inapp.data.checkers.MinIntervalBetweenShowsLimitChecker
Expand All @@ -25,6 +26,7 @@ import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.validators.InAppValidato
import cloud.mindbox.mobile_sdk.inapp.presentation.InAppMessageDelayedManager
import cloud.mindbox.mobile_sdk.inapp.presentation.MindboxNotificationManager
import cloud.mindbox.mobile_sdk.inapp.presentation.MindboxNotificationManagerImpl
import cloud.mindbox.mobile_sdk.inapp.presentation.view.BridgeMessage
import cloud.mindbox.mobile_sdk.managers.*
import cloud.mindbox.mobile_sdk.managers.MobileConfigSettingsManagerImpl
import cloud.mindbox.mobile_sdk.managers.RequestPermissionManager
Expand All @@ -37,6 +39,7 @@ import com.google.gson.Gson
import com.google.gson.GsonBuilder
import kotlinx.coroutines.Dispatchers

@OptIn(InternalMindboxApi::class)
internal fun DataModule(
appContextModule: AppContextModule,
apiModule: ApiModule
Expand Down Expand Up @@ -270,6 +273,23 @@ internal fun DataModule(

override val gson: Gson by lazy {
GsonBuilder()
.registerTypeAdapterFactory(
RuntimeTypeAdapterFactory
.of(
BridgeMessage::class.java,
BridgeMessage.TYPE_FIELD_NAME,
true
).registerSubtype(
BridgeMessage.Request::class.java,
BridgeMessage.TYPE_REQUEST
).registerSubtype(
BridgeMessage.Response::class.java,
BridgeMessage.TYPE_RESPONSE
).registerSubtype(
BridgeMessage.Error::class.java,
BridgeMessage.TYPE_ERROR
)
)
.registerTypeAdapterFactory(
RuntimeTypeAdapterFactory
.of(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package cloud.mindbox.mobile_sdk.inapp.data.validators

import cloud.mindbox.mobile_sdk.annotations.InternalMindboxApi
import cloud.mindbox.mobile_sdk.inapp.presentation.view.BridgeMessage
import cloud.mindbox.mobile_sdk.logger.mindboxLogW

@OptIn(InternalMindboxApi::class)
internal class BridgeMessageValidator : Validator<BridgeMessage?> {
override fun isValid(item: BridgeMessage?): Boolean {
item ?: return false

runCatching {
if (item.id.isBlank()) {
mindboxLogW("BridgeMessage id is empty")
return false
}

if (item.type !in listOf(
BridgeMessage.TYPE_REQUEST,
BridgeMessage.TYPE_RESPONSE,
BridgeMessage.TYPE_ERROR
)
) {
mindboxLogW("BridgeMessage type ${item.type} is not supported")
return false
}

if (item.action.name.isEmpty()) {
mindboxLogW("BridgeMessage action is empty")
return false
}

if (item.timestamp <= 0L) {
mindboxLogW("BridgeMessage timestamp must be positive")
return false
}

if (item.version > BridgeMessage.VERSION) {
mindboxLogW("BridgeMessage version ${item.version} is not supported")
return false
}
}.onFailure { error ->
mindboxLogW("BridgeMessage validation error: $error")
return false
}

return true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package cloud.mindbox.mobile_sdk.inapp.presentation.view

import cloud.mindbox.mobile_sdk.annotations.InternalMindboxApi
import cloud.mindbox.mobile_sdk.logger.mindboxLogW
import com.google.gson.annotations.SerializedName
import java.util.UUID

@InternalMindboxApi
public enum class WebViewAction {
@SerializedName("init")
INIT,

@SerializedName("ready")
READY,

@SerializedName("click")
CLICK,

@SerializedName("close")
CLOSE,

@SerializedName("hide")
HIDE,

@SerializedName("log")
LOG,

@SerializedName("alert")
ALERT,

@SerializedName("toast")
TOAST,
}

@InternalMindboxApi
public sealed class BridgeMessage {
public abstract val version: Int
public abstract val type: String
public abstract val action: WebViewAction
public abstract val payload: String?
public abstract val id: String
public abstract val timestamp: Long

public data class Request(
override val version: Int,
override val action: WebViewAction,
override val payload: String?,
override val id: String,
override val timestamp: Long,
override val type: String = TYPE_REQUEST,
) : BridgeMessage()

public data class Response(
override val version: Int,
override val action: WebViewAction,
override val payload: String?,
override val id: String,
override val timestamp: Long,
override val type: String = TYPE_RESPONSE,
) : BridgeMessage()

public data class Error(
override val version: Int,
override val action: WebViewAction,
override val payload: String?,
override val id: String,
override val timestamp: Long,
override val type: String = TYPE_ERROR,
) : BridgeMessage()

public companion object {
public const val VERSION: Int = 1
public const val EMPTY_PAYLOAD: String = "{}"
public const val TYPE_FIELD_NAME: String = "type"
public const val TYPE_REQUEST: String = "request"
public const val TYPE_RESPONSE: String = "response"
public const val TYPE_ERROR: String = "error"

public fun createAction(action: WebViewAction, payload: String): Request =
Request(
id = UUID.randomUUID().toString(),
version = VERSION,
action = action,
payload = payload,
timestamp = System.currentTimeMillis(),
)

public fun createResponseAction(message: Request, payload: String?): Response =
Response(
id = message.id,
version = message.version,
action = message.action,
payload = payload,
timestamp = System.currentTimeMillis(),
)

public fun createErrorAction(message: Request, payload: String?): Error =
Error(
id = message.id,
version = message.version,
action = message.action,
payload = payload,
timestamp = System.currentTimeMillis(),
)
}
}

@InternalMindboxApi
internal typealias BridgeMessageHandler = (BridgeMessage.Request) -> String

@InternalMindboxApi
internal typealias BridgeSuspendMessageHandler = suspend (BridgeMessage.Request) -> String

@InternalMindboxApi
internal class WebViewActionHandlers {

private val handlersByActionValue: MutableMap<WebViewAction, BridgeMessageHandler> = mutableMapOf()
private val suspendHandlersByActionValue: MutableMap<WebViewAction, BridgeSuspendMessageHandler> = mutableMapOf()

fun register(actionValue: WebViewAction, handler: BridgeMessageHandler) {
if (handlersByActionValue.containsKey(actionValue)) {
mindboxLogW("Handler for action $actionValue already registered")
}
handlersByActionValue[actionValue] = handler
}

fun registerSuspend(actionValue: WebViewAction, handler: BridgeSuspendMessageHandler) {
if (suspendHandlersByActionValue.containsKey(actionValue)) {
mindboxLogW("Suspend handler for action $actionValue already registered")
}
suspendHandlersByActionValue[actionValue] = handler
}

fun hasSuspendHandler(actionValue: WebViewAction): Boolean {
return suspendHandlersByActionValue.containsKey(actionValue)
}

fun handleRequest(message: BridgeMessage.Request): Result<String> {
return runCatching {
handlersByActionValue[message.action]?.invoke(message)
?: throw IllegalArgumentException("No handler for action ${message.action}")
}
}

suspend fun handleRequestSuspend(message: BridgeMessage.Request): Result<String> {
return runCatching {
suspendHandlersByActionValue[message.action]?.invoke(message)
?: throw IllegalArgumentException("No suspend handler for action ${message.action}")
}
}
}
Loading