@@ -10,10 +10,18 @@ class WhitelistRepository(private val context: Context) {
1010 private val apiService = RetrofitClient .apiService
1111 private val tokenManager = TokenManager (context)
1212
13+ // In-memory cache for whitelist
14+ private var cachedWhitelist: List <String >? = null
15+ private var cacheTimestamp: Long = 0
16+ private val cacheDuration = 5 * 60 * 1000L // 5 minutes
17+
1318 companion object {
1419 private const val TAG = " WhitelistRepository"
1520 private const val ADMIN_USERNAME = " admin"
16- private const val ADMIN_PASSWORD = " pass123"
21+ // Updated password to match API documentation
22+ private const val ADMIN_PASSWORD = " Byf8\$ G&F*G8vGEfuhfuhfEHU!89f2qfiHT88%ffyutf7^s"
23+ private const val MAX_RETRY_ATTEMPTS = 3
24+ private const val INITIAL_RETRY_DELAY = 1000L // 1 second
1725 }
1826
1927 /* *
@@ -41,15 +49,22 @@ class WhitelistRepository(private val context: Context) {
4149 }
4250
4351 /* *
44- * Get whitelist URLs from server
52+ * Get whitelist URLs from server with caching and retry logic
4553 */
46- suspend fun getWhitelist (): Result <List <String >> {
54+ suspend fun getWhitelist (forceRefresh : Boolean = false ): Result <List <String >> {
4755 return withContext(Dispatchers .IO ) {
4856 try {
57+ // Check in-memory cache first
58+ if (! forceRefresh && cachedWhitelist != null &&
59+ System .currentTimeMillis() - cacheTimestamp < cacheDuration) {
60+ Log .d(TAG , " Returning cached whitelist: ${cachedWhitelist!! .size} URLs" )
61+ return @withContext Result .success(cachedWhitelist!! )
62+ }
63+
4964 // Check if token is valid, if not login first
5065 if (! tokenManager.isTokenValid()) {
5166 Log .d(TAG , " Token expired or missing, logging in..." )
52- val loginResult = login ()
67+ val loginResult = loginWithRetry ()
5368 if (loginResult.isFailure) {
5469 return @withContext Result .failure(loginResult.exceptionOrNull() ? : Exception (" Login failed" ))
5570 }
@@ -61,23 +76,106 @@ class WhitelistRepository(private val context: Context) {
6176 return @withContext Result .failure(Exception (" No authentication token" ))
6277 }
6378
64- val response = apiService.getWhitelist(authHeader)
79+ // Fetch with retry logic
80+ val response = retryApiCall { apiService.getWhitelist(authHeader) }
81+
6582 if (response.isSuccessful && response.body() != null ) {
6683 val urls = response.body()!! .urls
84+ // Update cache
85+ cachedWhitelist = urls
86+ cacheTimestamp = System .currentTimeMillis()
6787 Log .d(TAG , " Whitelist fetched successfully: ${urls.size} URLs" )
6888 Result .success(urls)
89+ } else if (response.code() == 401 ) {
90+ // Token might be invalid, try re-login and retry
91+ Log .d(TAG , " Received 401, attempting re-login..." )
92+ val loginResult = loginWithRetry()
93+ if (loginResult.isSuccess) {
94+ val newAuthHeader = tokenManager.getAuthHeader()!!
95+ val retryResponse = apiService.getWhitelist(newAuthHeader)
96+ if (retryResponse.isSuccessful && retryResponse.body() != null ) {
97+ val urls = retryResponse.body()!! .urls
98+ cachedWhitelist = urls
99+ cacheTimestamp = System .currentTimeMillis()
100+ Log .d(TAG , " Whitelist fetched after re-login: ${urls.size} URLs" )
101+ return @withContext Result .success(urls)
102+ }
103+ }
104+ Result .failure(Exception (" Authentication failed" ))
69105 } else {
70106 val error = " Failed to fetch whitelist: ${response.code()} "
71107 Log .e(TAG , error)
72108 Result .failure(Exception (error))
73109 }
74110 } catch (e: Exception ) {
75111 Log .e(TAG , " Error fetching whitelist: ${e.message} " , e)
76- Result .failure(e)
112+ // Return cached data if available even on error
113+ if (cachedWhitelist != null ) {
114+ Log .d(TAG , " Returning stale cache due to error" )
115+ Result .success(cachedWhitelist!! )
116+ } else {
117+ Result .failure(e)
118+ }
77119 }
78120 }
79121 }
80122
123+ /* *
124+ * Login with exponential backoff retry
125+ */
126+ private suspend fun loginWithRetry (): Result <String > {
127+ var lastException: Exception ? = null
128+ repeat(MAX_RETRY_ATTEMPTS ) { attempt ->
129+ try {
130+ val result = login()
131+ if (result.isSuccess) {
132+ return result
133+ }
134+ lastException = result.exceptionOrNull() as ? Exception
135+ } catch (e: Exception ) {
136+ lastException = e
137+ }
138+
139+ if (attempt < MAX_RETRY_ATTEMPTS - 1 ) {
140+ val delay = INITIAL_RETRY_DELAY * (1 shl attempt) // Exponential backoff
141+ Log .d(TAG , " Login attempt ${attempt + 1 } failed, retrying in ${delay} ms..." )
142+ kotlinx.coroutines.delay(delay)
143+ }
144+ }
145+ return Result .failure(lastException ? : Exception (" Login failed after $MAX_RETRY_ATTEMPTS attempts" ))
146+ }
147+
148+ /* *
149+ * Generic retry logic for API calls
150+ */
151+ private suspend fun <T > retryApiCall (
152+ apiCall : suspend () -> retrofit2.Response <T >
153+ ): retrofit2.Response <T > {
154+ var lastException: Exception ? = null
155+ repeat(MAX_RETRY_ATTEMPTS ) { attempt ->
156+ try {
157+ return apiCall()
158+ } catch (e: Exception ) {
159+ lastException = e
160+ if (attempt < MAX_RETRY_ATTEMPTS - 1 ) {
161+ val delay = INITIAL_RETRY_DELAY * (1 shl attempt)
162+ Log .d(TAG , " API call attempt ${attempt + 1 } failed, retrying in ${delay} ms..." )
163+ kotlinx.coroutines.delay(delay)
164+ }
165+ }
166+ }
167+ throw lastException ? : Exception (" API call failed after $MAX_RETRY_ATTEMPTS attempts" )
168+ }
169+
170+ /* *
171+ * Clear the in-memory cache
172+ */
173+ fun clearCache () {
174+ cachedWhitelist = null
175+ cacheTimestamp = 0
176+ Log .d(TAG , " Cache cleared" )
177+ }
178+
81179 /* *
82180 * Add URL to whitelist
83181 */
0 commit comments