mirror of
https://github.com/nacabaro/vbhelper.git
synced 2026-06-05 13:52:54 +00:00
API calls now use X-Session-Token.
This commit is contained in:
parent
efa4bab144
commit
9365bc0215
@ -0,0 +1,31 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.battle
|
||||||
|
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Response
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OkHttp interceptor that adds Authorization header to API requests.
|
||||||
|
* Skips adding header for auth endpoints.
|
||||||
|
*/
|
||||||
|
class AuthInterceptor(private val token: String) : Interceptor {
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val originalRequest = chain.request()
|
||||||
|
|
||||||
|
// Skip adding auth header for auth endpoints
|
||||||
|
if (originalRequest.url.encodedPath.startsWith("/api/auth")) {
|
||||||
|
return chain.proceed(originalRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add authentication header for game endpoints
|
||||||
|
// Use X-Session-Token header (preferred) or Authorization: Bearer
|
||||||
|
val authenticatedRequest = originalRequest.newBuilder()
|
||||||
|
.header("X-Session-Token", token)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
// Debug: Log which header is being used (first few chars of token for security)
|
||||||
|
val tokenPreview = if (token.length > 8) "${token.take(4)}...${token.takeLast(4)}" else "***"
|
||||||
|
println("AuthInterceptor: Adding X-Session-Token header (token: $tokenPreview)")
|
||||||
|
|
||||||
|
return chain.proceed(authenticatedRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,5 +7,8 @@ import retrofit2.http.POST
|
|||||||
interface AuthService {
|
interface AuthService {
|
||||||
@POST("api/auth/validate")
|
@POST("api/auth/validate")
|
||||||
fun validate(@Body request: AuthenticateRequest): Call<AuthenticateResponse>
|
fun validate(@Body request: AuthenticateRequest): Call<AuthenticateResponse>
|
||||||
|
|
||||||
|
@POST("api/auth/login")
|
||||||
|
fun login(@Body request: AuthenticateRequest): Call<AuthenticateResponse>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@ data class UserInfo(
|
|||||||
data class AuthenticateResponse(
|
data class AuthenticateResponse(
|
||||||
val success: Boolean,
|
val success: Boolean,
|
||||||
val message: String? = null,
|
val message: String? = null,
|
||||||
val userInfo: UserInfo? = null
|
val userInfo: UserInfo? = null,
|
||||||
|
val sessionToken: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -7,18 +7,131 @@ import retrofit2.*
|
|||||||
import retrofit2.converter.gson.GsonConverterFactory
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import com.github.nacabaro.vbhelper.battle.BattleAuthContainer
|
||||||
|
|
||||||
class RetrofitHelper {
|
class RetrofitHelper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an OkHttpClient with authentication interceptor for game endpoints.
|
||||||
|
* Requires a non-null, non-empty token.
|
||||||
|
*/
|
||||||
|
private fun createAuthenticatedClient(token: String): OkHttpClient {
|
||||||
|
val loggingInterceptor = HttpLoggingInterceptor().apply {
|
||||||
|
level = HttpLoggingInterceptor.Level.BODY
|
||||||
|
}
|
||||||
|
|
||||||
|
return OkHttpClient.Builder()
|
||||||
|
.addInterceptor(AuthInterceptor(token))
|
||||||
|
.addInterceptor(loggingInterceptor)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the session token from AuthRepository for API calls.
|
||||||
|
* Falls back to nacatech token if session token is not available (backward compatibility).
|
||||||
|
*/
|
||||||
|
private fun getAuthToken(context: Context): String? {
|
||||||
|
return try {
|
||||||
|
val authContainer = BattleAuthContainer(context)
|
||||||
|
runBlocking {
|
||||||
|
// Prefer session token, fall back to nacatech token for backward compatibility
|
||||||
|
val sessionToken = authContainer.authRepository.sessionToken.first()
|
||||||
|
if (!sessionToken.isNullOrEmpty()) {
|
||||||
|
println("RetrofitHelper: Using sessionToken for API call")
|
||||||
|
sessionToken
|
||||||
|
} else {
|
||||||
|
// Fallback to nacatech token (slower, but works)
|
||||||
|
val nacatechToken = authContainer.authRepository.authToken.first()
|
||||||
|
if (!nacatechToken.isNullOrEmpty()) {
|
||||||
|
println("RetrofitHelper: No sessionToken found, falling back to nacatechToken")
|
||||||
|
}
|
||||||
|
nacatechToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("RetrofitHelper: Error getting auth token: ${e.message}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Retrofit instance with authentication for game endpoints.
|
||||||
|
*/
|
||||||
|
private fun createAuthenticatedRetrofit(context: Context): Retrofit? {
|
||||||
|
val token = getAuthToken(context)
|
||||||
|
if (token.isNullOrEmpty()) {
|
||||||
|
println("RetrofitHelper: No auth token available")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val client = createAuthenticatedClient(token)
|
||||||
|
return Retrofit.Builder()
|
||||||
|
.baseUrl("http://battle.io-void.com:8080/")
|
||||||
|
.client(client)
|
||||||
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles HTTP error responses (401, 403, 429).
|
||||||
|
* For 401/403, clears authentication state to trigger re-authentication.
|
||||||
|
*/
|
||||||
|
private fun handleErrorResponse(context: Context, response: Response<*>, errorMessage: String) {
|
||||||
|
when (response.code()) {
|
||||||
|
401 -> {
|
||||||
|
println("RetrofitHelper: Authentication failed (401) - token may be expired")
|
||||||
|
clearAuthAndNotify(context, "Authentication failed. Please log in again.")
|
||||||
|
}
|
||||||
|
403 -> {
|
||||||
|
println("RetrofitHelper: Access forbidden (403) - token may be expired or invalid")
|
||||||
|
// 403 could mean expired token, so clear auth state to trigger re-authentication
|
||||||
|
clearAuthAndNotify(context, "Session expired. Please log in again.")
|
||||||
|
}
|
||||||
|
429 -> {
|
||||||
|
println("RetrofitHelper: Rate limit exceeded (429)")
|
||||||
|
Toast.makeText(context, "Too many requests. Please wait a moment.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
println("RetrofitHelper: API error (${response.code()}): $errorMessage")
|
||||||
|
Toast.makeText(context, "Request failed: ${response.code()}", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears authentication state and shows a message.
|
||||||
|
* This will trigger BattlesScreen to detect the auth state change and open the login page.
|
||||||
|
*/
|
||||||
|
private fun clearAuthAndNotify(context: Context, message: String) {
|
||||||
|
try {
|
||||||
|
val authContainer = BattleAuthContainer(context)
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
authContainer.authRepository.logout()
|
||||||
|
println("RetrofitHelper: Cleared authentication state due to expired/invalid token")
|
||||||
|
}
|
||||||
|
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("RetrofitHelper: Error clearing auth state: ${e.message}")
|
||||||
|
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getOpponents(context: Context, stage: String, callback: (OpponentsDataModel) -> Unit) {
|
fun getOpponents(context: Context, stage: String, callback: (OpponentsDataModel) -> Unit) {
|
||||||
//println("RetrofitHelper: Starting API call for stage: $stage")
|
//println("RetrofitHelper: Starting API call for stage: $stage")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create a Retrofit instance with the base URL and
|
// Create an authenticated Retrofit instance
|
||||||
// a GsonConverterFactory for parsing the response.
|
val retrofit = createAuthenticatedRetrofit(context)
|
||||||
val retrofit: Retrofit = Retrofit.Builder().baseUrl("http://192.168.0.230:8080/").addConverterFactory(
|
if (retrofit == null) {
|
||||||
GsonConverterFactory.create()).build()
|
println("RetrofitHelper: Cannot create authenticated Retrofit - no token available")
|
||||||
//println("RetrofitHelper: Retrofit instance created")
|
Toast.makeText(context, "Authentication required. Please log in.", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Create an ApiService instance from the Retrofit instance.
|
// Create an ApiService instance from the Retrofit instance.
|
||||||
val service: OpponentService = retrofit.create<OpponentService>(OpponentService::class.java)
|
val service: OpponentService = retrofit.create<OpponentService>(OpponentService::class.java)
|
||||||
@ -47,13 +160,16 @@ class RetrofitHelper {
|
|||||||
val opponentsList: OpponentsDataModel = response.body() as OpponentsDataModel
|
val opponentsList: OpponentsDataModel = response.body() as OpponentsDataModel
|
||||||
callback(opponentsList)
|
callback(opponentsList)
|
||||||
} else {
|
} else {
|
||||||
println("RetrofitHelper: Response not successful - Error: ${response.errorBody()?.string()}")
|
val errorBody = response.errorBody()?.string()
|
||||||
|
println("RetrofitHelper: Response not successful - Error: $errorBody")
|
||||||
|
handleErrorResponse(context, response, errorBody ?: "Unknown error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
println("RetrofitHelper: Exception in getOpponents: ${e.message}")
|
println("RetrofitHelper: Exception in getOpponents: ${e.message}")
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
Toast.makeText(context, "Request failed: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +178,7 @@ class RetrofitHelper {
|
|||||||
|
|
||||||
// Create a Retrofit instance with the base URL and
|
// Create a Retrofit instance with the base URL and
|
||||||
// a GsonConverterFactory for parsing the response.
|
// a GsonConverterFactory for parsing the response.
|
||||||
val retrofit: Retrofit = Retrofit.Builder().baseUrl("http://192.168.0.230:8080/").addConverterFactory(
|
val retrofit: Retrofit = Retrofit.Builder().baseUrl("http://battle.io-void.com:8080/").addConverterFactory(
|
||||||
GsonConverterFactory.create()).build()
|
GsonConverterFactory.create()).build()
|
||||||
|
|
||||||
// Create an ApiService instance from the Retrofit instance.
|
// Create an ApiService instance from the Retrofit instance.
|
||||||
@ -102,7 +218,7 @@ class RetrofitHelper {
|
|||||||
|
|
||||||
// Create a Retrofit instance with the base URL and
|
// Create a Retrofit instance with the base URL and
|
||||||
// a GsonConverterFactory for parsing the response.
|
// a GsonConverterFactory for parsing the response.
|
||||||
val retrofit: Retrofit = Retrofit.Builder().baseUrl("http://192.168.0.230:8080/").addConverterFactory(
|
val retrofit: Retrofit = Retrofit.Builder().baseUrl("http://battle.io-void.com:8080/").addConverterFactory(
|
||||||
GsonConverterFactory.create()).build()
|
GsonConverterFactory.create()).build()
|
||||||
|
|
||||||
// Create an ApiService instance from the Retrofit instance.
|
// Create an ApiService instance from the Retrofit instance.
|
||||||
@ -141,42 +257,58 @@ class RetrofitHelper {
|
|||||||
|
|
||||||
fun getPVPWinner(context: Context, apiStage: Int, playerID: Int, playerDigi: String, playerStage: Int, critBar: Int, opponentDigi: String, opponentStage: Int, callback: (PVPDataModel) -> Unit) {
|
fun getPVPWinner(context: Context, apiStage: Int, playerID: Int, playerDigi: String, playerStage: Int, critBar: Int, opponentDigi: String, opponentStage: Int, callback: (PVPDataModel) -> Unit) {
|
||||||
|
|
||||||
// Create a Retrofit instance with the base URL and
|
try {
|
||||||
// a GsonConverterFactory for parsing the response.
|
// Create an authenticated Retrofit instance
|
||||||
val retrofit: Retrofit = Retrofit.Builder().baseUrl("http://192.168.0.230:8080/").addConverterFactory(
|
val retrofit = createAuthenticatedRetrofit(context)
|
||||||
GsonConverterFactory.create()).build()
|
if (retrofit == null) {
|
||||||
|
println("RetrofitHelper: Cannot create authenticated Retrofit - no token available")
|
||||||
// Create an ApiService instance from the Retrofit instance.
|
Toast.makeText(context, "Authentication required. Please log in.", Toast.LENGTH_SHORT).show()
|
||||||
val service: PVPService = retrofit.create<PVPService>(PVPService::class.java)
|
return
|
||||||
|
|
||||||
// Call the getwinner() method of the ApiService
|
|
||||||
// to make an API request.
|
|
||||||
val call: Call<PVPDataModel> = service.getwinner(apiStage, playerID, playerDigi, playerStage, critBar, opponentDigi, opponentStage)
|
|
||||||
|
|
||||||
// Use the enqueue() method of the Call object to
|
|
||||||
// make an asynchronous API request.
|
|
||||||
call.enqueue(object : Callback<PVPDataModel> {
|
|
||||||
// This is an anonymous inner class that implements the Callback interface.
|
|
||||||
|
|
||||||
override fun onFailure(call: Call<PVPDataModel>, t: Throwable) {
|
|
||||||
// This method is called when the API request fails.
|
|
||||||
Toast.makeText(context, "Request Fail", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResponse(call: Call<PVPDataModel>, response: Response<PVPDataModel>) {
|
// Create an ApiService instance from the Retrofit instance.
|
||||||
// This method is called when the API response is received successfully.
|
val service: PVPService = retrofit.create<PVPService>(PVPService::class.java)
|
||||||
|
|
||||||
if(response.isSuccessful){
|
// Call the getwinner() method of the ApiService
|
||||||
// If the response is successful, parse the
|
// to make an API request.
|
||||||
// response body to a DataModel object.
|
val call: Call<PVPDataModel> = service.getwinner(apiStage, playerID, playerDigi, playerStage, critBar, opponentDigi, opponentStage)
|
||||||
val apiResults: PVPDataModel = response.body() as PVPDataModel
|
|
||||||
|
|
||||||
// Call the callback function with the DataModel
|
// Use the enqueue() method of the Call object to
|
||||||
// object as a parameter.
|
// make an asynchronous API request.
|
||||||
callback(apiResults)
|
call.enqueue(object : Callback<PVPDataModel> {
|
||||||
|
// This is an anonymous inner class that implements the Callback interface.
|
||||||
|
|
||||||
|
override fun onFailure(call: Call<PVPDataModel>, t: Throwable) {
|
||||||
|
// This method is called when the API request fails.
|
||||||
|
println("RetrofitHelper: PVP API call failed: ${t.message}")
|
||||||
|
t.printStackTrace()
|
||||||
|
Toast.makeText(context, "Request Fail", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
override fun onResponse(call: Call<PVPDataModel>, response: Response<PVPDataModel>) {
|
||||||
|
// This method is called when the API response is received successfully.
|
||||||
|
println("RetrofitHelper: PVP API response received - Code: ${response.code()}")
|
||||||
|
|
||||||
|
if(response.isSuccessful){
|
||||||
|
// If the response is successful, parse the
|
||||||
|
// response body to a DataModel object.
|
||||||
|
val apiResults: PVPDataModel = response.body() as PVPDataModel
|
||||||
|
|
||||||
|
// Call the callback function with the DataModel
|
||||||
|
// object as a parameter.
|
||||||
|
callback(apiResults)
|
||||||
|
} else {
|
||||||
|
val errorBody = response.errorBody()?.string()
|
||||||
|
println("RetrofitHelper: PVP API response not successful - Code: ${response.code()}, Error: $errorBody")
|
||||||
|
handleErrorResponse(context, response, errorBody ?: "Unknown error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("RetrofitHelper: Exception in getPVPWinner: ${e.message}")
|
||||||
|
e.printStackTrace()
|
||||||
|
Toast.makeText(context, "Request failed: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun authenticate(context: Context, token: String, callback: (AuthenticateResponse) -> Unit) {
|
fun authenticate(context: Context, token: String, callback: (AuthenticateResponse) -> Unit) {
|
||||||
@ -198,14 +330,15 @@ class RetrofitHelper {
|
|||||||
.build()
|
.build()
|
||||||
|
|
||||||
val retrofit: Retrofit = Retrofit.Builder()
|
val retrofit: Retrofit = Retrofit.Builder()
|
||||||
.baseUrl("http://192.168.0.230:8080/")
|
.baseUrl("http://battle.io-void.com:8080/")
|
||||||
.client(okHttpClient)
|
.client(okHttpClient)
|
||||||
.addConverterFactory(GsonConverterFactory.create())
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val service: AuthService = retrofit.create<AuthService>(AuthService::class.java)
|
val service: AuthService = retrofit.create<AuthService>(AuthService::class.java)
|
||||||
val request = AuthenticateRequest(userToken = token)
|
val request = AuthenticateRequest(userToken = token)
|
||||||
val call: Call<AuthenticateResponse> = service.validate(request)
|
// Use login endpoint instead of validate to get sessionToken
|
||||||
|
val call: Call<AuthenticateResponse> = service.login(request)
|
||||||
|
|
||||||
call.enqueue(object : Callback<AuthenticateResponse> {
|
call.enqueue(object : Callback<AuthenticateResponse> {
|
||||||
override fun onFailure(call: Call<AuthenticateResponse>, t: Throwable) {
|
override fun onFailure(call: Call<AuthenticateResponse>, t: Throwable) {
|
||||||
|
|||||||
@ -72,6 +72,7 @@ import com.github.nacabaro.vbhelper.battle.AnimatedSpriteImage
|
|||||||
import com.github.nacabaro.vbhelper.battle.HitEffectOverlay
|
import com.github.nacabaro.vbhelper.battle.HitEffectOverlay
|
||||||
import com.github.nacabaro.vbhelper.battle.BattleAuthContainer
|
import com.github.nacabaro.vbhelper.battle.BattleAuthContainer
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
//import kotlin.math.sin
|
//import kotlin.math.sin
|
||||||
@ -805,7 +806,7 @@ fun MiddleBattleView(
|
|||||||
) {
|
) {
|
||||||
// Enemy HP bar (top)
|
// Enemy HP bar (top)
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
progress = battleSystem.opponentHP / (opponentCharacter?.baseHp?.toFloat() ?: 100f),
|
progress = { battleSystem.opponentHP / (opponentCharacter?.baseHp?.toFloat() ?: 100f) },
|
||||||
modifier = getLandscapeModifier(),
|
modifier = getLandscapeModifier(),
|
||||||
color = Color.Red,
|
color = Color.Red,
|
||||||
trackColor = Color.Gray
|
trackColor = Color.Gray
|
||||||
@ -1017,7 +1018,7 @@ fun MiddleBattleView(
|
|||||||
) {
|
) {
|
||||||
// Critical bar
|
// Critical bar
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
progress = battleSystem.critBarProgress / 100f,
|
progress = { battleSystem.critBarProgress / 100f },
|
||||||
modifier = getLandscapeModifier(),
|
modifier = getLandscapeModifier(),
|
||||||
color = Color.Yellow,
|
color = Color.Yellow,
|
||||||
trackColor = Color.Gray
|
trackColor = Color.Gray
|
||||||
@ -1035,7 +1036,7 @@ fun MiddleBattleView(
|
|||||||
) {
|
) {
|
||||||
// Player HP bar
|
// Player HP bar
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
progress = battleSystem.playerHP / (activeCharacter?.baseHp?.toFloat() ?: 100f),
|
progress = { battleSystem.playerHP / (activeCharacter?.baseHp?.toFloat() ?: 100f) },
|
||||||
modifier = getLandscapeModifier(),
|
modifier = getLandscapeModifier(),
|
||||||
color = Color.Green,
|
color = Color.Green,
|
||||||
trackColor = Color.Gray
|
trackColor = Color.Gray
|
||||||
@ -1215,7 +1216,7 @@ fun PlayerBattleView(
|
|||||||
) {
|
) {
|
||||||
// Health bar
|
// Health bar
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
progress = battleSystem.playerHP / (activeCharacter?.baseHp?.toFloat() ?: 100f),
|
progress = { battleSystem.playerHP / (activeCharacter?.baseHp?.toFloat() ?: 100f) },
|
||||||
modifier = getLandscapeModifier(),
|
modifier = getLandscapeModifier(),
|
||||||
color = Color.Green,
|
color = Color.Green,
|
||||||
trackColor = Color.Gray
|
trackColor = Color.Gray
|
||||||
@ -1401,7 +1402,7 @@ fun EnemyBattleView(
|
|||||||
) {
|
) {
|
||||||
// Enemy HP bar
|
// Enemy HP bar
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
progress = battleSystem.opponentHP / (activeCharacter?.baseHp?.toFloat() ?: 100f),
|
progress = { battleSystem.opponentHP / (activeCharacter?.baseHp?.toFloat() ?: 100f) },
|
||||||
modifier = getLandscapeModifier(),
|
modifier = getLandscapeModifier(),
|
||||||
color = Color.Red,
|
color = Color.Red,
|
||||||
trackColor = Color.Gray
|
trackColor = Color.Gray
|
||||||
@ -1765,11 +1766,20 @@ fun BattlesScreen() {
|
|||||||
// Exchange token with battle server
|
// Exchange token with battle server
|
||||||
RetrofitHelper().authenticate(context, token) { response ->
|
RetrofitHelper().authenticate(context, token) { response ->
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
// Token already marked as processed before API call, just extract userId
|
// Extract userId and sessionToken from response
|
||||||
// Extract userId from response
|
|
||||||
val extractedUserId = response.userInfo?.userId?.toLongOrNull()
|
val extractedUserId = response.userInfo?.userId?.toLongOrNull()
|
||||||
|
val sessionToken = response.sessionToken
|
||||||
|
|
||||||
|
println("BATTLESCREEN: Authentication successful, userId: $extractedUserId, sessionToken: ${if (sessionToken != null) "present" else "missing"}")
|
||||||
|
|
||||||
|
// Store both nacatech token (for re-auth) and sessionToken (for API calls)
|
||||||
kotlinx.coroutines.CoroutineScope(Dispatchers.IO).launch {
|
kotlinx.coroutines.CoroutineScope(Dispatchers.IO).launch {
|
||||||
battleAuthContainer.authRepository.setAuthenticated(true, token, extractedUserId)
|
battleAuthContainer.authRepository.setAuthenticated(
|
||||||
|
isAuthenticated = true,
|
||||||
|
nacatechToken = token,
|
||||||
|
sessionToken = sessionToken,
|
||||||
|
userId = extractedUserId
|
||||||
|
)
|
||||||
}
|
}
|
||||||
// Update UI state on main thread
|
// Update UI state on main thread
|
||||||
kotlinx.coroutines.CoroutineScope(Dispatchers.Main).launch {
|
kotlinx.coroutines.CoroutineScope(Dispatchers.Main).launch {
|
||||||
@ -1877,16 +1887,23 @@ fun BattlesScreen() {
|
|||||||
kotlinx.coroutines.CoroutineScope(Dispatchers.Main).launch {
|
kotlinx.coroutines.CoroutineScope(Dispatchers.Main).launch {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
val extractedUserId = response.userInfo?.userId?.toLongOrNull()
|
val extractedUserId = response.userInfo?.userId?.toLongOrNull()
|
||||||
// Update stored userId
|
val sessionToken = response.sessionToken
|
||||||
|
|
||||||
|
// Update stored userId and sessionToken
|
||||||
if (extractedUserId != null) {
|
if (extractedUserId != null) {
|
||||||
kotlinx.coroutines.CoroutineScope(Dispatchers.IO).launch {
|
kotlinx.coroutines.CoroutineScope(Dispatchers.IO).launch {
|
||||||
authRepository.setAuthenticated(true, storedToken, extractedUserId)
|
authRepository.setAuthenticated(
|
||||||
|
isAuthenticated = true,
|
||||||
|
nacatechToken = storedToken,
|
||||||
|
sessionToken = sessionToken,
|
||||||
|
userId = extractedUserId
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
isAuthenticated = true
|
isAuthenticated = true
|
||||||
isCheckingAuth = false
|
isCheckingAuth = false
|
||||||
userId = extractedUserId
|
userId = extractedUserId
|
||||||
println("BATTLESCREEN: Got userId from validation: $extractedUserId")
|
println("BATTLESCREEN: Got userId from validation: $extractedUserId, sessionToken: ${if (sessionToken != null) "present" else "missing"}")
|
||||||
} else {
|
} else {
|
||||||
println("BATTLESCREEN: Token validation failed: ${response.message}")
|
println("BATTLESCREEN: Token validation failed: ${response.message}")
|
||||||
// Check if it's a critical error that requires re-authentication
|
// Check if it's a critical error that requires re-authentication
|
||||||
@ -1999,6 +2016,28 @@ fun BattlesScreen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Watch auth repository state changes to detect when token is cleared (e.g., expired token)
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
battleAuthContainer.authRepository.isAuthenticated.collect { authState ->
|
||||||
|
if (!authState && isAuthenticated) {
|
||||||
|
// Auth state was cleared (e.g., by RetrofitHelper due to expired token)
|
||||||
|
println("BATTLESCREEN: Auth state cleared, triggering re-authentication")
|
||||||
|
isAuthenticated = false
|
||||||
|
isCheckingAuth = false
|
||||||
|
// Open auth URL to get a fresh token
|
||||||
|
val authUrl = "http://auth.nacatech.es/begin?app=443654920&redirect_uri=vbhelper://auth?token="
|
||||||
|
val authIntent = Intent(Intent.ACTION_VIEW, Uri.parse(authUrl))
|
||||||
|
try {
|
||||||
|
context.startActivity(authIntent)
|
||||||
|
println("BATTLESCREEN: Opened auth URL after token expiration: $authUrl")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("BATTLESCREEN: Failed to open auth URL: ${e.message}")
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Also check intent when authentication state changes
|
// Also check intent when authentication state changes
|
||||||
// Only process if it's a fresh ACTION_VIEW intent (deep link)
|
// Only process if it's a fresh ACTION_VIEW intent (deep link)
|
||||||
LaunchedEffect(isAuthenticated) {
|
LaunchedEffect(isAuthenticated) {
|
||||||
|
|||||||
@ -14,7 +14,8 @@ class AuthRepository(
|
|||||||
) {
|
) {
|
||||||
private companion object {
|
private companion object {
|
||||||
val IS_AUTHENTICATED = booleanPreferencesKey("is_authenticated")
|
val IS_AUTHENTICATED = booleanPreferencesKey("is_authenticated")
|
||||||
val AUTH_TOKEN = stringPreferencesKey("auth_token")
|
val AUTH_TOKEN = stringPreferencesKey("auth_token") // Nacatech token (for re-authentication)
|
||||||
|
val SESSION_TOKEN = stringPreferencesKey("session_token") // Session token (for API calls)
|
||||||
val USER_ID = longPreferencesKey("user_id")
|
val USER_ID = longPreferencesKey("user_id")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,16 +29,24 @@ class AuthRepository(
|
|||||||
preferences[AUTH_TOKEN]
|
preferences[AUTH_TOKEN]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val sessionToken: Flow<String?> = dataStore.data
|
||||||
|
.map { preferences ->
|
||||||
|
preferences[SESSION_TOKEN]
|
||||||
|
}
|
||||||
|
|
||||||
val userId: Flow<Long?> = dataStore.data
|
val userId: Flow<Long?> = dataStore.data
|
||||||
.map { preferences ->
|
.map { preferences ->
|
||||||
preferences[USER_ID]
|
preferences[USER_ID]
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setAuthenticated(isAuthenticated: Boolean, token: String? = null, userId: Long? = null) {
|
suspend fun setAuthenticated(isAuthenticated: Boolean, nacatechToken: String? = null, sessionToken: String? = null, userId: Long? = null) {
|
||||||
dataStore.edit { preferences ->
|
dataStore.edit { preferences ->
|
||||||
preferences[IS_AUTHENTICATED] = isAuthenticated
|
preferences[IS_AUTHENTICATED] = isAuthenticated
|
||||||
if (token != null) {
|
if (nacatechToken != null) {
|
||||||
preferences[AUTH_TOKEN] = token
|
preferences[AUTH_TOKEN] = nacatechToken
|
||||||
|
}
|
||||||
|
if (sessionToken != null) {
|
||||||
|
preferences[SESSION_TOKEN] = sessionToken
|
||||||
}
|
}
|
||||||
if (userId != null) {
|
if (userId != null) {
|
||||||
preferences[USER_ID] = userId
|
preferences[USER_ID] = userId
|
||||||
@ -49,6 +58,7 @@ class AuthRepository(
|
|||||||
dataStore.edit { preferences ->
|
dataStore.edit { preferences ->
|
||||||
preferences[IS_AUTHENTICATED] = false
|
preferences[IS_AUTHENTICATED] = false
|
||||||
preferences.remove(AUTH_TOKEN)
|
preferences.remove(AUTH_TOKEN)
|
||||||
|
preferences.remove(SESSION_TOKEN)
|
||||||
preferences.remove(USER_ID)
|
preferences.remove(USER_ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user