diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d84ef48..fd29ec1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: - name: Create pip requirements run: | - echo "matrix-synapse==v1.60.0" > requirements.txt + echo "matrix-synapse" > requirements.txt - name: Set up Python 3.8 uses: actions/setup-python@v2 diff --git a/app/src/main/kotlin/app/dapk/st/graph/AppModule.kt b/app/src/main/kotlin/app/dapk/st/graph/AppModule.kt index c39313b..9917e86 100644 --- a/app/src/main/kotlin/app/dapk/st/graph/AppModule.kt +++ b/app/src/main/kotlin/app/dapk/st/graph/AppModule.kt @@ -68,7 +68,7 @@ import java.time.Clock internal class AppModule(context: Application, logger: MatrixLogger) { - private val buildMeta = BuildMeta(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE) + private val buildMeta = BuildMeta(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, isDebug = BuildConfig.DEBUG) private val deviceMeta = DeviceMeta(Build.VERSION.SDK_INT) private val trackingModule by unsafeLazy { TrackingModule( @@ -94,7 +94,7 @@ internal class AppModule(context: Application, logger: MatrixLogger) { private val workModule = WorkModule(context) private val imageLoaderModule = ImageLoaderModule(context) - private val matrixModules = MatrixModules(storeModule, trackingModule, workModule, logger, coroutineDispatchers, context.contentResolver) + private val matrixModules = MatrixModules(storeModule, trackingModule, workModule, logger, coroutineDispatchers, context.contentResolver, buildMeta) val domainModules = DomainModules(matrixModules, trackingModule.errorTracker, workModule, storeModule, context, coroutineDispatchers) val coreAndroidModule = CoreAndroidModule( @@ -232,6 +232,7 @@ internal class MatrixModules( private val logger: MatrixLogger, private val coroutineDispatchers: CoroutineDispatchers, private val contentResolver: ContentResolver, + private val buildMeta: BuildMeta, ) { val matrix by unsafeLazy { @@ -240,7 +241,7 @@ internal class MatrixModules( MatrixClient( KtorMatrixHttpClientFactory( credentialsStore, - includeLogging = true + includeLogging = buildMeta.isDebug, ), logger ).also { diff --git a/build.gradle b/build.gradle index 7fa1282..66dbbba 100644 --- a/build.gradle +++ b/build.gradle @@ -132,7 +132,7 @@ ext.kotlinTest = { dependencies -> dependencies.testImplementation Dependencies.mavenCentral.kluent dependencies.testImplementation Dependencies.mavenCentral.kotlinTest dependencies.testImplementation "org.jetbrains.kotlin:kotlin-test-junit:1.6.10" - dependencies.testImplementation 'io.mockk:mockk:1.12.7' + dependencies.testImplementation 'io.mockk:mockk:1.12.8' dependencies.testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4' dependencies.testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0' diff --git a/core/src/main/kotlin/app/dapk/st/core/BuildMeta.kt b/core/src/main/kotlin/app/dapk/st/core/BuildMeta.kt index 3fd09ac..d1d63ba 100644 --- a/core/src/main/kotlin/app/dapk/st/core/BuildMeta.kt +++ b/core/src/main/kotlin/app/dapk/st/core/BuildMeta.kt @@ -3,4 +3,5 @@ package app.dapk.st.core data class BuildMeta( val versionName: String, val versionCode: Int, + val isDebug: Boolean, ) diff --git a/dependencies.gradle b/dependencies.gradle index f56161d..4be9fcd 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -143,7 +143,7 @@ ext.Dependencies.with { junit = "junit:junit:4.13.2" kluent = "org.amshove.kluent:kluent:1.68" - mockk = 'io.mockk:mockk:1.12.7' + mockk = 'io.mockk:mockk:1.12.8' matrixOlm = "org.matrix.android:olm-sdk:3.2.12" } diff --git a/domains/olm/src/main/kotlin/app/dapk/st/olm/OlmExtensions.kt b/domains/olm/src/main/kotlin/app/dapk/st/olm/OlmExtensions.kt index c26554b..9a47f49 100644 --- a/domains/olm/src/main/kotlin/app/dapk/st/olm/OlmExtensions.kt +++ b/domains/olm/src/main/kotlin/app/dapk/st/olm/OlmExtensions.kt @@ -8,3 +8,7 @@ fun OlmAccount.readIdentityKeys(): Pair { val identityKeys = this.identityKeys() return Ed25519(identityKeys["ed25519"]!!) to Curve25519(identityKeys["curve25519"]!!) } + +fun OlmAccount.oneTimeCurveKeys(): List> { + return this.oneTimeKeys()["curve25519"]?.map { it.key to Curve25519(it.value) } ?: emptyList() +} \ No newline at end of file diff --git a/domains/olm/src/main/kotlin/app/dapk/st/olm/OlmWrapper.kt b/domains/olm/src/main/kotlin/app/dapk/st/olm/OlmWrapper.kt index 9f9826b..ad6a6e6 100644 --- a/domains/olm/src/main/kotlin/app/dapk/st/olm/OlmWrapper.kt +++ b/domains/olm/src/main/kotlin/app/dapk/st/olm/OlmWrapper.kt @@ -67,7 +67,7 @@ class OlmWrapper( private suspend fun accountCrypto(deviceCredentials: DeviceCredentials): AccountCryptoSession? { return olmStore.read()?.let { olmAccount -> - createAccountCryptoSession(deviceCredentials, olmAccount) + createAccountCryptoSession(deviceCredentials, olmAccount, isNew = false) } } @@ -80,12 +80,12 @@ class OlmWrapper( val olmAccount = this.olmAccount as OlmAccount olmAccount.generateOneTimeKeys(count) - val oneTimeKeys = DeviceService.OneTimeKeys(olmAccount.oneTimeKeys()["curve25519"]!!.map { + val oneTimeKeys = DeviceService.OneTimeKeys(olmAccount.oneTimeCurveKeys().map { (key, value) -> DeviceService.OneTimeKeys.Key.SignedCurve( - keyId = it.key, - value = it.value, + keyId = key, + value = value.value, signature = DeviceService.OneTimeKeys.Key.SignedCurve.Ed25519Signature( - value = it.value.toSignedJson(olmAccount), + value = value.value.toSignedJson(olmAccount), deviceId = credentials.deviceId, userId = credentials.userId, ) @@ -98,20 +98,21 @@ class OlmWrapper( private suspend fun createAccountCrypto(deviceCredentials: DeviceCredentials, action: suspend (AccountCryptoSession) -> Unit): AccountCryptoSession { val olmAccount = OlmAccount() - return createAccountCryptoSession(deviceCredentials, olmAccount).also { + return createAccountCryptoSession(deviceCredentials, olmAccount, isNew = true).also { action(it) olmStore.persist(olmAccount) } } - private fun createAccountCryptoSession(credentials: DeviceCredentials, olmAccount: OlmAccount): AccountCryptoSession { + private fun createAccountCryptoSession(credentials: DeviceCredentials, olmAccount: OlmAccount, isNew: Boolean): AccountCryptoSession { val (identityKey, senderKey) = olmAccount.readIdentityKeys() return AccountCryptoSession( fingerprint = identityKey, senderKey = senderKey, deviceKeys = deviceKeyFactory.create(credentials.userId, credentials.deviceId, identityKey, senderKey, olmAccount), olmAccount = olmAccount, - maxKeys = olmAccount.maxOneTimeKeys().toInt() + maxKeys = olmAccount.maxOneTimeKeys().toInt(), + hasKeys = !isNew, ) } @@ -136,6 +137,7 @@ class OlmWrapper( singletonFlows.update("room-${roomId.value}", rotatedSession) } } + else -> this } } @@ -277,10 +279,12 @@ class OlmWrapper( } } } + OlmMessage.MESSAGE_TYPE_MESSAGE -> { logger.crypto("decrypting olm message type") session.decryptMessage(olmMessage)?.let { JsonString(it) } } + else -> throw IllegalArgumentException("Unknown message type: $type") } }.onFailure { @@ -297,7 +301,7 @@ class OlmWrapper( } private suspend fun AccountCryptoSession.updateAccountInstance(olmAccount: OlmAccount) { - singletonFlows.update("account-crypto", this.copy(olmAccount = olmAccount)) + singletonFlows.update("account-crypto", this.copy(olmAccount = olmAccount, hasKeys = true)) olmStore.persist(olmAccount) } diff --git a/features/home/src/main/kotlin/app/dapk/st/home/HomeViewModel.kt b/features/home/src/main/kotlin/app/dapk/st/home/HomeViewModel.kt index 56617f7..728c6c0 100644 --- a/features/home/src/main/kotlin/app/dapk/st/home/HomeViewModel.kt +++ b/features/home/src/main/kotlin/app/dapk/st/home/HomeViewModel.kt @@ -100,19 +100,27 @@ class HomeViewModel( Loading -> current is SignedIn -> { when (page) { - Page.Directory -> { - // do nothing + current.page -> current + else -> current.copy(page = page).also { + pageChangeSideEffects(page) } - - Page.Profile -> profileViewModel.reset() } - current.copy(page = page) } SignedOut -> current } } + private fun pageChangeSideEffects(page: Page) { + when (page) { + Page.Directory -> { + // do nothing + } + + Page.Profile -> profileViewModel.reset() + } + } + fun stop() { viewModelScope.cancel() } diff --git a/features/settings/src/test/kotlin/app/dapk/st/settings/SettingsItemFactoryTest.kt b/features/settings/src/test/kotlin/app/dapk/st/settings/SettingsItemFactoryTest.kt index ab631c4..b34c362 100644 --- a/features/settings/src/test/kotlin/app/dapk/st/settings/SettingsItemFactoryTest.kt +++ b/features/settings/src/test/kotlin/app/dapk/st/settings/SettingsItemFactoryTest.kt @@ -18,7 +18,7 @@ private const val ENABLED_MATERIAL_YOU = true class SettingsItemFactoryTest { - private val buildMeta = BuildMeta(versionName = "a-version-name", versionCode = 100) + private val buildMeta = BuildMeta(versionName = "a-version-name", versionCode = 100, isDebug = false) private val deviceMeta = DeviceMeta(apiVersion = 31) private val fakePushTokenRegistrars = FakePushRegistrars() private val fakeThemeStore = FakeThemeStore() diff --git a/matrix/services/crypto/src/main/kotlin/app/dapk/st/matrix/crypto/Olm.kt b/matrix/services/crypto/src/main/kotlin/app/dapk/st/matrix/crypto/Olm.kt index 7417576..4d2c208 100644 --- a/matrix/services/crypto/src/main/kotlin/app/dapk/st/matrix/crypto/Olm.kt +++ b/matrix/services/crypto/src/main/kotlin/app/dapk/st/matrix/crypto/Olm.kt @@ -72,6 +72,7 @@ interface Olm { val fingerprint: Ed25519, val senderKey: Curve25519, val deviceKeys: DeviceKeys, + val hasKeys: Boolean, val maxKeys: Int, val olmAccount: Any, ) diff --git a/matrix/services/crypto/src/main/kotlin/app/dapk/st/matrix/crypto/internal/OneTimeKeyUploaderUseCase.kt b/matrix/services/crypto/src/main/kotlin/app/dapk/st/matrix/crypto/internal/OneTimeKeyUploaderUseCase.kt index bd6d6a1..4046568 100644 --- a/matrix/services/crypto/src/main/kotlin/app/dapk/st/matrix/crypto/internal/OneTimeKeyUploaderUseCase.kt +++ b/matrix/services/crypto/src/main/kotlin/app/dapk/st/matrix/crypto/internal/OneTimeKeyUploaderUseCase.kt @@ -15,16 +15,28 @@ internal class MaybeCreateAndUploadOneTimeKeysUseCaseImpl( private val credentialsStore: CredentialsStore, private val deviceService: DeviceService, private val logger: MatrixLogger, -): MaybeCreateAndUploadOneTimeKeysUseCase { +) : MaybeCreateAndUploadOneTimeKeysUseCase { override suspend fun invoke(currentServerKeyCount: ServerKeyCount) { - val ensureCryptoAccount = fetchAccountCryptoUseCase.invoke() - val keysDiff = (ensureCryptoAccount.maxKeys / 2) - currentServerKeyCount.value - if (keysDiff > 0) { - logger.crypto("current otk: $currentServerKeyCount, creating: $keysDiff") - ensureCryptoAccount.createAndUploadOneTimeKeys(countToCreate = keysDiff + (ensureCryptoAccount.maxKeys / 4)) - } else { - logger.crypto("current otk: $currentServerKeyCount, not creating new keys") + val cryptoAccount = fetchAccountCryptoUseCase.invoke() + when { + currentServerKeyCount.value == 0 && cryptoAccount.hasKeys -> { + logger.crypto("Server has no keys but a crypto instance exists, waiting for next update") + } + + else -> { + val keysDiff = (cryptoAccount.maxKeys / 2) - currentServerKeyCount.value + when { + keysDiff > 0 -> { + logger.crypto("current otk: $currentServerKeyCount, creating: $keysDiff") + cryptoAccount.createAndUploadOneTimeKeys(countToCreate = keysDiff + (cryptoAccount.maxKeys / 4)) + } + + else -> { + logger.crypto("current otk: $currentServerKeyCount, not creating new keys") + } + } + } } } diff --git a/matrix/services/crypto/src/test/kotlin/app/dapk/st/matrix/crypto/internal/MaybeCreateAndUploadOneTimeKeysUseCaseTest.kt b/matrix/services/crypto/src/test/kotlin/app/dapk/st/matrix/crypto/internal/MaybeCreateAndUploadOneTimeKeysUseCaseTest.kt index 0266e38..055f43c 100644 --- a/matrix/services/crypto/src/test/kotlin/app/dapk/st/matrix/crypto/internal/MaybeCreateAndUploadOneTimeKeysUseCaseTest.kt +++ b/matrix/services/crypto/src/test/kotlin/app/dapk/st/matrix/crypto/internal/MaybeCreateAndUploadOneTimeKeysUseCaseTest.kt @@ -22,12 +22,11 @@ class MaybeCreateAndUploadOneTimeKeysUseCaseTest { private val fakeDeviceService = FakeDeviceService() private val fakeOlm = FakeOlm() - private val fakeCredentialsStore = FakeCredentialsStore().also { - it.givenCredentials().returns(A_USER_CREDENTIALS) - } + private val fakeCredentialsStore = FakeCredentialsStore().also { it.givenCredentials().returns(A_USER_CREDENTIALS) } + private val fakeFetchAccountCryptoUseCase = FakeFetchAccountCryptoUseCase() private val maybeCreateAndUploadOneTimeKeysUseCase = MaybeCreateAndUploadOneTimeKeysUseCaseImpl( - FakeFetchAccountCryptoUseCase().also { it.givenFetch().returns(AN_ACCOUNT_CRYPTO_SESSION) }, + fakeFetchAccountCryptoUseCase.also { it.givenFetch().returns(AN_ACCOUNT_CRYPTO_SESSION) }, fakeOlm, fakeCredentialsStore, fakeDeviceService, @@ -43,6 +42,16 @@ class MaybeCreateAndUploadOneTimeKeysUseCaseTest { fakeDeviceService.verifyDidntUploadOneTimeKeys() } + @Test + fun `given account has keys and server count is 0 then does nothing`() = runTest { + fakeFetchAccountCryptoUseCase.givenFetch().returns(AN_ACCOUNT_CRYPTO_SESSION.copy(hasKeys = true)) + val zeroServiceKeys = ServerKeyCount(0) + + maybeCreateAndUploadOneTimeKeysUseCase.invoke(zeroServiceKeys) + + fakeDeviceService.verifyDidntUploadOneTimeKeys() + } + @Test fun `given 0 current keys than generates and uploads 75 percent of the max key capacity`() = runTest { fakeDeviceService.expect { it.uploadOneTimeKeys(GENERATED_ONE_TIME_KEYS) } diff --git a/matrix/services/crypto/src/testFixtures/kotlin/fixture/CryptoSessionFixtures.kt b/matrix/services/crypto/src/testFixtures/kotlin/fixture/CryptoSessionFixtures.kt index 40ae5bc..18d55b7 100644 --- a/matrix/services/crypto/src/testFixtures/kotlin/fixture/CryptoSessionFixtures.kt +++ b/matrix/services/crypto/src/testFixtures/kotlin/fixture/CryptoSessionFixtures.kt @@ -10,8 +10,9 @@ fun anAccountCryptoSession( senderKey: Curve25519 = aCurve25519(), deviceKeys: DeviceKeys = aDeviceKeys(), maxKeys: Int = 5, + hasKeys: Boolean = false, olmAccount: Any = mockk(), -) = Olm.AccountCryptoSession(fingerprint, senderKey, deviceKeys, maxKeys, olmAccount) +) = Olm.AccountCryptoSession(fingerprint, senderKey, deviceKeys, hasKeys, maxKeys, olmAccount) fun aRoomCryptoSession( creationTimestampUtc: Long = 0L, diff --git a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/request/ApiSyncResponse.kt b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/request/ApiSyncResponse.kt index b4bc9e9..2bc14c2 100644 --- a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/request/ApiSyncResponse.kt +++ b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/request/ApiSyncResponse.kt @@ -56,6 +56,12 @@ internal data class ApiSyncRoom( @SerialName("state") val state: ApiSyncRoomState, @SerialName("account_data") val accountData: ApiAccountData? = null, @SerialName("ephemeral") val ephemeral: ApiEphemeral? = null, + @SerialName("summary") val summary: ApiRoomSummary? = null, +) + +@Serializable +internal data class ApiRoomSummary( + @SerialName("m.heroes") val heroes: List? = null ) @Serializable diff --git a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/request/ApiTimelineEvent.kt b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/request/ApiTimelineEvent.kt index dfd0a64..6a26b02 100644 --- a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/request/ApiTimelineEvent.kt +++ b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/request/ApiTimelineEvent.kt @@ -21,7 +21,13 @@ internal sealed class ApiTimelineEvent { @Serializable internal data class Content( @SerialName("type") val type: String? = null - ) + ) { + + object Type { + const val SPACE = "m.space" + } + + } } @Serializable @@ -50,6 +56,18 @@ internal sealed class ApiTimelineEvent { ) } + @Serializable + @SerialName("m.room.canonical_alias") + internal data class CanonicalAlias( + @SerialName("event_id") val id: EventId, + @SerialName("content") val content: Content, + ) : ApiTimelineEvent() { + + @Serializable + internal data class Content( + @SerialName("alias") val alias: String + ) + } @Serializable @SerialName("m.room.avatar") diff --git a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/room/SyncSideEffects.kt b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/room/SyncSideEffects.kt index 288aab1..f08dd1d 100644 --- a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/room/SyncSideEffects.kt +++ b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/room/SyncSideEffects.kt @@ -27,6 +27,7 @@ internal class SyncSideEffects( response.deviceLists?.changed?.ifEmpty { null }?.let { notifyDevicesUpdated.notifyChanges(it, requestToken) } + oneTimeKeyProducer.onServerKeyCount(response.oneTimeKeysCount["signed_curve25519"] ?: ServerKeyCount(0)) val decryptedToDeviceEvents = decryptedToDeviceEvents(response) diff --git a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/RoomOverviewProcessor.kt b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/RoomOverviewProcessor.kt index 22d2061..d4d4ebf 100644 --- a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/RoomOverviewProcessor.kt +++ b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/RoomOverviewProcessor.kt @@ -12,37 +12,43 @@ internal class RoomOverviewProcessor( private val roomMembersService: RoomMembersService, ) { - suspend fun process(roomToProcess: RoomToProcess, previousState: RoomOverview?, lastMessage: LastMessage?): RoomOverview { + suspend fun process(roomToProcess: RoomToProcess, previousState: RoomOverview?, lastMessage: LastMessage?): RoomOverview? { val combinedEvents = roomToProcess.apiSyncRoom.state.stateEvents + roomToProcess.apiSyncRoom.timeline.apiTimelineEvents val isEncrypted = combinedEvents.any { it is ApiTimelineEvent.Encryption } - val readMarker = roomToProcess.apiSyncRoom.accountData?.events?.filterIsInstance()?.firstOrNull()?.content?.eventId return when (previousState) { null -> combinedEvents.filterIsInstance().first().let { roomCreate -> - val roomName = roomDisplayName(combinedEvents) - val isGroup = roomToProcess.directMessage == null - RoomOverview( - roomName = roomName ?: roomToProcess.directMessage?.let { - roomMembersService.find(roomToProcess.roomId, it)?.let { it.displayName ?: it.id.value } - }, - roomCreationUtc = roomCreate.utcTimestamp, - lastMessage = lastMessage, - roomId = roomToProcess.roomId, - isGroup = isGroup, - roomAvatarUrl = roomAvatar( - roomToProcess.roomId, - roomMembersService, - roomToProcess.directMessage, - combinedEvents, - roomToProcess.userCredentials.homeServer - ), - readMarker = readMarker, - isEncrypted = isEncrypted, - ) + when (roomCreate.content.type) { + ApiTimelineEvent.RoomCreate.Content.Type.SPACE -> null + else -> { + val roomName = roomDisplayName(roomToProcess, combinedEvents) + val isGroup = roomToProcess.directMessage == null + val processedName = roomName ?: roomToProcess.directMessage?.let { + roomMembersService.find(roomToProcess.roomId, it)?.let { it.displayName ?: it.id.value } + } + RoomOverview( + roomName = processedName, + roomCreationUtc = roomCreate.utcTimestamp, + lastMessage = lastMessage, + roomId = roomToProcess.roomId, + isGroup = isGroup, + roomAvatarUrl = roomAvatar( + roomToProcess.roomId, + roomMembersService, + roomToProcess.directMessage, + combinedEvents, + roomToProcess.userCredentials.homeServer + ), + readMarker = readMarker, + isEncrypted = isEncrypted, + ) + } + } } + else -> { previousState.copy( - roomName = previousState.roomName ?: roomDisplayName(combinedEvents), + roomName = previousState.roomName ?: roomDisplayName(roomToProcess, combinedEvents), lastMessage = lastMessage ?: previousState.lastMessage, roomAvatarUrl = previousState.roomAvatarUrl ?: roomAvatar( roomToProcess.roomId, @@ -58,9 +64,13 @@ internal class RoomOverviewProcessor( } } - private fun roomDisplayName(combinedEvents: List): String? { - val roomName = combinedEvents.filterIsInstance().lastOrNull() - return roomName?.content?.name + private suspend fun roomDisplayName(roomToProcess: RoomToProcess, combinedEvents: List): String? { + val roomName = combinedEvents.filterIsInstance().lastOrNull()?.content?.name + ?: combinedEvents.filterIsInstance().lastOrNull()?.content?.alias + ?: roomToProcess.heroes?.let { + roomMembersService.find(roomToProcess.roomId, it).joinToString { it.displayName ?: it.id.value } + } + return roomName?.takeIf { it.isNotEmpty() } } private suspend fun roomAvatar( @@ -75,6 +85,7 @@ internal class RoomOverviewProcessor( val filterIsInstance = combinedEvents.filterIsInstance() filterIsInstance.lastOrNull()?.content?.url?.convertMxUrToUrl(homeServerUrl)?.let { AvatarUrl(it) } } + else -> membersService.find(roomId, dmUser)?.avatarUrl } } diff --git a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/RoomProcessor.kt b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/RoomProcessor.kt index f851f46..5eb0353 100644 --- a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/RoomProcessor.kt +++ b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/RoomProcessor.kt @@ -17,7 +17,7 @@ internal class RoomProcessor( private val ephemeralEventsUseCase: EphemeralEventsUseCase, ) { - suspend fun processRoom(roomToProcess: RoomToProcess, isInitialSync: Boolean): RoomState { + suspend fun processRoom(roomToProcess: RoomToProcess, isInitialSync: Boolean): RoomState? { val members = roomToProcess.apiSyncRoom.collectMembers(roomToProcess.userCredentials) roomMembersService.insert(roomToProcess.roomId, members) @@ -28,16 +28,17 @@ internal class RoomProcessor( previousState?.events ?: emptyList(), ) - val overview = createRoomOverview(distinctEvents, roomToProcess, previousState) - unreadEventsProcessor.processUnreadState(overview, previousState?.roomOverview, newEvents, roomToProcess.userCredentials.userId, isInitialSync) + return createRoomOverview(distinctEvents, roomToProcess, previousState)?.let { + unreadEventsProcessor.processUnreadState(it, previousState?.roomOverview, newEvents, roomToProcess.userCredentials.userId, isInitialSync) - return RoomState(overview, distinctEvents).also { - roomDataSource.persist(roomToProcess.roomId, previousState, it) - ephemeralEventsUseCase.processEvents(roomToProcess) + RoomState(it, distinctEvents).also { + roomDataSource.persist(roomToProcess.roomId, previousState, it) + ephemeralEventsUseCase.processEvents(roomToProcess) + } } } - private suspend fun createRoomOverview(distinctEvents: List, roomToProcess: RoomToProcess, previousState: RoomState?): RoomOverview { + private suspend fun createRoomOverview(distinctEvents: List, roomToProcess: RoomToProcess, previousState: RoomState?): RoomOverview? { val lastMessage = distinctEvents.sortedByDescending { it.utcTimestamp }.findLastMessage() return roomOverviewProcessor.process(roomToProcess, previousState?.roomOverview, lastMessage) } @@ -56,6 +57,7 @@ private fun ApiSyncRoom.collectMembers(userCredentials: UserCredentials): List null } } diff --git a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/RoomToProcess.kt b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/RoomToProcess.kt index 757663c..9c4d7c9 100644 --- a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/RoomToProcess.kt +++ b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/RoomToProcess.kt @@ -10,4 +10,5 @@ internal data class RoomToProcess( val apiSyncRoom: ApiSyncRoom, val directMessage: UserId?, val userCredentials: UserCredentials, + val heroes: List?, ) \ No newline at end of file diff --git a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/SyncReducer.kt b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/SyncReducer.kt index 62cd990..5875854 100644 --- a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/SyncReducer.kt +++ b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/SyncReducer.kt @@ -30,7 +30,6 @@ internal class SyncReducer( suspend fun reduce(isInitialSync: Boolean, sideEffects: SideEffectResult, response: ApiSyncResponse, userCredentials: UserCredentials): ReducerResult { val directMessages = response.directMessages() - val invites = response.rooms?.invite?.map { roomInvite(it, userCredentials) } ?: emptyList() val roomsLeft = findRoomsLeft(response, userCredentials) val newRooms = response.rooms?.join?.keys?.filterNot { roomDataSource.contains(it) } ?: emptyList() @@ -46,6 +45,7 @@ internal class SyncReducer( apiSyncRoom = apiRoom, directMessage = directMessages[roomId], userCredentials = userCredentials, + heroes = apiRoom.summary?.heroes, ), isInitialSync = isInitialSync ) diff --git a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/TimelineEventsProcessor.kt b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/TimelineEventsProcessor.kt index ee68463..0ac76c1 100644 --- a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/TimelineEventsProcessor.kt +++ b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/TimelineEventsProcessor.kt @@ -38,6 +38,7 @@ internal class TimelineEventsProcessor( is ApiTimelineEvent.RoomMember -> null is ApiTimelineEvent.RoomName -> null is ApiTimelineEvent.RoomTopic -> null + is ApiTimelineEvent.CanonicalAlias -> null ApiTimelineEvent.Ignored -> null } roomEvent diff --git a/matrix/services/sync/src/test/kotlin/app/dapk/st/matrix/sync/internal/sync/EphemeralEventsUseCaseTest.kt b/matrix/services/sync/src/test/kotlin/app/dapk/st/matrix/sync/internal/sync/EphemeralEventsUseCaseTest.kt index 80ce1d9..d7f81cc 100644 --- a/matrix/services/sync/src/test/kotlin/app/dapk/st/matrix/sync/internal/sync/EphemeralEventsUseCaseTest.kt +++ b/matrix/services/sync/src/test/kotlin/app/dapk/st/matrix/sync/internal/sync/EphemeralEventsUseCaseTest.kt @@ -70,4 +70,5 @@ private fun aRoomToProcess(ephemeral: ApiEphemeral? = null) = RoomToProcess( anApiSyncRoom(ephemeral = ephemeral), directMessage = null, userCredentials = aUserCredentials(), + heroes = null, ) diff --git a/matrix/services/sync/src/test/kotlin/app/dapk/st/matrix/sync/internal/sync/TimelineEventsProcessorTest.kt b/matrix/services/sync/src/test/kotlin/app/dapk/st/matrix/sync/internal/sync/TimelineEventsProcessorTest.kt index 531351a..31ecef5 100644 --- a/matrix/services/sync/src/test/kotlin/app/dapk/st/matrix/sync/internal/sync/TimelineEventsProcessorTest.kt +++ b/matrix/services/sync/src/test/kotlin/app/dapk/st/matrix/sync/internal/sync/TimelineEventsProcessorTest.kt @@ -101,4 +101,4 @@ internal fun aRoomToProcess( apiSyncRoom: ApiSyncRoom = anApiSyncRoom(), directMessage: UserId? = null, userCredentials: UserCredentials = aUserCredentials(), -) = RoomToProcess(roomId, apiSyncRoom, directMessage, userCredentials) +) = RoomToProcess(roomId, apiSyncRoom, directMessage, userCredentials, heroes = null) diff --git a/test-harness/build.gradle b/test-harness/build.gradle index 4664053..e694fdd 100644 --- a/test-harness/build.gradle +++ b/test-harness/build.gradle @@ -9,7 +9,7 @@ test { dependencies { kotlinTest(it) - testImplementation 'app.cash.turbine:turbine:0.9.0' + testImplementation 'app.cash.turbine:turbine:0.10.0' testImplementation Dependencies.mavenCentral.kotlinSerializationJson diff --git a/test-harness/src/test/kotlin/SmokeTest.kt b/test-harness/src/test/kotlin/SmokeTest.kt index 8642059..d692c96 100644 --- a/test-harness/src/test/kotlin/SmokeTest.kt +++ b/test-harness/src/test/kotlin/SmokeTest.kt @@ -20,10 +20,7 @@ import org.junit.jupiter.api.MethodOrderer import org.junit.jupiter.api.Order import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestMethodOrder -import test.MatrixTestScope -import test.TestMatrix -import test.flowTest -import test.restoreLoginAndInitialSync +import test.* import java.nio.file.Paths import java.util.* @@ -35,8 +32,8 @@ class SmokeTest { @Test @Order(1) fun `can register accounts`() = runTest { - SharedState._alice = createAndRegisterAccount() - SharedState._bob = createAndRegisterAccount() + SharedState._alice = createAndRegisterAccount("alice") + SharedState._bob = createAndRegisterAccount("bob") } @Test @@ -94,7 +91,7 @@ class SmokeTest { @Test fun `can import E2E room keys file`() = runTest { - val ignoredUser = TestUser("ignored", RoomMember(UserId("ignored"), null, null), "ignored") + val ignoredUser = TestUser("ignored", RoomMember(UserId("ignored"), null, null), "ignored", "ignored") val cryptoService = TestMatrix(ignoredUser, includeLogging = true).client.cryptoService() val stream = loadResourceStream("element-keys.txt") @@ -133,10 +130,10 @@ class SmokeTest { } } -private suspend fun createAndRegisterAccount(): TestUser { +private suspend fun createAndRegisterAccount(testUsername: String): TestUser { val aUserName = "${UUID.randomUUID()}" val userId = UserId("@$aUserName:localhost:8080") - val aUser = TestUser("aaaa11111zzzz", RoomMember(userId, aUserName, null), HTTPS_TEST_SERVER_URL) + val aUser = TestUser("aaaa11111zzzz", RoomMember(userId, aUserName, null), HTTPS_TEST_SERVER_URL, testUsername) val result = TestMatrix(aUser, includeLogging = true, includeHttpLogging = true) .client @@ -167,26 +164,35 @@ private suspend fun login(user: TestUser) { } object SharedState { + val alice: TestUser get() = _alice!! var _alice: TestUser? = null + set(value) { + field = value!! + TestUsers.users.add(value) + } val bob: TestUser get() = _bob!! var _bob: TestUser? = null + set(value) { + field = value!! + TestUsers.users.add(value) + } val sharedRoom: RoomId get() = _sharedRoom!! var _sharedRoom: RoomId? = null } -data class TestUser(val password: String, val roomMember: RoomMember, val homeServer: String) +data class TestUser(val password: String, val roomMember: RoomMember, val homeServer: String, val testName: String) data class TestMessage(val content: String, val author: RoomMember) fun String.from(roomMember: RoomMember) = TestMessage("$this - ${UUID.randomUUID()}", roomMember) fun testAfterInitialSync(block: suspend MatrixTestScope.(TestMatrix, TestMatrix) -> Unit) { - restoreLoginAndInitialSync(TestMatrix(SharedState.alice, includeLogging = false), TestMatrix(SharedState.bob, includeLogging = false), block) + restoreLoginAndInitialSync(TestMatrix(SharedState.alice, includeLogging = true), TestMatrix(SharedState.bob, includeLogging = false), block) } private fun Flow.automaticVerification(testMatrix: TestMatrix) = this.onEach { diff --git a/test-harness/src/test/kotlin/test/Test.kt b/test-harness/src/test/kotlin/test/Test.kt index 91da2a0..83cdc01 100644 --- a/test-harness/src/test/kotlin/test/Test.kt +++ b/test-harness/src/test/kotlin/test/Test.kt @@ -5,17 +5,13 @@ package test import TestMessage import TestUser import app.dapk.st.core.extensions.ifNull -import app.dapk.st.matrix.common.MxUrl import app.dapk.st.matrix.common.RoomId import app.dapk.st.matrix.common.RoomMember -import app.dapk.st.matrix.common.convertMxUrToUrl -import app.dapk.st.matrix.http.MatrixHttpClient import app.dapk.st.matrix.message.MessageService import app.dapk.st.matrix.message.messageService import app.dapk.st.matrix.sync.RoomEvent import app.dapk.st.matrix.sync.syncService import io.ktor.client.* -import io.ktor.client.call.* import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.util.cio.* @@ -28,7 +24,6 @@ import org.amshove.kluent.fail import org.amshove.kluent.shouldBeEqualTo import java.io.File import java.math.BigInteger -import java.net.URL import java.security.MessageDigest import java.util.* diff --git a/test-harness/src/test/kotlin/test/TestMatrix.kt b/test-harness/src/test/kotlin/test/TestMatrix.kt index 590056e..411f8b6 100644 --- a/test-harness/src/test/kotlin/test/TestMatrix.kt +++ b/test-harness/src/test/kotlin/test/TestMatrix.kt @@ -46,6 +46,12 @@ import java.io.File import java.time.Clock import javax.imageio.ImageIO +object TestUsers { + + val users = mutableSetOf() + +} + class TestMatrix( private val user: TestUser, temporaryDatabase: Boolean = false, @@ -53,10 +59,11 @@ class TestMatrix( includeLogging: Boolean = false, ) { - private val errorTracker = PrintingErrorTracking(prefix = user.roomMember.id.value.split(":")[0]) + private val errorTracker = PrintingErrorTracking(prefix = user.testName) private val logger: MatrixLogger = { tag, message -> if (includeLogging) { - println("${user.roomMember.id.value.split(":")[0]} $tag $message") + val messageWithIdReplaceByName = TestUsers.users.fold(message) { acc, user -> acc.replace(user.roomMember.id.value, "*${user.testName}") } + println("${user.testName} $tag $messageWithIdReplaceByName") } } diff --git a/version.json b/version.json index 8335d3b..5f46553 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { - "code": 16, - "name": "15/09/2022-V1" + "code": 17, + "name": "19/09/2022-V1" } \ No newline at end of file