Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
package io.element.android.libraries.matrix.impl

import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.matrix.impl.mapper.toSessionData
import io.element.android.libraries.matrix.impl.paths.getSessionPaths
import io.element.android.libraries.matrix.impl.util.anonymizedTokens
import io.element.android.libraries.sessionstorage.api.SessionStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import org.matrix.rustcomponents.sdk.ClientDelegate
import org.matrix.rustcomponents.sdk.ClientSessionDelegate
Expand All @@ -22,21 +22,20 @@
import java.lang.ref.WeakReference
import java.util.concurrent.atomic.AtomicBoolean

private val loggerTag = LoggerTag("RustClientSessionDelegate")

/**
* This class is responsible for handling the session data for the Rust SDK.
*
* It implements both [ClientSessionDelegate] and [ClientDelegate] to react to session data updates and auth errors.
*
* IMPORTANT: you must set the [client] property as soon as possible so [didReceiveAuthError] can work properly.
*/
@OptIn(ExperimentalCoroutinesApi::class)
class RustClientSessionDelegate(
private val sessionStore: SessionStore,
private val appCoroutineScope: CoroutineScope,
coroutineDispatchers: CoroutineDispatchers,
) : ClientSessionDelegate, ClientDelegate {
private val clientLog = Timber.tag("$this")

// Used to ensure several calls to `didReceiveAuthError` don't trigger multiple logouts
private val isLoggingOut = AtomicBoolean(false)

Expand Down Expand Up @@ -64,7 +63,7 @@
appCoroutineScope.launch(updateTokensDispatcher) {
val existingData = sessionStore.getSession(session.userId) ?: return@launch
val (anonymizedAccessToken, anonymizedRefreshToken) = session.anonymizedTokens()
clientLog.d(
Timber.tag(loggerTag.value).d(
"Saving new session data with token: access token '$anonymizedAccessToken' and refresh token '$anonymizedRefreshToken'. " +
"Was token valid: ${existingData.isTokenValid}"
)
Expand All @@ -75,48 +74,48 @@
sessionPaths = existingData.getSessionPaths(),
)
sessionStore.updateData(newData)
clientLog.d("Saved new session data with access token: '$anonymizedAccessToken'.")
Timber.tag(loggerTag.value).d("Saved new session data with access token: '$anonymizedAccessToken'.")
}.invokeOnCompletion {
if (it != null) {
clientLog.e(it, "Failed to save new session data.")
Timber.tag(loggerTag.value).e(it, "Failed to save new session data.")

Check warning on line 80 in libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt

View check run for this annotation

Codecov / codecov/patch

libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt#L80

Added line #L80 was not covered by tests
}
}
}

