Skip to content

Commit

Permalink
fetch fallback host if needed (#83)
Browse files Browse the repository at this point in the history
  • Loading branch information
ren6 authored Mar 28, 2024
1 parent c8aaae2 commit 235a367
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 13 deletions.
56 changes: 51 additions & 5 deletions sdk/src/main/java/com/apphud/sdk/ApphudInternal+Fallback.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package com.apphud.sdk

import android.content.Context
import android.content.SharedPreferences
import com.apphud.sdk.client.ApiClient
import com.apphud.sdk.domain.ApphudUser
import com.apphud.sdk.domain.FallbackJsonObject
import com.apphud.sdk.managers.RequestManager
import com.apphud.sdk.mappers.PaywallsMapper
import com.apphud.sdk.parser.GsonParser
import com.apphud.sdk.parser.Parser
Expand All @@ -14,16 +16,57 @@ import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.launch
import okhttp3.Request
import java.io.IOException
import java.net.MalformedURLException
import java.net.URL

internal fun String.withRemovedScheme(): String {
return replace("https://", "")
}

private val gson = GsonBuilder().serializeNulls().create()
private val parser: Parser = GsonParser(gson)
private val paywallsMapper = PaywallsMapper(parser)

internal fun ApphudInternal.processFallbackError(request: Request) {
internal var fallbackHost: String? = null
internal var processedFallbackData = false
internal fun ApphudInternal.processFallbackError(request: Request, isTimeout: Boolean) {
if ((request.url.encodedPath.endsWith("/customers") ||
request.url.encodedPath.endsWith("/subscriptions"))
&& !fallbackMode) {
processFallbackData()
request.url.encodedPath.endsWith("/subscriptions") ||
request.url.encodedPath.endsWith("/products"))
&& !processedFallbackData) {

if (fallbackHost?.withRemovedScheme() == request.url.host) {
processFallbackData()
} else {
coroutineScope.launch {
tryFallbackHost()
if (fallbackHost == null) {
processFallbackData()
}
}
}
}
}

internal fun tryFallbackHost() {
val host = RequestManager.fetchFallbackHost()
host?.let {
if (isValidUrl(it)) {
fallbackHost = it
ApphudInternal.fallbackMode = true
ApiClient.host = fallbackHost!!
ApphudLog.logE("Fallback to host $fallbackHost")
ApphudInternal.isRegisteringUser = false
ApphudInternal.refreshPaywallsIfNeeded()
}
}
}

fun isValidUrl(urlString: String): Boolean {
return try {
URL(urlString)
true
} catch (e: MalformedURLException) {
false
}
}

Expand All @@ -38,6 +81,8 @@ private fun ApphudInternal.processFallbackData() {
ApphudLog.log("Fallback: user created: $userId")
}

processedFallbackData = true

// read paywalls from cache
var ids = paywalls.map { it.products?.map { it.productId } ?: listOf() }.flatten()
if (ids.isEmpty()) {
Expand Down Expand Up @@ -87,6 +132,7 @@ private fun getJsonDataFromAsset(

internal fun ApphudInternal.disableFallback() {
fallbackMode = false
processedFallbackData = false
ApphudLog.log("Fallback: DISABLED")
coroutineScope.launch(errorHandler) {
if (productGroups.isEmpty()) { // if fallback raised on start, there no product groups, so reload products and details
Expand Down
2 changes: 1 addition & 1 deletion sdk/src/main/java/com/apphud/sdk/client/ApiClient.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.apphud.sdk.client

object ApiClient {
var host = "https://api.apphud.com"
var host = "https://gateway.apphud.com"
var readTimeout: Long = 10L
}
26 changes: 21 additions & 5 deletions sdk/src/main/java/com/apphud/sdk/managers/HttpRetryInterceptor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import com.apphud.sdk.ApphudInternal
import com.apphud.sdk.ApphudInternal.FALLBACK_ERRORS
import com.apphud.sdk.ApphudInternal.fallbackMode
import com.apphud.sdk.ApphudLog
import com.apphud.sdk.ApphudUtils
import com.apphud.sdk.fallbackHost
import com.apphud.sdk.processFallbackError
import com.apphud.sdk.tryFallbackHost
import com.apphud.sdk.withRemovedScheme
import kotlinx.coroutines.launch
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
Expand Down Expand Up @@ -48,22 +53,22 @@ class HttpRetryInterceptor : Interceptor {
}

if (response.code in FALLBACK_ERRORS) {
ApphudInternal.processFallbackError(request)
ApphudInternal.processFallbackError(request, isTimeout = false)
if (ApphudInternal.fallbackMode) {
tryCount = MAX_COUNT
}
}

ApphudLog.logE(
"Request (${request.url.encodedPath}) failed with code (${response.code}). Will retry in ${STEP / 1000} seconds ($tryCount).",
"Request (${request.url}) failed with code (${response.code}). Will retry in ${STEP / 1000} seconds ($tryCount).",
)

Thread.sleep(STEP)
}
} catch (e: SocketTimeoutException) {
ApphudInternal.processFallbackError(request)
ApphudInternal.processFallbackError(request, isTimeout = true)
ApphudLog.logE(
"Request (${request.url.encodedPath}) failed with SocketTimeoutException. Will retry in ${STEP / 1000} seconds ($tryCount).",
"Request (${request.url}) failed with SocketTimeoutException. Will retry in ${STEP / 1000} seconds ($tryCount).",
)
if (ApphudInternal.fallbackMode) {
throw e
Expand All @@ -72,9 +77,14 @@ class HttpRetryInterceptor : Interceptor {
} catch (e: UnknownHostException) {
// do not retry when no internet connection issue
tryCount = MAX_COUNT
if (ApphudUtils.isOnline(ApphudInternal.context)) {
ApphudInternal.coroutineScope.launch {
tryFallbackHost()
}
}
} catch (e: Exception) {
ApphudLog.logE(
"Request (${request.url.encodedPath}) failed with Exception. Will retry in ${STEP / 1000} seconds ($tryCount).",
"Request (${request.url}) failed with Exception. Will retry in ${STEP / 1000} seconds ($tryCount).",
)
Thread.sleep(STEP)
} finally {
Expand All @@ -83,6 +93,12 @@ class HttpRetryInterceptor : Interceptor {
if (!isSuccess && tryCount < MAX_COUNT && !(response?.code in 401..403)) {
// response?.close()
}

if (fallbackHost != null && fallbackHost?.withRemovedScheme() != request.url.host) {
// invalid host, need to abort these requests
tryCount = MAX_COUNT
throw UnknownHostException("APPHUD_HOST_CHANGED")
}
}
}
if (!isSuccess) {
Expand Down
24 changes: 22 additions & 2 deletions sdk/src/main/java/com/apphud/sdk/managers/RequestManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ object RequestManager {
completionHandler(null, ApphudError(message))
}
} catch (e: SocketTimeoutException) {
ApphudInternal.processFallbackError(request)
ApphudInternal.processFallbackError(request, isTimeout = true)
val message = e.message ?: "Undefined error"
completionHandler(null, ApphudError(message, null, APPHUD_ERROR_TIMEOUT))
} catch (e: IOException) {
Expand Down Expand Up @@ -399,7 +399,7 @@ object RequestManager {
completionHandler(null, ApphudError("Registration failed"))
}
} catch (e: SocketTimeoutException) {
ApphudInternal.processFallbackError(request)
ApphudInternal.processFallbackError(request, isTimeout = true)
val message = e.message ?: "Registration failed"
completionHandler(null, ApphudError(message, null, APPHUD_ERROR_TIMEOUT))
} catch (ex: UnknownHostException) {
Expand Down Expand Up @@ -657,6 +657,26 @@ object RequestManager {
}
}

fun fetchFallbackHost(): String? {
val url = "https://apphud.blob.core.windows.net/apphud-gateway/fallback.txt"
val client = OkHttpClient()

// Build the request
val request = Request.Builder()
.url(url)
.build()

// Execute the request
val response = client.newCall(request).execute()

// Return the response body as a string if the request was successful
return if (response.isSuccessful) {
response.body?.string()
} else {
null
}
}

fun grantPromotional(
daysCount: Int,
productId: String?,
Expand Down

0 comments on commit 235a367

Please sign in to comment.