diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/ClientSecret.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/ClientSecret.kt new file mode 100644 index 0000000000..25d040e15b --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/ClientSecret.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl + +import okio.ByteString.Companion.decodeHex + +/** + * Represents a client secret used to encrypt/decrypt data from databases, which can be either a passphrase or a raw key. + */ +sealed interface ClientSecret { + /** + * A passphrase that can be used to derive a key for encryption/decryption. + */ + data class Passphrase(val value: String) : ClientSecret { + override fun formattedAsString(): String = toString() + } + + /** + * A raw key that can be directly used for encryption/decryption. The key is represented as a byte array, and is formatted as a string in the form of + * `x'...'` where the bytes are encoded as hex characters. + */ + data class RawKey(val bytes: ByteArray) : ClientSecret { + override fun formattedAsString() = "x'${bytes.toHexString()}'" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + return bytes.contentEquals((other as RawKey).bytes) + } + + override fun hashCode(): Int { + return bytes.contentHashCode() + } + + override fun toString(): String { + return formattedAsString() + } + } + + /** + * Format the client secret as a string that can be parsed back with [fromString]. + * For a passphrase, this is just the passphrase value. For a raw key, this is the hex-encoded representation of the key formatted as `x'...'`. + */ + fun formattedAsString(): String + + companion object { + /** + * Parse a string representation of a client secret, which can be either a passphrase or a raw key formatted as `x'...'`. + */ + fun fromString(secret: String): ClientSecret { + val regex = Regex("^x'([0-9a-fA-F]+)'$") + val rawKeyMatch = regex.matchEntire(secret) + return if (rawKeyMatch != null) { + RawKey(rawKeyMatch.groupValues[1].decodeHex().toByteArray()) + } else { + Passphrase(secret) + } + } + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt index c22a8b9454..9a7452aa5f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt @@ -75,9 +75,11 @@ class RustMatrixClientFactory( ) suspend fun create(sessionData: SessionData): RustMatrixClient = withContext(coroutineDispatchers.io) { + // This secret is called 'passphrase' for historical reasons, but it can be a raw key or an actual passphrase + val clientSecret = sessionData.passphrase?.let(ClientSecret::fromString) val client = getBaseClientBuilder( sessionPaths = sessionData.getSessionPaths(), - passphrase = sessionData.passphrase, + clientSecret = clientSecret, slidingSyncType = ClientBuilderSlidingSync.Restored, ) .homeserverUrl(sessionData.homeserverUrl) @@ -137,13 +139,13 @@ class RustMatrixClientFactory( internal suspend fun getBaseClientBuilder( sessionPaths: SessionPaths, - passphrase: String?, + clientSecret: ClientSecret?, slidingSyncType: ClientBuilderSlidingSync, ): ClientBuilder { return clientBuilderProvider.provide() .run { sqliteStoreBuilderProvider.provide(sessionPaths) - .passphrase(passphrase) + .secret(clientSecret) .setupClientBuilder(this) } .setSessionDelegate(sessionDelegate) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index 692edf79ae..812a0dd6fe 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -30,12 +30,13 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.impl.ClientBuilderSlidingSync +import io.element.android.libraries.matrix.impl.ClientSecret import io.element.android.libraries.matrix.impl.RustMatrixClientFactory import io.element.android.libraries.matrix.impl.auth.qrlogin.QrErrorMapper import io.element.android.libraries.matrix.impl.auth.qrlogin.SdkQrCodeLoginData import io.element.android.libraries.matrix.impl.auth.qrlogin.toStep import io.element.android.libraries.matrix.impl.exception.mapClientException -import io.element.android.libraries.matrix.impl.keys.PassphraseGenerator +import io.element.android.libraries.matrix.impl.keys.SecretGenerator import io.element.android.libraries.matrix.impl.mapper.toSessionData import io.element.android.libraries.matrix.impl.paths.SessionPaths import io.element.android.libraries.matrix.impl.paths.SessionPathsFactory @@ -65,7 +66,7 @@ class RustMatrixAuthenticationService( private val coroutineDispatchers: CoroutineDispatchers, private val sessionStore: SessionStore, private val rustMatrixClientFactory: RustMatrixClientFactory, - private val passphraseGenerator: PassphraseGenerator, + private val secretGenerator: SecretGenerator, private val oAuthConfigurationProvider: OAuthConfigurationProvider, private val enterpriseService: EnterpriseService, ) : MatrixAuthenticationService { @@ -74,7 +75,7 @@ class RustMatrixAuthenticationService( // Passphrase which will be used for new sessions. Existing sessions will use the passphrase // stored in the SessionData. - private val pendingPassphrase = getDatabasePassphrase() + private val pendingKey by lazy { getDatabaseKey() } // Need to keep a copy of the current session path to eventually delete it. // Ideally it would be possible to get the sessionPath from the Client to avoid doing this. @@ -115,12 +116,9 @@ class RustMatrixAuthenticationService( } } - private fun getDatabasePassphrase(): String? { - val passphrase = passphraseGenerator.generatePassphrase() - if (passphrase != null) { - Timber.w("New sessions will be encrypted with a passphrase") - } - return passphrase + private fun getDatabaseKey(): ClientSecret { + Timber.d("New sessions will be encrypted with a raw key") + return secretGenerator.generateKey() } override suspend fun setHomeserver(homeserver: String): Result = @@ -159,7 +157,7 @@ class RustMatrixAuthenticationService( .toSessionData( isTokenValid = true, loginType = LoginType.PASSWORD, - passphrase = pendingPassphrase, + passphrase = pendingKey.formattedAsString(), sessionPaths = currentSessionPaths, ) val matrixClient = rustMatrixClientFactory.create(client) @@ -231,7 +229,7 @@ class RustMatrixAuthenticationService( val sessionData = externalSession.toSessionData( isTokenValid = true, loginType = LoginType.PASSWORD, - passphrase = pendingPassphrase, + passphrase = pendingKey.formattedAsString(), sessionPaths = currentSessionPaths, ) @@ -324,7 +322,7 @@ class RustMatrixAuthenticationService( val sessionData = client.session().toSessionData( isTokenValid = true, loginType = LoginType.OIDC, - passphrase = pendingPassphrase, + passphrase = pendingKey.formattedAsString(), sessionPaths = currentSessionPaths, ) val matrixClient = rustMatrixClientFactory.create(client) @@ -389,7 +387,7 @@ class RustMatrixAuthenticationService( .toSessionData( isTokenValid = true, loginType = LoginType.QR, - passphrase = pendingPassphrase, + passphrase = pendingKey.formattedAsString(), sessionPaths = emptySessionPaths, ) val matrixClient = rustMatrixClientFactory.create(client) @@ -422,7 +420,7 @@ class RustMatrixAuthenticationService( return rustMatrixClientFactory .getBaseClientBuilder( sessionPaths = sessionPaths, - passphrase = pendingPassphrase, + clientSecret = pendingKey, slidingSyncType = ClientBuilderSlidingSync.Discovered, ) .config() @@ -454,7 +452,7 @@ class RustMatrixAuthenticationService( return rustMatrixClientFactory .getBaseClientBuilder( sessionPaths = sessionPaths, - passphrase = pendingPassphrase, + clientSecret = pendingKey, slidingSyncType = ClientBuilderSlidingSync.Discovered, ) .serverNameOrHomeserverUrl(baseUrlOrServerName) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultPassphraseGenerator.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultSecretGenerator.kt similarity index 50% rename from libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultPassphraseGenerator.kt rename to libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultSecretGenerator.kt index d13ff7779e..042dae36d9 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultPassphraseGenerator.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultSecretGenerator.kt @@ -11,15 +11,20 @@ package io.element.android.libraries.matrix.impl.keys import android.util.Base64 import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding +import io.element.android.libraries.matrix.impl.ClientSecret import java.security.SecureRandom -private const val SECRET_SIZE = 256 - @ContributesBinding(AppScope::class) -class DefaultPassphraseGenerator : PassphraseGenerator { - override fun generatePassphrase(): String? { - val key = ByteArray(size = SECRET_SIZE) +class DefaultSecretGenerator : SecretGenerator { + override fun generatePassphrase(size: Int): ClientSecret.Passphrase? { + val key = ByteArray(size = size) SecureRandom().nextBytes(key) - return Base64.encodeToString(key, Base64.NO_PADDING or Base64.NO_WRAP) + return ClientSecret.Passphrase(Base64.encodeToString(key, Base64.NO_PADDING or Base64.NO_WRAP)) + } + + override fun generateKey(size: Int): ClientSecret.RawKey { + val key = ByteArray(size = size) + SecureRandom().nextBytes(key) + return ClientSecret.RawKey(key) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/PassphraseGenerator.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/PassphraseGenerator.kt deleted file mode 100644 index e0f925fa28..0000000000 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/PassphraseGenerator.kt +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2024, 2025 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.matrix.impl.keys - -interface PassphraseGenerator { - /** - * Generate a passphrase to encrypt the databases of a session. - * Return null to not encrypt the databases. - */ - fun generatePassphrase(): String? -} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/SecretGenerator.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/SecretGenerator.kt new file mode 100644 index 0000000000..4699f2a720 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/SecretGenerator.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.keys + +import io.element.android.libraries.matrix.impl.ClientSecret + +private const val PASSPHRASE_SIZE = 256 +private const val KEY_SIZE = 32 + +interface SecretGenerator { + /** + * Generate a passphrase to encrypt the databases. + * @param size the size of the passphrase in bytes, before encoding. The default value is 256 bytes. + * @return either a random passphrase or `null` to not encrypt the databases. + */ + fun generatePassphrase(size: Int = PASSPHRASE_SIZE): ClientSecret.Passphrase? + + /** + * Generate a key to encrypt the databases. + * @param size the size of the key in bytes. The default value is 32 bytes. + * @return a random key. + */ + fun generateKey(size: Int = KEY_SIZE): ClientSecret.RawKey +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/storage/SqliteStoreBuilder.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/storage/SqliteStoreBuilder.kt index 84f124868e..1e5b8e037c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/storage/SqliteStoreBuilder.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/storage/SqliteStoreBuilder.kt @@ -9,12 +9,23 @@ package io.element.android.libraries.matrix.impl.storage import io.element.android.libraries.core.data.ByteUnit import io.element.android.libraries.core.data.megaBytes +import io.element.android.libraries.matrix.impl.ClientSecret import io.element.android.libraries.matrix.impl.paths.SessionPaths import org.matrix.rustcomponents.sdk.ClientBuilder import org.matrix.rustcomponents.sdk.SqliteStoreBuilder as SdkSqliteStoreBuilder +/** + * Abstraction over the SDK's [SdkSqliteStoreBuilder] to allow configuring it with a ClientSecret and to hide the SDK from the rest of the codebase. + */ interface SqliteStoreBuilder { - fun passphrase(passphrase: String?): SqliteStoreBuilder + /** + * Configure the builder with a [ClientSecret], if provided. If the [clientSecret] is null, the databases will not be encrypted. + */ + fun secret(clientSecret: ClientSecret?): SqliteStoreBuilder + + /** + * Configure the provided [clientBuilder] with the configured [SdkSqliteStoreBuilder] and return it. + */ fun setupClientBuilder(clientBuilder: ClientBuilder): ClientBuilder } @@ -26,8 +37,15 @@ class RustSqliteStoreBuilder( cachePath = sessionPaths.cacheDirectory.absolutePath, ).journalSizeLimit(25.megaBytes.into(ByteUnit.BYTES).toUInt()) - override fun passphrase(passphrase: String?): SqliteStoreBuilder { - inner = inner.passphrase(passphrase) + override fun secret(clientSecret: ClientSecret?): SqliteStoreBuilder { + when (clientSecret) { + null -> Unit + is ClientSecret.Passphrase -> inner = inner.passphrase(clientSecret.value) + is ClientSecret.RawKey -> { + // Ensure the key is 32 bytes long, as required by the SDK + inner = inner.key(clientSecret.keyOfSize(32)) + } + } return this } @@ -35,3 +53,15 @@ class RustSqliteStoreBuilder( return clientBuilder.sqliteStore(this.inner) } } + +private fun ClientSecret.RawKey.keyOfSize(size: Int): ByteArray { + return if (bytes.size == 32) { + bytes + } else if (bytes.size < 32) { + // If the key is shorter than 32 bytes, pad it with zeros + bytes + ByteArray(32 - bytes.size) + } else { + // Otherwise, take the first 32 bytes of the key + bytes.copyOfRange(0, 32) + } +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/FakePassphraseGenerator.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/FakePassphraseGenerator.kt deleted file mode 100644 index a089920422..0000000000 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/FakePassphraseGenerator.kt +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2024, 2025 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.matrix.impl.auth - -import io.element.android.libraries.matrix.impl.keys.PassphraseGenerator -import io.element.android.libraries.matrix.test.A_PASSPHRASE - -class FakePassphraseGenerator( - private val passphrase: () -> String? = { A_PASSPHRASE } -) : PassphraseGenerator { - override fun generatePassphrase(): String? = passphrase() -} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/FakeSecretGenerator.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/FakeSecretGenerator.kt new file mode 100644 index 0000000000..6bd995348a --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/FakeSecretGenerator.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.auth + +import io.element.android.libraries.matrix.impl.ClientSecret +import io.element.android.libraries.matrix.impl.keys.SecretGenerator +import io.element.android.libraries.matrix.test.A_PASSPHRASE + +class FakeSecretGenerator( + private val passphrase: (Int) -> String? = { A_PASSPHRASE }, + private val key: (Int) -> ByteArray = { ByteArray(it) { 0 } }, +) : SecretGenerator { + override fun generatePassphrase(size: Int): ClientSecret.Passphrase? = passphrase(size)?.let { ClientSecret.Passphrase(it) } + override fun generateKey(size: Int): ClientSecret.RawKey = ClientSecret.RawKey(key(size)) +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationServiceTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationServiceTest.kt index 7f422acfcf..2b9080195f 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationServiceTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationServiceTest.kt @@ -66,7 +66,7 @@ class RustMatrixAuthenticationServiceTest { coroutineDispatchers = testCoroutineDispatchers(), sessionStore = sessionStore, rustMatrixClientFactory = rustMatrixClientFactory, - passphraseGenerator = FakePassphraseGenerator(), + secretGenerator = FakeSecretGenerator(), oAuthConfigurationProvider = OAuthConfigurationProvider( buildMeta = aBuildMeta(), oAuthRedirectUrlProvider = FakeOAuthRedirectUrlProvider(), diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultPassphraseGeneratorTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultPassphraseGeneratorTest.kt deleted file mode 100644 index 465d920074..0000000000 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultPassphraseGeneratorTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2024, 2025 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.matrix.impl.keys - -import com.google.common.truth.Truth.assertThat -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner - -@RunWith(RobolectricTestRunner::class) -class DefaultPassphraseGeneratorTest { - @Test - fun `check that generated passphrase has the expected length`() { - val passphraseGenerator = DefaultPassphraseGenerator() - val passphrase = passphraseGenerator.generatePassphrase() - assertThat(passphrase!!.length).isEqualTo(342) - } -} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultSecretGeneratorTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultSecretGeneratorTest.kt new file mode 100644 index 0000000000..5fa2b263a1 --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultSecretGeneratorTest.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.keys + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.impl.ClientSecret +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class DefaultSecretGeneratorTest { + @Test + fun `check that generated passphrase has the expected length`() { + val secretGenerator = DefaultSecretGenerator() + val passphrase = secretGenerator.generatePassphrase(256) + assertThat(passphrase).isInstanceOf(ClientSecret.Passphrase::class.java) + // Size after Base64 encoding should be 4/3 of the original size, without padding + assertThat(passphrase!!.value).hasLength(342) + } + + @Test + fun `check that generated key has the expected length`() { + val secretGenerator = DefaultSecretGenerator() + val key = secretGenerator.generateKey(123) + assertThat(key).isInstanceOf(ClientSecret.RawKey::class.java) + assertThat(key.bytes).hasLength(123) + } +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/storage/FakeSqliteStoreBuilder.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/storage/FakeSqliteStoreBuilder.kt index 2f12587f5a..c33a67790b 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/storage/FakeSqliteStoreBuilder.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/storage/FakeSqliteStoreBuilder.kt @@ -7,10 +7,11 @@ package io.element.android.libraries.matrix.impl.storage +import io.element.android.libraries.matrix.impl.ClientSecret import org.matrix.rustcomponents.sdk.ClientBuilder class FakeSqliteStoreBuilder : SqliteStoreBuilder { - override fun passphrase(passphrase: String?): SqliteStoreBuilder = this + override fun secret(clientSecret: ClientSecret?): SqliteStoreBuilder = this override fun setupClientBuilder(clientBuilder: ClientBuilder): ClientBuilder { return clientBuilder diff --git a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsColdStartWatcher.kt b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsColdStartWatcher.kt index e596bc35a5..a9647ba3a1 100644 --- a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsColdStartWatcher.kt +++ b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsColdStartWatcher.kt @@ -56,8 +56,9 @@ class DefaultAnalyticsColdStartWatcher( override fun onRoomListVisible() { if (isColdStart.getAndSet(false)) { - Timber.d("Room list is visible, finishing cold start check") - analyticsService.finishLongRunningTransaction(AnalyticsLongRunningTransaction.ColdStart) + analyticsService.finishLongRunningTransaction(AnalyticsLongRunningTransaction.ColdStart) { + Timber.d("Room list is visible, finishing cold start check. Elapsed: ${it.duration}") + } } } }