override fun didReceiveAuthError(isSoftLogout: Boolean) {
clientLog.w("didReceiveAuthError(isSoftLogout=$isSoftLogout)")
Timber.tag(loggerTag.value).w("didReceiveAuthError(isSoftLogout=$isSoftLogout)")

Check warning on line 86 in libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt

View check run for this annotation

Codecov / codecov/patch

libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt#L86

Added line #L86 was not covered by tests
if (isLoggingOut.getAndSet(true).not()) {
clientLog.v("didReceiveAuthError -> do the cleanup")
Timber.tag(loggerTag.value).v("didReceiveAuthError -> do the cleanup")

Check warning on line 88 in libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt

View check run for this annotation

Codecov / codecov/patch

libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt#L88

Added line #L88 was not covered by tests
// TODO handle isSoftLogout parameter.
appCoroutineScope.launch(updateTokensDispatcher) {
val currentClient = client.get()
if (currentClient == null) {
clientLog.w("didReceiveAuthError -> no client, exiting")
Timber.tag(loggerTag.value).w("didReceiveAuthError -> no client, exiting")

Check warning on line 93 in libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt

View check run for this annotation

Codecov / codecov/patch

libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt#L93

Added line #L93 was not covered by tests
isLoggingOut.set(false)
return@launch
}
val existingData = sessionStore.getSession(currentClient.sessionId.value)
val (anonymizedAccessToken, anonymizedRefreshToken) = existingData.anonymizedTokens()
clientLog.d(
Timber.tag(loggerTag.value).d(

Check warning on line 99 in libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt

View check run for this annotation

Codecov / codecov/patch

libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt#L99

Added line #L99 was not covered by tests
"Removing session data with access token '$anonymizedAccessToken' " +
"and refresh token '$anonymizedRefreshToken'."
)
if (existingData != null) {
// Set isTokenValid to false
val newData = existingData.copy(isTokenValid = false)
sessionStore.updateData(newData)
clientLog.d("Invalidated session data with access token: '$anonymizedAccessToken'.")
Timber.tag(loggerTag.value).d("Invalidated session data with access token: '$anonymizedAccessToken'.")

Check warning on line 107 in libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt

View check run for this annotation

Codecov / codecov/patch

libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt#L107

Added line #L107 was not covered by tests
} else {
clientLog.d("No session data found.")
Timber.tag(loggerTag.value).d("No session data found.")

Check warning on line 109 in libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt

View check run for this annotation

Codecov / codecov/patch

libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt#L109

Added line #L109 was not covered by tests
}
currentClient.logout(userInitiated = false, ignoreSdkError = true)
}.invokeOnCompletion {
if (it != null) {
clientLog.e(it, "Failed to remove session data.")
Timber.tag(loggerTag.value).e(it, "Failed to remove session data.")

Check warning on line 114 in libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt

View check run for this annotation

Codecov / codecov/patch

libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt#L114

Added line #L114 was not covered by tests
}
}
} else {
clientLog.v("didReceiveAuthError -> already cleaning up")
Timber.tag(loggerTag.value).v("didReceiveAuthError -> already cleaning up")

Check warning on line 118 in libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt

View check run for this annotation

Codecov / codecov/patch

libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt#L118

Added line #L118 was not covered by tests
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package io.element.android.libraries.pushproviders.unifiedpush
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.di.AppScope
import kotlinx.coroutines.withContext
import retrofit2.HttpException
Expand All @@ -29,43 +30,45 @@ interface UnifiedPushGatewayResolver {
suspend fun getGateway(endpoint: String): UnifiedPushGatewayResolverResult
}

private val loggerTag = LoggerTag("DefaultUnifiedPushGatewayResolver")

@ContributesBinding(AppScope::class)
class DefaultUnifiedPushGatewayResolver @Inject constructor(
private val unifiedPushApiFactory: UnifiedPushApiFactory,
private val coroutineDispatchers: CoroutineDispatchers,
) : UnifiedPushGatewayResolver {
override suspend fun getGateway(endpoint: String): UnifiedPushGatewayResolverResult {
val url = tryOrNull(
onException = { Timber.tag("DefaultUnifiedPushGatewayResolver").d(it, "Cannot parse endpoint as an URL") }
onException = { Timber.tag(loggerTag.value).d(it, "Cannot parse endpoint as an URL") }
) {
URL(endpoint)
}
return if (url == null) {
Timber.tag("DefaultUnifiedPushGatewayResolver").d("ErrorInvalidUrl")
Timber.tag(loggerTag.value).d("ErrorInvalidUrl")
UnifiedPushGatewayResolverResult.ErrorInvalidUrl
} else {
val port = if (url.port != -1) ":${url.port}" else ""
val customBase = "${url.protocol}://${url.host}$port"
val customUrl = "$customBase/_matrix/push/v1/notify"
Timber.tag("DefaultUnifiedPushGatewayResolver").i("Testing $customUrl")
Timber.tag(loggerTag.value).i("Testing $customUrl")
return withContext(coroutineDispatchers.io) {
val api = unifiedPushApiFactory.create(customBase)
try {
val discoveryResponse = api.discover()
if (discoveryResponse.unifiedpush.gateway == "matrix") {
Timber.tag("DefaultUnifiedPushGatewayResolver").d("The endpoint seems to be a valid UnifiedPush gateway")
Timber.tag(loggerTag.value).d("The endpoint seems to be a valid UnifiedPush gateway")
UnifiedPushGatewayResolverResult.Success(customUrl)
} else {
// The endpoint returned a 200 OK but didn't promote an actual matrix gateway, which means it doesn't have any
Timber.tag("DefaultUnifiedPushGatewayResolver").w("The endpoint does not seem to be a valid UnifiedPush gateway, using fallback")
Timber.tag(loggerTag.value).w("The endpoint does not seem to be a valid UnifiedPush gateway, using fallback")
UnifiedPushGatewayResolverResult.NoMatrixGateway
}
} catch (throwable: Throwable) {
if ((throwable as? HttpException)?.code() == HttpURLConnection.HTTP_NOT_FOUND) {
Timber.tag("DefaultUnifiedPushGatewayResolver").i("Checking for UnifiedPush endpoint yielded 404, using fallback")
Timber.tag(loggerTag.value).i("Checking for UnifiedPush endpoint yielded 404, using fallback")
UnifiedPushGatewayResolverResult.NoMatrixGateway
} else {
Timber.tag("DefaultUnifiedPushGatewayResolver").e(throwable, "Error checking for UnifiedPush endpoint")
Timber.tag(loggerTag.value).e(throwable, "Error checking for UnifiedPush endpoint")
UnifiedPushGatewayResolverResult.Error(customUrl)
}
}
Expand Down
Loading