diff --git a/app/src/main/java/com/example/login_v3/data/di/NetworkModule.kt b/app/src/main/java/com/example/login_v3/data/di/NetworkModule.kt index 92d5fc0..b8b7c9c 100644 --- a/app/src/main/java/com/example/login_v3/data/di/NetworkModule.kt +++ b/app/src/main/java/com/example/login_v3/data/di/NetworkModule.kt @@ -2,6 +2,7 @@ package com.example.login_v3.data.di import androidx.lifecycle.ViewModel import com.example.login_v3.data.api.TecnologiaApi +import com.example.login_v3.data.repository.basic.TokenAuthenticator import com.example.login_v3.data.repository.basic.TokenManager import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory @@ -12,7 +13,9 @@ import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.components.SingletonComponent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.runBlocking +import okhttp3.Interceptor import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory @@ -32,39 +35,49 @@ object NetworkModule { .build() } + // TokenInterceptor @Provides @Singleton - fun provideOkHttpClient(tokenManager: TokenManager): OkHttpClient { + fun provideTokenInterceptor(tokenManager: TokenManager): Interceptor { + return Interceptor { chain -> + val originalRequest = chain.request() + val path = originalRequest.url.encodedPath + + // 如果是認證相關的 API,直接放行,不附加 Token Header + if (path.contains("api/auth/login") || + path.contains("api/auth/register") || + path.contains("api/auth/refresh") || + path.contains("api/health")) { + return@Interceptor chain.proceed(originalRequest) + } + + // 在背景執行緒中安全地獲取當前 Token + val token = runBlocking(Dispatchers.IO) { + tokenManager.currentAccessToken.firstOrNull() + } + + val requestBuilder = originalRequest.newBuilder() + if (!token.isNullOrEmpty()) { + requestBuilder.addHeader("Authorization", "Bearer $token") + } + + // ⚠️ 注意:這裡不再手動處理 401 logout,純粹放行交給 Authenticator 處理 + chain.proceed(requestBuilder.build()) + } + } + + // 2. 配置 OkHttpClient,把 Interceptor 和 Authenticator 綁進去 + @Provides + @Singleton + fun provideOkHttpClient( + tokenInterceptor: Interceptor, + tokenAuthenticator: TokenAuthenticator // 👈 注入前面實作好的 Authenticator + ): OkHttpClient { return OkHttpClient.Builder() .connectTimeout(15, TimeUnit.SECONDS) .readTimeout(15, TimeUnit.SECONDS) - .addInterceptor { chain -> - // 在 OkHttp 的背景執行緒中安全地進行同步等待 - val (token, userId) = runBlocking(Dispatchers.IO) { - val t = tokenManager.currentAccessToken.first() - val id = tokenManager.currentUserId.first() - t to id - } - - val requestBuilder = chain.request().newBuilder() - token?.let { - requestBuilder.addHeader("Authorization", "Bearer $it") - } - - val response = chain.proceed(requestBuilder.build()) - - // 處理 Token 失效 (401) - if (response.code == 401) { - userId?.let { id -> - // 使用 Dispatchers.IO 確保不會卡死主執行緒 - runBlocking(Dispatchers.IO) { - tokenManager.logout(id) - } - } - } - - response - } + .addInterceptor(tokenInterceptor) // 負責幫一般請求加上 Token + .authenticator(tokenAuthenticator) // 負責在 401 時自動刷新 Token .build() }