TP-75284 | added same idempotencyKey for each request in case of api Polling (#11946)
Co-authored-by: Kishan Kumar <kishan.kumar@navi.com>
This commit is contained in:
committed by
GitHub
parent
3a3f0add83
commit
12404a0276
@@ -66,7 +66,9 @@ import com.navi.ap.utils.toMap
|
||||
import com.navi.ap.utils.toMutableMap
|
||||
import com.navi.base.AppServiceManager
|
||||
import com.navi.base.model.CtaData
|
||||
import com.navi.base.utils.EMPTY
|
||||
import com.navi.base.utils.isNotNull
|
||||
import com.navi.base.utils.isNull
|
||||
import com.navi.base.utils.orElse
|
||||
import com.navi.base.utils.orFalse
|
||||
import com.navi.common.constants.API_SUCCESS_CODE
|
||||
@@ -82,6 +84,7 @@ import com.navi.uitron.model.UiTronResponse
|
||||
import com.navi.uitron.model.action.TriggerApiAction
|
||||
import com.navi.uitron.model.data.UiTronActionData
|
||||
import com.navi.uitron.utils.orVal
|
||||
import java.util.UUID
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
@@ -520,7 +523,9 @@ abstract class ApplicationPlatformVM(
|
||||
action: String = queryMap[APP_ACTION] ?: ApNavigationActions.NEXT.name,
|
||||
isScreenDefinitionRequired: Boolean = true,
|
||||
sourceScreen: String = DEFAULT_SOURCE_SCREEN,
|
||||
apiAction: TriggerApiAction? = null
|
||||
apiAction: TriggerApiAction? = null,
|
||||
idempotencyKey: String = EMPTY, // It is non-empty for polling requests
|
||||
isPolling: Boolean = false // It is true when this is a polling request
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
_ctaResponseState.value = GetCtaState.Loading
|
||||
@@ -535,7 +540,8 @@ abstract class ApplicationPlatformVM(
|
||||
sourceScreen = sourceScreen,
|
||||
currentScreen = getQueryMap()[APP_PLATFORM_SCREEN_ID] ?: DEFAULT_SOURCE_SCREEN,
|
||||
currentScreenState =
|
||||
getQueryMap()[APP_PLATFORM_SCREEN_STATE_ID] ?: DEFAULT_SCREEN_STATE_ID
|
||||
getQueryMap()[APP_PLATFORM_SCREEN_STATE_ID] ?: DEFAULT_SCREEN_STATE_ID,
|
||||
idempotencyKey = idempotencyKey,
|
||||
)
|
||||
|
||||
shouldPollStrategyUseCase.execute(
|
||||
@@ -545,8 +551,16 @@ abstract class ApplicationPlatformVM(
|
||||
bottomSheetStateHolder = bottomSheetStateHolder.value
|
||||
)
|
||||
|
||||
_ctaResponseState.value =
|
||||
_ctaResponseState.update {
|
||||
when {
|
||||
/**
|
||||
* isPolling is true when it is a polling request. periodicTaskScheduler is null
|
||||
* when polling is stopped (because we have received a response from the
|
||||
* previous request in the poller)
|
||||
*/
|
||||
isPolling && periodicTaskScheduler.isNull() -> {
|
||||
GetCtaState.Nothing
|
||||
}
|
||||
response.statusCode == ApiConstants.API_ERROR_NO_USER_FOUND ->
|
||||
getCtaStateWithLogoutCta()
|
||||
response.data?.currentScreenCta?.shouldPoll.orFalse() -> {
|
||||
@@ -573,6 +587,7 @@ abstract class ApplicationPlatformVM(
|
||||
}
|
||||
else -> GetCtaState.Nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -654,6 +669,7 @@ abstract class ApplicationPlatformVM(
|
||||
?.initialDelay
|
||||
.orVal(AP_POLL_INITIAL_DELAY)
|
||||
_ctaResponseState.value = GetCtaState.Loading
|
||||
val idempotencyKey = UUID.randomUUID().toString()
|
||||
periodicTaskScheduler =
|
||||
PeriodicTaskScheduler(
|
||||
maxAttempts = numberOfRetry,
|
||||
@@ -677,6 +693,7 @@ abstract class ApplicationPlatformVM(
|
||||
)
|
||||
},
|
||||
) {
|
||||
/** idempotencyKey will be same for each request in the poller */
|
||||
getCta(
|
||||
applicationId = queryMap[APP_PLATFORM_APPLICATION_ID].orEmpty(),
|
||||
applicationType = queryMap[APP_PLATFORM_APPLICATION_TYPE].orEmpty(),
|
||||
@@ -685,7 +702,9 @@ abstract class ApplicationPlatformVM(
|
||||
?: queryMap[APP_PLATFORM_APPLICATION_TYPE].orEmpty(),
|
||||
action = queryMap[APP_ACTION].orEmpty(),
|
||||
isScreenDefinitionRequired = true,
|
||||
apiAction = apiAction
|
||||
apiAction = apiAction,
|
||||
idempotencyKey = idempotencyKey,
|
||||
isPolling = true
|
||||
)
|
||||
}
|
||||
.apply { startTask() }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2023 by Navi Technologies Limited
|
||||
* * Copyright © 2023-2024 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
@@ -150,6 +150,7 @@ constructor(private val service: APRetrofitService) :
|
||||
sourceScreen: String,
|
||||
currentScreen: String,
|
||||
currentScreenState: String,
|
||||
idempotencyKey: String,
|
||||
): ApRepoResult<ApGetNextCtaResponse> {
|
||||
val apApiRequest =
|
||||
ApApiRequest(
|
||||
@@ -164,7 +165,8 @@ constructor(private val service: APRetrofitService) :
|
||||
applicationId = applicationId,
|
||||
request = apApiRequest,
|
||||
applicationType = applicationType,
|
||||
verticalType = verticalType
|
||||
verticalType = verticalType,
|
||||
idempotencyKey = idempotencyKey,
|
||||
)
|
||||
logApEvent(
|
||||
Pair(APP_PLATFORM_APPLICATION_ID, applicationId),
|
||||
|
||||
@@ -51,7 +51,8 @@ interface ApplicationPlatformRepository {
|
||||
isScreenDefinitionRequired: Boolean,
|
||||
sourceScreen: String,
|
||||
currentScreen: String,
|
||||
currentScreenState: String
|
||||
currentScreenState: String,
|
||||
idempotencyKey: String,
|
||||
): ApRepoResult<ApGetNextCtaResponse>
|
||||
|
||||
suspend fun createApplicationAndGetCta(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2023 by Navi Technologies Limited
|
||||
* * Copyright © 2023-2024 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
@@ -24,7 +24,8 @@ class NaviApHttpClient
|
||||
@Inject
|
||||
constructor(
|
||||
networkInfo: NetworkInfo = NetworkInfoProvider.networkInfo,
|
||||
@ApplicationContext private val context: Context
|
||||
@ApplicationContext private val context: Context,
|
||||
private val platformInterceptor: PlatformInterceptor,
|
||||
) : BaseHttpClient(networkInfo, context) {
|
||||
|
||||
val httpClientBuilder: OkHttpClient.Builder
|
||||
@@ -42,7 +43,7 @@ constructor(
|
||||
.build()
|
||||
)
|
||||
}
|
||||
addInterceptor(PlatformInterceptor())
|
||||
addInterceptor(platformInterceptor)
|
||||
}
|
||||
return okHttpClientBuilder
|
||||
}
|
||||
|
||||
@@ -11,10 +11,13 @@ import com.navi.ap.network.utils.RequestCache
|
||||
import com.navi.ap.network.utils.isInternalServerError
|
||||
import com.navi.ap.utils.constants.X_IDEMPOTENCY_KEY
|
||||
import com.navi.ap.utils.helper.RequestHelper
|
||||
import com.navi.base.utils.isNotNull
|
||||
import com.navi.common.network.ApiConstants
|
||||
import com.navi.common.network.handleException
|
||||
import com.navi.common.network.logIOException
|
||||
import com.navi.common.network.models.ErrorMessage
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.Protocol
|
||||
@@ -22,18 +25,18 @@ import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
|
||||
class PlatformInterceptor(
|
||||
private val requestCache: RequestCache = RequestCache(),
|
||||
private val requestHelper: RequestHelper = RequestHelper(),
|
||||
class PlatformInterceptor
|
||||
@Inject
|
||||
constructor(
|
||||
private val requestCache: RequestCache,
|
||||
private val requestHelper: RequestHelper,
|
||||
) : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
val stringUrl = request.url.toString()
|
||||
val cachedRequest = requestCache.getCachedRequest(stringUrl)
|
||||
val idempotencyKey = requestHelper.generateIdempotencyKey(request, cachedRequest)
|
||||
|
||||
val modifiedRequest = buildModifiedRequest(request, idempotencyKey)
|
||||
val modifiedRequest = buildModifiedRequest(request, stringUrl)
|
||||
|
||||
return try {
|
||||
val response = chain.proceed(modifiedRequest)
|
||||
@@ -45,10 +48,37 @@ class PlatformInterceptor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildModifiedRequest(request: Request, idempotencyKey: String): Request {
|
||||
return request.newBuilder().addHeader(X_IDEMPOTENCY_KEY, idempotencyKey).build()
|
||||
private fun buildModifiedRequest(request: Request, stringUrl: String): Request {
|
||||
return request.newBuilder().addOrReplaceValidIdempotencyKey(request, stringUrl).build()
|
||||
}
|
||||
|
||||
/**
|
||||
* If the request have an empty or null idempotencyKey, then it will add a new idempotencyKey in
|
||||
* the request header.
|
||||
*
|
||||
* If the request already have idempotencyKey but cached idempotencyKey is not null, then it
|
||||
* will replace the idempotencyKey in the request header.
|
||||
*/
|
||||
private fun Request.Builder.addOrReplaceValidIdempotencyKey(
|
||||
request: Request,
|
||||
stringUrl: String
|
||||
) = apply {
|
||||
val cachedRequest = requestCache.getCachedRequest(stringUrl)
|
||||
val cachedIdempotencyKey = requestHelper.getCachedIdempotencyKey(request, cachedRequest)
|
||||
if (request.header(X_IDEMPOTENCY_KEY).isNullOrEmpty() or cachedIdempotencyKey.isNotNull()) {
|
||||
val idempotencyKey = cachedIdempotencyKey ?: UUID.randomUUID().toString()
|
||||
header(X_IDEMPOTENCY_KEY, idempotencyKey)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the api request is successful, then it will remove the cached request from the request
|
||||
* cache.
|
||||
*
|
||||
* If the api request gets internal server error, then it will cache the request in the request
|
||||
* cache. And this idempotencyKey will be used in the next request if both the request are
|
||||
* idempotent.
|
||||
*/
|
||||
private fun handleResponse(response: Response, stringUrl: String): Response {
|
||||
when {
|
||||
response.isSuccessful -> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2023 by Navi Technologies Limited
|
||||
* * Copyright © 2023-2024 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
@@ -67,6 +67,7 @@ interface APRetrofitService : PlLambdaService, CoinsLambdaService, GenericLambda
|
||||
@Header("X-Platform") applicationHash: String = OS_ANDROID,
|
||||
): Response<GenericResponse<ApCreateApplicationResponse>>
|
||||
|
||||
/** Idempotency key is non-null and not empty here in the case of polling */
|
||||
@POST("/arc-warden/api/v3/application/{applicationId}/screen")
|
||||
suspend fun getCta(
|
||||
@Path("applicationId") applicationId: String,
|
||||
@@ -76,6 +77,7 @@ interface APRetrofitService : PlLambdaService, CoinsLambdaService, GenericLambda
|
||||
@Header("X-Target") target: String = ModuleName.AP.name,
|
||||
@Header("Accept-Encoding") acceptEncoding: String = GZIP,
|
||||
@Header("X-Platform") applicationHash: String = OS_ANDROID,
|
||||
@Header("X-Idempotency-Key") idempotencyKey: String,
|
||||
): Response<GenericResponse<ApGetNextCtaResponse>>
|
||||
|
||||
@GET("/application-platform/application/{applicationId}/end-routing")
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
|
||||
package com.navi.ap.network.utils
|
||||
|
||||
import javax.inject.Inject
|
||||
import okhttp3.Request
|
||||
|
||||
class RequestCache {
|
||||
class RequestCache @Inject constructor() {
|
||||
|
||||
private val cache = mutableMapOf<String, CachedRequest>()
|
||||
|
||||
|
||||
@@ -9,23 +9,22 @@ package com.navi.ap.utils.helper
|
||||
|
||||
import com.navi.ap.network.utils.CachedRequest
|
||||
import com.navi.ap.utils.bodyToString
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
|
||||
class RequestHelper {
|
||||
class RequestHelper @Inject constructor() {
|
||||
|
||||
fun generateIdempotencyKey(request: Request, cachedRequest: CachedRequest?): String {
|
||||
return if (cachedRequest != null && areRequestsEqual(cachedRequest.request, request)) {
|
||||
cachedRequest.idempotencyKey
|
||||
} else {
|
||||
UUID.randomUUID().toString()
|
||||
fun getCachedIdempotencyKey(request: Request, cachedRequest: CachedRequest?): String? {
|
||||
if (cachedRequest != null && areRequestsEqual(cachedRequest.request, request)) {
|
||||
return cachedRequest.idempotencyKey
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun areRequestsEqual(cachedRequest: Request, newRequest: Request): Boolean {
|
||||
if (cachedRequest.method != newRequest.method) return false
|
||||
if (cachedRequest.url != newRequest.url) return false
|
||||
if (cachedRequest.method != newRequest.method) return false
|
||||
if (areRequestBodiesEqual(cachedRequest.body, newRequest.body).not()) return false
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user