From c9951aa8de080f436b5e52bb3b65c82bacee92c4 Mon Sep 17 00:00:00 2001 From: hasibprince Date: Thu, 9 Mar 2023 00:22:40 +0600 Subject: [PATCH 1/3] handled auth error of gplay api --- app/build.gradle | 2 +- .../java/foundation/e/apps/MainActivity.kt | 9 +- .../e/apps/api/fused/FusedAPIImpl.kt | 95 ++++++++++++++----- .../e/apps/api/gplay/utils/GPlayHttpClient.kt | 5 + .../e/apps/login/LoginSourceRepository.kt | 1 + .../e/apps/utils/enums/ResultStatus.kt | 1 + .../e/apps/utils/eventBus/AppEvent.kt | 2 + 7 files changed, 91 insertions(+), 24 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 369f1e3b1..8a1d97c8c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,7 +11,7 @@ plugins { def versionMajor = 2 def versionMinor = 4 -def versionPatch = 7 +def versionPatch = 9 def getGitHash = { -> def stdOut = new ByteArrayOutputStream() diff --git a/app/src/main/java/foundation/e/apps/MainActivity.kt b/app/src/main/java/foundation/e/apps/MainActivity.kt index 67efd606d..a116b9257 100644 --- a/app/src/main/java/foundation/e/apps/MainActivity.kt +++ b/app/src/main/java/foundation/e/apps/MainActivity.kt @@ -55,7 +55,6 @@ import foundation.e.apps.utils.eventBus.AppEvent import foundation.e.apps.utils.eventBus.EventBus import foundation.e.apps.utils.exceptions.GPlayValidationException import foundation.e.apps.utils.modules.CommonUtilsFunctions -import foundation.e.apps.utils.modules.CommonUtilsModule import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch @@ -214,6 +213,14 @@ class MainActivity : AppCompatActivity() { viewModel.updateAppWarningList() lifecycleScope.launchWhenResumed { + EventBus.events.filter { it is AppEvent.AuthUpdateEvent }.collectLatest { + Timber.d("Updated AuthObjects") + val authObjectMutableList = loginViewModel.authObjects.value?.toMutableList() + authObjectMutableList?.removeIf { it.result.data is AuthData } + authObjectMutableList?.add(it.data as AuthObject) + loginViewModel.authObjects.postValue(authObjectMutableList) + } + EventBus.events.filter { appEvent -> appEvent is AppEvent.SignatureMissMatchError }.collectLatest { diff --git a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt index 889cbb999..c4ae773d5 100644 --- a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt +++ b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt @@ -32,6 +32,7 @@ import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.Category import com.aurora.gplayapi.data.models.StreamBundle import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.gplayapi.exceptions.ApiException import com.aurora.gplayapi.helpers.TopChartsHelper import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.R @@ -48,7 +49,10 @@ import foundation.e.apps.api.fused.data.FusedHome import foundation.e.apps.api.fused.data.Ratings import foundation.e.apps.api.fused.utils.CategoryUtils import foundation.e.apps.api.gplay.GPlayAPIRepository +import foundation.e.apps.api.gplay.utils.GPlayHttpClient import foundation.e.apps.home.model.HomeChildFusedAppDiffUtil +import foundation.e.apps.login.AuthObject +import foundation.e.apps.login.LoginSourceRepository import foundation.e.apps.manager.database.fusedDownload.FusedDownload import foundation.e.apps.manager.pkg.PkgManagerModule import foundation.e.apps.utils.Constants.timeoutDurationInMillis @@ -60,6 +64,8 @@ import foundation.e.apps.utils.enums.Source import foundation.e.apps.utils.enums.Status import foundation.e.apps.utils.enums.Type import foundation.e.apps.utils.enums.isUnFiltered +import foundation.e.apps.utils.eventBus.AppEvent +import foundation.e.apps.utils.eventBus.EventBus import foundation.e.apps.utils.modules.PWAManagerModule import foundation.e.apps.utils.modules.PreferenceManagerModule import kotlinx.coroutines.TimeoutCancellationException @@ -76,6 +82,7 @@ class FusedAPIImpl @Inject constructor( private val pwaManagerModule: PWAManagerModule, private val preferenceManagerModule: PreferenceManagerModule, private val fdroidWebInterface: FdroidWebInterface, + private val loginSourceRepository: LoginSourceRepository, @ApplicationContext private val context: Context ) { @@ -148,7 +155,7 @@ class FusedAPIImpl @Inject constructor( val apiStatus = when (source) { Source.GPLAY -> runCodeBlockWithTimeout({ - priorList.addAll(fetchGPlayHome(authData)) + priorList.addAll(fetchGPlayHome(it ?: authData)) }) Source.OPEN -> runCodeBlockWithTimeout({ @@ -234,10 +241,11 @@ class FusedAPIImpl @Inject constructor( * for all results to be fetched from network before showing them. * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5171 */ + return liveData { val packageSpecificResults = ArrayList() fetchPackageSpecificResult(authData, query, packageSpecificResults)?.let { - if (it.data?.second == true) { // if there are no data to load + if (it.data?.second == false) { // if there are no data to load emit(it) return@liveData } @@ -374,7 +382,7 @@ class FusedAPIImpl @Inject constructor( val status = runCodeBlockWithTimeout({ if (preferenceManagerModule.isGplaySelected()) { - gplayPackageResult = getGplayPackagResult(query, authData) + gplayPackageResult = getGplayPackagResult(query, it ?: authData) } if (preferenceManagerModule.isOpenSourceSelected()) { @@ -395,7 +403,7 @@ class FusedAPIImpl @Inject constructor( * If there was a timeout, return it and don't try to fetch anything else. * Also send true in the pair to signal more results being loaded. */ - if (status != ResultStatus.OK) { + if (status != ResultStatus.OK || packageSpecificResults.isEmpty()) { return ResultSupreme.create(status, Pair(packageSpecificResults, true)) } return ResultSupreme.create(status, Pair(packageSpecificResults, false)) @@ -558,7 +566,7 @@ class FusedAPIImpl @Inject constructor( var streamBundle = StreamBundle() val status = runCodeBlockWithTimeout({ streamBundle = - gPlayAPIRepository.getNextStreamBundle(authData, homeUrl, currentStreamBundle) + gPlayAPIRepository.getNextStreamBundle(it ?: authData, homeUrl, currentStreamBundle) }) return ResultSupreme.create(status, streamBundle) } @@ -571,7 +579,7 @@ class FusedAPIImpl @Inject constructor( var streamCluster = StreamCluster() val status = runCodeBlockWithTimeout({ streamCluster = - gPlayAPIRepository.getAdjustedFirstCluster(authData, streamBundle, pointer) + gPlayAPIRepository.getAdjustedFirstCluster(it ?: authData, streamBundle, pointer) }) return ResultSupreme.create(status, streamCluster) } @@ -582,7 +590,8 @@ class FusedAPIImpl @Inject constructor( ): ResultSupreme { var streamCluster = StreamCluster() val status = runCodeBlockWithTimeout({ - streamCluster = gPlayAPIRepository.getNextStreamCluster(authData, currentStreamCluster) + streamCluster = + gPlayAPIRepository.getNextStreamCluster(it ?: authData, currentStreamCluster) }) return ResultSupreme.create(status, streamCluster) } @@ -594,7 +603,7 @@ class FusedAPIImpl @Inject constructor( val list = mutableListOf() val status = runCodeBlockWithTimeout({ list.addAll( - gPlayAPIRepository.listApps(browseUrl, authData).map { app -> + gPlayAPIRepository.listApps(browseUrl, it ?: authData).map { app -> app.transformToFusedApp() } ) @@ -706,7 +715,7 @@ class FusedAPIImpl @Inject constructor( * Old code moved from getApplicationDetails() */ val status = runCodeBlockWithTimeout({ - gPlayAPIRepository.getAppDetails(packageNameList, authData).forEach { app -> + gPlayAPIRepository.getAppDetails(packageNameList, it ?: authData).forEach { app -> /* * Some apps are restricted to locations. Example "com.skype.m2". * For restricted apps, check if it is possible to get their specific app info. @@ -743,9 +752,9 @@ class FusedAPIImpl @Inject constructor( appList: List, ): ResultSupreme> { val filteredFusedApps = mutableListOf() - val status = runCodeBlockWithTimeout({ + val status = runCodeBlockWithTimeout({ auth -> appList.forEach { - val filter = getAppFilterLevel(it, authData) + val filter = getAppFilterLevel(it, auth ?: authData) if (filter.isUnFiltered()) { filteredFusedApps.add( it.transformToFusedApp().apply { @@ -837,10 +846,11 @@ class FusedAPIImpl @Inject constructor( var response: FusedApp? = null val status = runCodeBlockWithTimeout({ + response = if (origin == Origin.CLEANAPK) { cleanAPKRepository.getAppOrPWADetailsByID(id).body()?.app } else { - val app = gPlayAPIRepository.getAppDetails(packageName, authData) + val app = gPlayAPIRepository.getAppDetails(packageName, it ?: authData) app?.transformToFusedApp() } response?.let { @@ -850,7 +860,7 @@ class FusedAPIImpl @Inject constructor( it.updateFilterLevel(authData) } }) - + Timber.d("getAppDetails: $status") return Pair(response ?: FusedApp(), status) } @@ -945,12 +955,15 @@ class FusedAPIImpl @Inject constructor( var errorApplicationCategory = "" var apiStatus = ResultStatus.OK val categoryList = mutableListOf() + runCodeBlockWithTimeout({ - val playResponse = gPlayAPIRepository.getCategoriesList(type, authData).map { app -> - val category = app.transformToFusedCategory() - updateCategoryDrawable(category) - category - } + + val playResponse = + gPlayAPIRepository.getCategoriesList(type, it ?: authData).map { app -> + val category = app.transformToFusedCategory() + updateCategoryDrawable(category) + category + } categoryList.addAll(playResponse) }, { errorApplicationCategory = APP_TYPE_ANY @@ -1022,13 +1035,18 @@ class FusedAPIImpl @Inject constructor( * @return Instance of [ResultStatus] based on whether [block] was executed within timeout limit. */ private suspend fun runCodeBlockWithTimeout( - block: suspend () -> Unit, + block: suspend (authData: AuthData?) -> Unit, timeoutBlock: (() -> Unit)? = null, exceptionBlock: ((e: Exception) -> Unit)? = null, ): ResultStatus { return try { + var authObject: AuthObject? withTimeout(timeoutDurationInMillis) { - block() + authObject = executeBlockHandlingException(block) + } + + authObject?.let { + EventBus.invokeEvent(AppEvent.AuthUpdateEvent(it)) } ResultStatus.OK } catch (e: TimeoutCancellationException) { @@ -1043,6 +1061,35 @@ class FusedAPIImpl @Inject constructor( } } + private suspend fun FusedAPIImpl.executeBlockHandlingException( + block: suspend (authData: AuthData?) -> Unit, + ): AuthObject? { + return try { + block(null) + handleUnauthorizedAuthData(block) + } catch (e: ApiException.AppNotFound) { + Timber.w(e) + handleUnauthorizedAuthData(block) ?: throw e + } + } + + private suspend fun handleUnauthorizedAuthData( + block: suspend (authData: AuthData?) -> Unit + ): AuthObject? { + if (!GPlayHttpClient.IS_AUTH_VALID) { + val loginSources = loginSourceRepository.getAuthObjects() + + loginSources.find { it is AuthObject.GPlayAuth }?.let { + val authData = (it.result.data as AuthData) + GPlayHttpClient.IS_AUTH_VALID = true + block.invoke(authData) + Timber.d("data refreshed with new auth!") + return it + } + } + return null + } + private fun updateCategoryDrawable( category: FusedCategory, ) { @@ -1190,7 +1237,8 @@ class FusedAPIImpl @Inject constructor( query: String, authData: AuthData ): LiveData, Boolean>> { - val searchResults = gPlayAPIRepository.getSearchResults(query, authData, ::replaceWithFDroid) + val searchResults = + gPlayAPIRepository.getSearchResults(query, authData, ::replaceWithFDroid) return searchResults.map { Pair( it.first, @@ -1336,7 +1384,10 @@ class FusedAPIImpl @Inject constructor( updateFilterLevel(authData) } } - list.add(FusedHome(it.key, result)) + + if (result.isNotEmpty()) { + list.add(FusedHome(it.key, result)) + } } return list } diff --git a/app/src/main/java/foundation/e/apps/api/gplay/utils/GPlayHttpClient.kt b/app/src/main/java/foundation/e/apps/api/gplay/utils/GPlayHttpClient.kt index 6725e07b1..d2c4c9e3c 100644 --- a/app/src/main/java/foundation/e/apps/api/gplay/utils/GPlayHttpClient.kt +++ b/app/src/main/java/foundation/e/apps/api/gplay/utils/GPlayHttpClient.kt @@ -47,6 +47,7 @@ class GPlayHttpClient @Inject constructor( companion object { private const val TAG = "GPlayHttpClient" + var IS_AUTH_VALID = true } private val okHttpClient = OkHttpClient().newBuilder() @@ -177,6 +178,10 @@ class GPlayHttpClient @Inject constructor( return PlayResponse().apply { isSuccessful = response.isSuccessful code = response.code + Timber.d("GplayHttpClient: Url: ${response.request.url} \n Status: ${response.code}") + if (response.code == 401) { + IS_AUTH_VALID = false + } if (response.body != null) { responseBytes = response.body!!.bytes() diff --git a/app/src/main/java/foundation/e/apps/login/LoginSourceRepository.kt b/app/src/main/java/foundation/e/apps/login/LoginSourceRepository.kt index ddc4bb392..8aaa3ff06 100644 --- a/app/src/main/java/foundation/e/apps/login/LoginSourceRepository.kt +++ b/app/src/main/java/foundation/e/apps/login/LoginSourceRepository.kt @@ -17,6 +17,7 @@ package foundation.e.apps.login +import com.aurora.gplayapi.data.models.AuthData import foundation.e.apps.utils.enums.User import javax.inject.Inject import javax.inject.Singleton diff --git a/app/src/main/java/foundation/e/apps/utils/enums/ResultStatus.kt b/app/src/main/java/foundation/e/apps/utils/enums/ResultStatus.kt index 87dcf9530..8f0ac555c 100644 --- a/app/src/main/java/foundation/e/apps/utils/enums/ResultStatus.kt +++ b/app/src/main/java/foundation/e/apps/utils/enums/ResultStatus.kt @@ -4,6 +4,7 @@ enum class ResultStatus { OK, TIMEOUT, UNKNOWN, + UNAUTHORIZED, RETRY; var message: String = "" } diff --git a/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt b/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt index 797f708c0..08d1403f0 100644 --- a/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt +++ b/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt @@ -21,9 +21,11 @@ package foundation.e.apps.utils.eventBus import foundation.e.apps.api.ResultSupreme +import foundation.e.apps.login.AuthObject import foundation.e.apps.utils.enums.ResultStatus sealed class AppEvent(val data: Any) { class SignatureMissMatchError(packageName: String) : AppEvent(packageName) class UpdateEvent(result: ResultSupreme.WorkError) : AppEvent(result) + class AuthUpdateEvent(authObject: AuthObject) : AppEvent(authObject) } -- GitLab From 5dd40f10b413a3fd83646e809122559b1f5f42ad Mon Sep 17 00:00:00 2001 From: hasibprince Date: Sat, 11 Mar 2023 15:27:36 +0600 Subject: [PATCH 2/3] refactoring: gplay and fused repository related stuffs --- .../e/apps/api/fused/FusedAPIImpl.kt | 35 +++++----------- .../e/apps/api/gplay/GPlayAPIImpl.kt | 28 +++++++++++-- .../e/apps/api/gplay/GPlayAPIRepository.kt | 12 +++++- .../e/apps/api/gplay/utils/GplayUtils.kt | 42 +++++++++++++++++++ .../foundation/e/apps/login/AuthValidator.kt | 23 ++++++++++ .../e/apps/login/LoginSourceGPlay.kt | 27 ++++++++---- .../e/apps/login/LoginSourceRepository.kt | 8 +++- .../foundation/e/apps/FusedApiImplTest.kt | 29 +++++++++---- 8 files changed, 157 insertions(+), 47 deletions(-) create mode 100644 app/src/main/java/foundation/e/apps/api/gplay/utils/GplayUtils.kt create mode 100644 app/src/main/java/foundation/e/apps/login/AuthValidator.kt diff --git a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt index d4da0a37c..b98542cf1 100644 --- a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt +++ b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt @@ -48,7 +48,6 @@ import foundation.e.apps.api.fused.data.FusedHome import foundation.e.apps.api.fused.data.Ratings import foundation.e.apps.api.fused.utils.CategoryUtils import foundation.e.apps.api.gplay.GPlayAPIRepository -import foundation.e.apps.api.gplay.utils.GPlayHttpClient import foundation.e.apps.home.model.HomeChildFusedAppDiffUtil import foundation.e.apps.login.AuthObject import foundation.e.apps.login.LoginSourceRepository @@ -884,7 +883,7 @@ class FusedAPIImpl @Inject constructor( it.updateFilterLevel(authData) } }) - Timber.d("getAppDetails: $status") + return Pair(response ?: FusedApp(), status) } @@ -1085,33 +1084,19 @@ class FusedAPIImpl @Inject constructor( } } - private suspend fun FusedAPIImpl.executeBlockHandlingException( + private suspend fun executeBlockHandlingException( block: suspend (authData: AuthData?) -> Unit, ): AuthObject? { return try { block(null) - handleUnauthorizedAuthData(block) + gPlayAPIRepository.handleUnauthorizedAuthData(block) { loginSourceRepository.validateAuthObject() } } catch (e: ApiException.AppNotFound) { Timber.w(e) - handleUnauthorizedAuthData(block) ?: throw e - } - } - - private suspend fun handleUnauthorizedAuthData( - block: suspend (authData: AuthData?) -> Unit - ): AuthObject? { - if (!GPlayHttpClient.IS_AUTH_VALID) { - val loginSources = loginSourceRepository.getAuthObjects() - - loginSources.find { it is AuthObject.GPlayAuth }?.let { - val authData = (it.result.data as AuthData) - GPlayHttpClient.IS_AUTH_VALID = true - block.invoke(authData) - Timber.d("data refreshed with new auth!") - return it - } + gPlayAPIRepository.handleUnauthorizedAuthData(block) { loginSourceRepository.validateAuthObject() } + ?: throw e + } catch (e: Exception) { + throw e } - return null } private fun updateCategoryDrawable( @@ -1123,7 +1108,7 @@ class FusedAPIImpl @Inject constructor( private fun getCategoryIconName(category: FusedCategory): String { var categoryTitle = if (category.tag.getOperationalTag() - .contentEquals(AppTag.GPlay().getOperationalTag()) + .contentEquals(AppTag.GPlay().getOperationalTag()) ) category.id else category.title if (categoryTitle.contains(CATEGORY_TITLE_REPLACEABLE_CONJUNCTION)) { @@ -1262,7 +1247,9 @@ class FusedAPIImpl @Inject constructor( authData: AuthData ): LiveData, Boolean>> { val searchResults = - gPlayAPIRepository.getSearchResults(query, authData, ::replaceWithFDroid) + gPlayAPIRepository.getSearchResults(query, authData, ::replaceWithFDroid) { + loginSourceRepository.validateAuthObject() + } return searchResults.map { Pair( it.first, diff --git a/app/src/main/java/foundation/e/apps/api/gplay/GPlayAPIImpl.kt b/app/src/main/java/foundation/e/apps/api/gplay/GPlayAPIImpl.kt index f68b6680e..2a63353bf 100644 --- a/app/src/main/java/foundation/e/apps/api/gplay/GPlayAPIImpl.kt +++ b/app/src/main/java/foundation/e/apps/api/gplay/GPlayAPIImpl.kt @@ -38,12 +38,16 @@ import com.aurora.gplayapi.helpers.StreamHelper import com.aurora.gplayapi.helpers.TopChartsHelper import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.api.gplay.utils.GPlayHttpClient +import foundation.e.apps.api.gplay.utils.GplayUtils +import foundation.e.apps.login.AuthObject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.withContext import javax.inject.Inject -class GPlayAPIImpl @Inject constructor(private val gPlayHttpClient: GPlayHttpClient) { +class GPlayAPIImpl @Inject constructor( + private val gPlayHttpClient: GPlayHttpClient +) { suspend fun getSearchSuggestions(query: String, authData: AuthData): List { val searchData = mutableListOf() @@ -62,6 +66,7 @@ class GPlayAPIImpl @Inject constructor(private val gPlayHttpClient: GPlayHttpCli query: String, authData: AuthData, replaceWithFDroid: suspend (App) -> FusedApp, + authValidator: suspend () -> AuthObject? ): LiveData, Boolean>> { /* * Send livedata to improve UI performance, so we don't have to wait for loading all results. @@ -73,8 +78,15 @@ class GPlayAPIImpl @Inject constructor(private val gPlayHttpClient: GPlayHttpCli * Variable names and logic made same as that of Aurora store. * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5171 */ - val searchHelper = SearchHelper(authData).using(gPlayHttpClient) - val searchBundle = searchHelper.searchResults(query) + var searchHelper = SearchHelper(authData).using(gPlayHttpClient) + var searchBundle = searchHelper.searchResults(query) + + GplayUtils.handleUnauthorizedAuthData({ authData -> + authData?.let { + searchHelper = SearchHelper(it).using(gPlayHttpClient) + searchBundle = searchHelper.searchResults(query) + } + }, authValidator) val initialReplacedList = mutableListOf() val INITIAL_LIMIT = 4 @@ -91,7 +103,15 @@ class GPlayAPIImpl @Inject constructor(private val gPlayHttpClient: GPlayHttpCli var nextSubBundleSet: MutableSet do { nextSubBundleSet = searchBundle.subBundles - val newSearchBundle = searchHelper.next(nextSubBundleSet) + var newSearchBundle = searchHelper.next(nextSubBundleSet) + + GplayUtils.handleUnauthorizedAuthData({ authData -> + authData?.let { + searchHelper = SearchHelper(it).using(gPlayHttpClient) + newSearchBundle = searchHelper.next(nextSubBundleSet) + } + }, authValidator) + if (newSearchBundle.appList.isNotEmpty()) { searchBundle.apply { subBundles.clear() diff --git a/app/src/main/java/foundation/e/apps/api/gplay/GPlayAPIRepository.kt b/app/src/main/java/foundation/e/apps/api/gplay/GPlayAPIRepository.kt index 4475cb03a..492e0a64c 100644 --- a/app/src/main/java/foundation/e/apps/api/gplay/GPlayAPIRepository.kt +++ b/app/src/main/java/foundation/e/apps/api/gplay/GPlayAPIRepository.kt @@ -28,6 +28,8 @@ import com.aurora.gplayapi.data.models.StreamBundle import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.helpers.TopChartsHelper import foundation.e.apps.api.fused.data.FusedApp +import foundation.e.apps.api.gplay.utils.GplayUtils +import foundation.e.apps.login.AuthObject import javax.inject.Inject class GPlayAPIRepository @Inject constructor(private val gPlayAPIImpl: GPlayAPIImpl) { @@ -40,8 +42,9 @@ class GPlayAPIRepository @Inject constructor(private val gPlayAPIImpl: GPlayAPII query: String, authData: AuthData, replaceWithFDroid: suspend (App) -> FusedApp, + authValidator: suspend () -> AuthObject? ): LiveData, Boolean>> { - return gPlayAPIImpl.getSearchResults(query, authData, replaceWithFDroid) + return gPlayAPIImpl.getSearchResults(query, authData, replaceWithFDroid, authValidator) } suspend fun getOnDemandModule( @@ -109,4 +112,11 @@ class GPlayAPIRepository @Inject constructor(private val gPlayAPIImpl: GPlayAPII suspend fun listApps(browseUrl: String, authData: AuthData): List { return gPlayAPIImpl.listApps(browseUrl, authData) } + + suspend fun handleUnauthorizedAuthData( + block: suspend (authData: AuthData?) -> Unit, + authDataFetcher: suspend () -> AuthObject? + ): AuthObject? { + return GplayUtils.handleUnauthorizedAuthData(block, authDataFetcher) + } } diff --git a/app/src/main/java/foundation/e/apps/api/gplay/utils/GplayUtils.kt b/app/src/main/java/foundation/e/apps/api/gplay/utils/GplayUtils.kt new file mode 100644 index 000000000..ed789b489 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/api/gplay/utils/GplayUtils.kt @@ -0,0 +1,42 @@ +/* + * Copyright MURENA SAS 2023 + * Apps Quickly and easily install Android apps onto your device! + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.api.gplay.utils + +import com.aurora.gplayapi.data.models.AuthData +import foundation.e.apps.login.AuthObject +import timber.log.Timber + +object GplayUtils { + suspend fun handleUnauthorizedAuthData( + block: suspend (authData: AuthData?) -> Unit, + authValidator: suspend () -> AuthObject? + ): AuthObject? { + if (!GPlayHttpClient.IS_AUTH_VALID) { + authValidator()?.let { + val authData = (it.result.data as AuthData) + GPlayHttpClient.IS_AUTH_VALID = true + block.invoke(authData) + Timber.d("data refreshed with new auth!") + return it + } + } + + return null + } +} \ No newline at end of file diff --git a/app/src/main/java/foundation/e/apps/login/AuthValidator.kt b/app/src/main/java/foundation/e/apps/login/AuthValidator.kt new file mode 100644 index 000000000..f12fdcaf3 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/login/AuthValidator.kt @@ -0,0 +1,23 @@ +/* + * Copyright MURENA SAS 2023 + * Apps Quickly and easily install Android apps onto your device! + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.login + +interface AuthValidator { + suspend fun validateAuth(): AuthObject? +} \ No newline at end of file diff --git a/app/src/main/java/foundation/e/apps/login/LoginSourceGPlay.kt b/app/src/main/java/foundation/e/apps/login/LoginSourceGPlay.kt index c48081ddb..8659ac5f3 100644 --- a/app/src/main/java/foundation/e/apps/login/LoginSourceGPlay.kt +++ b/app/src/main/java/foundation/e/apps/login/LoginSourceGPlay.kt @@ -43,7 +43,7 @@ class LoginSourceGPlay @Inject constructor( @ApplicationContext private val context: Context, private val gson: Gson, private val loginDataStore: LoginDataStore, -) : LoginSourceInterface { +) : LoginSourceInterface, AuthValidator { @Inject lateinit var gPlayApiFactory: GPlayApiFactory @@ -86,13 +86,8 @@ class LoginSourceGPlay @Inject constructor( } ) - // validate authData and save it if nothing is saved (first time use.) - validateAuthData(authData).run { - if (isSuccess() && savedAuth == null) { - saveAuthData(authData) - } - return AuthObject.GPlayAuth(this, user) - } + loginDataStore.saveAuthData(authData) + return AuthObject.GPlayAuth(ResultSupreme.Success(authData), user) } override suspend fun clearSavedAuth() { @@ -244,4 +239,20 @@ class LoginSourceGPlay @Inject constructor( ) } } + + override suspend fun validateAuth(): AuthObject? { + val authData = getSavedAuthData() + + // validate authData and save it if nothing is saved (first time use.) + authData?.let { + validateAuthData(authData).run { + if (isSuccess()) { + saveAuthData(authData) + } + return AuthObject.GPlayAuth(this, user) + } + } + + return null + } } diff --git a/app/src/main/java/foundation/e/apps/login/LoginSourceRepository.kt b/app/src/main/java/foundation/e/apps/login/LoginSourceRepository.kt index 8aaa3ff06..2141a8ffe 100644 --- a/app/src/main/java/foundation/e/apps/login/LoginSourceRepository.kt +++ b/app/src/main/java/foundation/e/apps/login/LoginSourceRepository.kt @@ -17,7 +17,6 @@ package foundation.e.apps.login -import com.aurora.gplayapi.data.models.AuthData import foundation.e.apps.utils.enums.User import javax.inject.Inject import javax.inject.Singleton @@ -59,4 +58,11 @@ class LoginSourceRepository @Inject constructor( suspend fun logout() { loginCommon.logout() } + + suspend fun validateAuthObject(): AuthObject? { + (sources.find { it is AuthValidator })?.let { + return (it as AuthValidator).validateAuth() + } + return null + } } diff --git a/app/src/test/java/foundation/e/apps/FusedApiImplTest.kt b/app/src/test/java/foundation/e/apps/FusedApiImplTest.kt index 646b84fad..137879533 100644 --- a/app/src/test/java/foundation/e/apps/FusedApiImplTest.kt +++ b/app/src/test/java/foundation/e/apps/FusedApiImplTest.kt @@ -35,6 +35,7 @@ import foundation.e.apps.api.fused.FusedAPIImpl import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.api.fused.data.FusedHome import foundation.e.apps.api.gplay.GPlayAPIRepository +import foundation.e.apps.login.LoginSourceRepository import foundation.e.apps.manager.pkg.PkgManagerModule import foundation.e.apps.util.MainCoroutineRule import foundation.e.apps.util.getOrAwaitValue @@ -93,6 +94,9 @@ class FusedApiImplTest { @Mock private lateinit var fdroidWebInterface: FdroidWebInterface + @Mock + private lateinit var loginSourceRepository: LoginSourceRepository + private lateinit var preferenceManagerModule: FakePreferenceModule private lateinit var formatterMocked: MockedStatic @@ -113,6 +117,7 @@ class FusedApiImplTest { pwaManagerModule, preferenceManagerModule, fdroidWebInterface, + loginSourceRepository, context ) } @@ -767,7 +772,8 @@ class FusedApiImplTest { preferenceManagerModule.isGplaySelectedFake = true val gplayLivedata: LiveData, Boolean>> = MutableLiveData( Pair( - listOf(FusedApp("a.b.c"), FusedApp("c.d.e"), FusedApp("d.e.f"), FusedApp("d.e.g")), false + listOf(FusedApp("a.b.c"), FusedApp("c.d.e"), FusedApp("d.e.f"), FusedApp("d.e.g")), + false ) ) @@ -776,10 +782,10 @@ class FusedApiImplTest { ) val searchResultLiveData = - fusedAPIImpl.getSearchResults("com.search.package", AUTH_DATA).getOrAwaitValue() + fusedAPIImpl.getSearchResults("com.search.package", AUTH_DATA).getOrAwaitValue(12) val size = searchResultLiveData.data?.first?.size ?: -2 - assertEquals("getSearchResult", 8, size) + assertEquals("getSearchResult", 1, size) } private suspend fun setupMockingSearchApp( @@ -826,7 +832,8 @@ class FusedApiImplTest { gPlayAPIRepository.getSearchResults( eq("com.search.package"), eq(authData), - eq(::replaceWithFDroid) + eq(::replaceWithFDroid), + any() ) ) .thenReturn(gplayLivedata) @@ -863,20 +870,24 @@ class FusedApiImplTest { val gplayPackageResult = App("com.search.package") val gplayLivedata = - MutableLiveData(Pair(listOf(FusedApp("a.b.c"), FusedApp("c.d.e"), FusedApp("d.e.f")), false)) + MutableLiveData( + Pair( + listOf(FusedApp("a.b.c"), FusedApp("c.d.e"), FusedApp("d.e.f")), + false + ) + ) setupMockingSearchApp( packageNameSearchResponse, AUTH_DATA, gplayPackageResult, gplayLivedata, true ) preferenceManagerModule.isPWASelectedFake = false - preferenceManagerModule.isOpenSourceelectedFake = false - preferenceManagerModule.isGplaySelectedFake = true + preferenceManagerModule.isOpenSourceelectedFake = true + preferenceManagerModule.isGplaySelectedFake = false val searchResultLiveData = fusedAPIImpl.getSearchResults("com.search.package", AUTH_DATA).getOrAwaitValue() - val size = searchResultLiveData.data?.first?.size ?: -2 - assertEquals("getSearchResult", 3, size) + assertEquals("getSearchResult", 1, size) } } -- GitLab From 5e775bcc098554b9aa929bebd53485bb7502285e Mon Sep 17 00:00:00 2001 From: hasibprince Date: Tue, 14 Mar 2023 10:39:08 +0600 Subject: [PATCH 3/3] updated authvalidation process --- .../e/apps/login/LoginSourceGPlay.kt | 42 +++++++------------ 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/foundation/e/apps/login/LoginSourceGPlay.kt b/app/src/main/java/foundation/e/apps/login/LoginSourceGPlay.kt index 8659ac5f3..ae5563968 100644 --- a/app/src/main/java/foundation/e/apps/login/LoginSourceGPlay.kt +++ b/app/src/main/java/foundation/e/apps/login/LoginSourceGPlay.kt @@ -77,14 +77,14 @@ class LoginSourceGPlay @Inject constructor( val savedAuth = getSavedAuthData() val authData = ( - savedAuth ?: run { - // if no saved data, then generate new auth data. - generateAuthData().let { - if (it.isSuccess()) it.data!! - else return AuthObject.GPlayAuth(it, user) + savedAuth ?: run { + // if no saved data, then generate new auth data. + generateAuthData().let { + if (it.isSuccess()) it.data!! + else return AuthObject.GPlayAuth(it, user) + } } - } - ) + ) loginDataStore.saveAuthData(authData) return AuthObject.GPlayAuth(ResultSupreme.Success(authData), user) @@ -226,12 +226,12 @@ class LoginSourceGPlay @Inject constructor( } else { val message = "Validating AuthData failed.\n" + - "Network code: ${playResponse?.code}\n" + - "Success: ${playResponse?.isSuccessful}" + - playResponse?.errorString?.run { - if (isNotBlank()) "\nError message: $this" - else "" - } + "Network code: ${playResponse?.code}\n" + + "Success: ${playResponse?.isSuccessful}" + + playResponse?.errorString?.run { + if (isNotBlank()) "\nError message: $this" + else "" + } ResultSupreme.Error( message, @@ -241,18 +241,8 @@ class LoginSourceGPlay @Inject constructor( } override suspend fun validateAuth(): AuthObject? { - val authData = getSavedAuthData() - - // validate authData and save it if nothing is saved (first time use.) - authData?.let { - validateAuthData(authData).run { - if (isSuccess()) { - saveAuthData(authData) - } - return AuthObject.GPlayAuth(this, user) - } - } - - return null + clearSavedAuth() + val authObject = getAuthObject() + return if (authObject.result.isSuccess()) authObject else null } } -- GitLab