diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 7e5ec41..9f3f11e 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -37,3 +37,9 @@ # OkHttp -dontwarn okhttp3.** -dontwarn okio.** + +# Google Tink / Error Prone annotations +-dontwarn com.google.errorprone.annotations.CanIgnoreReturnValue +-dontwarn com.google.errorprone.annotations.CheckReturnValue +-dontwarn com.google.errorprone.annotations.Immutable +-dontwarn com.google.errorprone.annotations.RestrictedApi diff --git a/app/src/main/java/com/fastmask/data/api/JmapApi.kt b/app/src/main/java/com/fastmask/data/api/JmapApi.kt index a593863..cb28e13 100644 --- a/app/src/main/java/com/fastmask/data/api/JmapApi.kt +++ b/app/src/main/java/com/fastmask/data/api/JmapApi.kt @@ -33,8 +33,14 @@ class JmapApi @Inject constructor( fun getApiUrl(): String = cachedSession?.apiUrl ?: JmapService.FASTMAIL_API_URL + private suspend fun ensureSession(token: String): String { + cachedAccountId?.let { return it } + getSession(token).getOrThrow() + return cachedAccountId ?: throw IllegalStateException("Failed to get account ID from session") + } + suspend fun getMaskedEmails(token: String): Result> = runCatching { - val accountId = cachedAccountId ?: throw IllegalStateException("Session not initialized") + val accountId = ensureSession(token) val authHeader = "Bearer $token" val methodCall = buildJsonArray { @@ -63,7 +69,7 @@ class JmapApi @Inject constructor( token: String, create: MaskedEmailCreate ): Result = runCatching { - val accountId = cachedAccountId ?: throw IllegalStateException("Session not initialized") + val accountId = ensureSession(token) val authHeader = "Bearer $token" val createObject = buildJsonObject { @@ -104,7 +110,7 @@ class JmapApi @Inject constructor( id: String, update: MaskedEmailUpdate ): Result = runCatching { - val accountId = cachedAccountId ?: throw IllegalStateException("Session not initialized") + val accountId = ensureSession(token) val authHeader = "Bearer $token" val updateObject = buildJsonObject { @@ -140,7 +146,7 @@ class JmapApi @Inject constructor( } suspend fun deleteMaskedEmail(token: String, id: String): Result = runCatching { - val accountId = cachedAccountId ?: throw IllegalStateException("Session not initialized") + val accountId = ensureSession(token) val authHeader = "Bearer $token" val methodCall = buildJsonArray { @@ -227,6 +233,11 @@ class JmapApi @Inject constructor( setResponse.notUpdated?.get(id)?.let { error -> throw JmapException("Failed to update: ${error.type} - ${error.description}") } + + // Verify the update actually succeeded + if (setResponse.updated?.containsKey(id) != true) { + throw JmapException("Update not confirmed by server - ID not found in updated response") + } } private fun parseSetResponseDestroyed(response: JmapResponse, id: String) { diff --git a/app/src/main/java/com/fastmask/ui/auth/LoginViewModel.kt b/app/src/main/java/com/fastmask/ui/auth/LoginViewModel.kt index 000e6fa..fc9ba78 100644 --- a/app/src/main/java/com/fastmask/ui/auth/LoginViewModel.kt +++ b/app/src/main/java/com/fastmask/ui/auth/LoginViewModel.kt @@ -30,7 +30,8 @@ class LoginViewModel @Inject constructor( } fun login() { - val token = _uiState.value.token.trim() + // Remove all whitespace characters (spaces, newlines, tabs) from the token + val token = _uiState.value.token.filterNot { it.isWhitespace() } if (token.isBlank()) { _uiState.update { it.copy(error = "Please enter your API token") } return diff --git a/app/src/main/java/com/fastmask/ui/detail/MaskedEmailDetailViewModel.kt b/app/src/main/java/com/fastmask/ui/detail/MaskedEmailDetailViewModel.kt index 44b29a9..01393d2 100644 --- a/app/src/main/java/com/fastmask/ui/detail/MaskedEmailDetailViewModel.kt +++ b/app/src/main/java/com/fastmask/ui/detail/MaskedEmailDetailViewModel.kt @@ -52,6 +52,7 @@ class MaskedEmailDetailViewModel @Inject constructor( _uiState.update { it.copy( isLoading = false, + isUpdating = false, email = email, editedDescription = email.description ?: "", editedForDomain = email.forDomain ?: "", @@ -62,6 +63,7 @@ class MaskedEmailDetailViewModel @Inject constructor( _uiState.update { it.copy( isLoading = false, + isUpdating = false, error = "Email not found" ) } @@ -71,6 +73,7 @@ class MaskedEmailDetailViewModel @Inject constructor( _uiState.update { it.copy( isLoading = false, + isUpdating = false, error = error.message ?: "Failed to load email" ) }