Coverage Summary for Class: MindboxWebViewLinkRouter (cloud.mindbox.mobile_sdk.inapp.presentation.view)

Class Method, % Branch, % Line, % Instruction, %
MindboxWebViewLinkRouter 100% (11/11) 76.1% (35/46) 90.8% (59/65) 89.1% (416/467)
MindboxWebViewLinkRouter$Companion
Total 100% (11/11) 76.1% (35/46) 90.8% (59/65) 89.1% (416/467)


 package cloud.mindbox.mobile_sdk.inapp.presentation.view
 
 import android.content.ActivityNotFoundException
 import android.content.Context
 import android.content.Intent
 import android.net.Uri
 import androidx.core.net.toUri
 import cloud.mindbox.mobile_sdk.logger.mindboxLogW
 import com.google.gson.JsonParser
 
 internal interface WebViewLinkRouter {
     fun executeOpenLink(request: String?): Result<String>
 }
 
 internal class MindboxWebViewLinkRouter(
     private val context: Context,
 ) : WebViewLinkRouter {
 
     companion object {
         private const val SCHEME_HTTP = "http"
         private const val SCHEME_HTTPS = "https"
         private const val SCHEME_INTENT = "intent"
         private const val SCHEME_TEL = "tel"
         private const val SCHEME_MAILTO = "mailto"
         private const val SCHEME_SMS = "sms"
         private const val KEY_URL = "url"
         private val BLOCKED_SCHEMES: Set<String> = setOf("javascript", "file", "data", "blob")
         private const val ERROR_MISSING_URL = "Invalid payload: missing or empty 'url' field"
     }
 
     override fun executeOpenLink(request: String?): Result<String> {
         return runCatching {
             val url: String = extractTargetUrl(request)
             val parsedUri = parseUrl(url)
             routeByScheme(
                 parsedUri = parsedUri,
                 targetUrl = url,
             )
         }
     }
 
     private fun extractTargetUrl(request: String?): String {
         if (request.isNullOrBlank()) {
             throw IllegalStateException(ERROR_MISSING_URL)
         }
         val parsedJsonElement = runCatching { JsonParser.parseString(request) }.getOrNull()
             ?: throw IllegalStateException(ERROR_MISSING_URL)
         if (!parsedJsonElement.isJsonObject) {
             throw IllegalStateException(ERROR_MISSING_URL)
         }
         val url: String = parsedJsonElement.asJsonObject.get(KEY_URL)?.asString?.trim().orEmpty()
         if (url.isBlank()) {
             throw IllegalStateException(ERROR_MISSING_URL)
         }
         return url
     }
 
     private fun parseUrl(url: String): Uri {
         val parsedUri: Uri = url.toUri()
         val scheme: String = parsedUri.scheme?.lowercase().orEmpty()
         if (scheme.isBlank()) {
             throw IllegalStateException("Invalid URL: '$url' could not be parsed")
         }
         if (scheme in BLOCKED_SCHEMES) {
             throw IllegalStateException("Blocked URL scheme: '$scheme'")
         }
         return parsedUri
     }
 
     private fun routeByScheme(
         parsedUri: Uri,
         targetUrl: String,
     ): String {
         val scheme = parsedUri.scheme
         requireNotNull(scheme) { "Url scheme must be not null" }
         return when (scheme.lowercase()) {
             SCHEME_INTENT -> openIntentUri(targetUrl)
             SCHEME_TEL -> openDialLink(parsedUri, targetUrl)
             SCHEME_SMS, SCHEME_MAILTO -> openSendToLink(parsedUri, targetUrl)
             SCHEME_HTTP, SCHEME_HTTPS -> openUriWithViewIntent(parsedUri, targetUrl)
             else -> openUriWithViewIntent(parsedUri, targetUrl)
         }
     }
 
     private fun openIntentUri(rawIntentUri: String): String {
         val parsedIntent: Intent = runCatching { Intent.parseUri(rawIntentUri, Intent.URI_INTENT_SCHEME) }
             .getOrElse {
                 mindboxLogW("Intent URI parse failed: $rawIntentUri")
                 throw IllegalStateException("Invalid URL: '$rawIntentUri' could not be parsed")
             }
         if (parsedIntent.action.isNullOrBlank()) {
             parsedIntent.action = Intent.ACTION_VIEW
         }
         parsedIntent.selector = null
         parsedIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
         return startIntent(parsedIntent, rawIntentUri)
     }
 
     private fun openDialLink(uri: Uri, rawUrl: String): String {
         val dialIntent: Intent = Intent(Intent.ACTION_DIAL, uri).apply {
             addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
         }
         return startIntent(dialIntent, rawUrl)
     }
 
     private fun openSendToLink(uri: Uri, rawUrl: String): String {
         val smsIntent: Intent = Intent(Intent.ACTION_SENDTO, uri).apply {
             addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
         }
         return startIntent(smsIntent, rawUrl)
     }
 
     private fun openUriWithViewIntent(uri: Uri, rawUrl: String): String {
         val intent: Intent = Intent(Intent.ACTION_VIEW, uri).apply {
             addCategory(Intent.CATEGORY_BROWSABLE)
             addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
         }
         return startIntent(intent, rawUrl)
     }
 
     private fun startIntent(intent: Intent, rawUrl: String): String {
         return try {
             context.startActivity(intent)
             rawUrl
         } catch (error: ActivityNotFoundException) {
             mindboxLogW("Activity not found for URI: $rawUrl")
             throw IllegalStateException(
                 "ActivityNotFoundException: ${error.message ?: "No activity found to handle URL"}"
             )
         } catch (error: SecurityException) {
             mindboxLogW("Security exception for URI: $rawUrl")
             throw IllegalStateException(
                 "SecurityException: ${error.message ?: "Cannot open URL"}"
             )
         } catch (error: Throwable) {
             throw IllegalStateException(error.message ?: "Navigation failed: unable to open URL")
         }
     }
 }