Room/Timeline: simplify the apis

This commit is contained in:
ganfra
2023-06-21 16:25:18 +02:00
parent ea21ea2ace
commit 7c8df186f6
13 changed files with 134 additions and 150 deletions

View File

@@ -18,7 +18,6 @@ package io.element.android.appnav
import android.os.Parcelable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.Modifier
import androidx.lifecycle.lifecycleScope
import com.bumble.appyx.core.composable.Children
@@ -83,6 +82,7 @@ class RoomFlowNode @AssistedInject constructor(
lifecycle.subscribe(
onCreate = {
Timber.v("OnCreate")
inputs.room.open()
plugins<LifecycleCallback>().forEach { it.onFlowCreated(id, inputs.room) }
appNavigationStateService.onNavigateToRoom(id, inputs.room.roomId)
fetchRoomMembers()
@@ -149,18 +149,8 @@ class RoomFlowNode @AssistedInject constructor(
data class RoomMemberDetails(val userId: UserId) : NavTarget
}
private val timeline = inputs.room.timeline()
@Composable
override fun View(modifier: Modifier) {
DisposableEffect(Unit) {
timeline.initialize()
onDispose {
timeline.dispose()
}
}
Children(
navModel = backstack,
modifier = modifier,

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.core.coroutine
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.job
import kotlinx.coroutines.plus
fun childScopeOf(
parentScope: CoroutineScope,
dispatcher: CoroutineDispatcher,
name: String,
): CoroutineScope = run {
val supervisorJob = SupervisorJob(parent = parentScope.coroutineContext.job)
parentScope + dispatcher + supervisorJob + CoroutineName(name)
}

View File

@@ -61,6 +61,8 @@ interface MatrixRoom : Closeable {
fun timeline(): MatrixTimeline
fun open(): Result<Unit>
suspend fun userDisplayName(userId: UserId): Result<String?>
suspend fun userAvatarUrl(userId: UserId): Result<String?>

View File

@@ -28,20 +28,8 @@ interface MatrixTimeline {
)
fun paginationState(): StateFlow<PaginationState>
fun timelineItems(): Flow<List<MatrixTimelineItem>>
suspend fun paginateBackwards(requestSize: Int, untilNumberOfItems: Int): Result<Unit>
fun initialize()
fun dispose()
/**
* @param message markdown message
*/
suspend fun sendMessage(message: String): Result<Unit>
suspend fun editMessage(originalEventId: EventId, message: String): Result<Unit>
suspend fun replyMessage(inReplyToEventId: EventId, message: String): Result<Unit>
suspend fun fetchDetailsForEvent(eventId: EventId): Result<Unit>
}

View File

@@ -19,6 +19,7 @@
package io.element.android.libraries.matrix.impl
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.coroutine.childScopeOf
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
@@ -39,6 +40,7 @@ import io.element.android.libraries.matrix.impl.notification.RustNotificationSer
import io.element.android.libraries.matrix.impl.pushers.RustPushersService
import io.element.android.libraries.matrix.impl.room.RustMatrixRoom
import io.element.android.libraries.matrix.impl.room.RustRoomSummaryDataSource
import io.element.android.libraries.matrix.impl.room.roomOrNull
import io.element.android.libraries.matrix.impl.usersearch.UserProfileMapper
import io.element.android.libraries.matrix.impl.usersearch.UserSearchResultMapper
import io.element.android.libraries.matrix.impl.verification.RustSessionVerificationService
@@ -47,7 +49,7 @@ import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
@@ -66,7 +68,7 @@ import org.matrix.rustcomponents.sdk.RoomVisibility as RustRoomVisibility
class RustMatrixClient constructor(
private val client: Client,
private val sessionStore: SessionStore,
private val coroutineScope: CoroutineScope,
private val appCoroutineScope: CoroutineScope,
private val dispatchers: CoroutineDispatchers,
private val baseDirectory: File,
private val baseCacheDirectory: File,
@@ -75,13 +77,13 @@ class RustMatrixClient constructor(
override val sessionId: UserId = UserId(client.userId())
private val sessionCoroutineScope = childScopeOf(appCoroutineScope, dispatchers.main, "Session-${sessionId}")
private val verificationService = RustSessionVerificationService()
private val pushersService = RustPushersService(
client = client,
dispatchers = dispatchers,
)
private val notificationService = RustNotificationService(client)
private var slidingSyncUpdateJob: Job? = null
private val clientDelegate = object : ClientDelegate {
override fun didReceiveAuthError(isSoftLogout: Boolean) {
@@ -95,6 +97,7 @@ class RustMatrixClient constructor(
private val rustRoomSummaryDataSource: RustRoomSummaryDataSource =
RustRoomSummaryDataSource(
roomList,
sessionCoroutineScope,
dispatchers,
)
@@ -104,6 +107,7 @@ class RustMatrixClient constructor(
private val rustInvitesDataSource: RustRoomSummaryDataSource =
RustRoomSummaryDataSource(
roomList,
sessionCoroutineScope,
dispatchers,
)
@@ -127,14 +131,15 @@ class RustMatrixClient constructor(
}
override fun getRoom(roomId: RoomId): MatrixRoom? {
val roomListItem = roomList.room(roomId.value)
val roomListItem = roomList.roomOrNull(roomId.value) ?: return null
val fullRoom = roomListItem.fullRoom()
return RustMatrixRoom(
sessionId = sessionId,
roomListItem = roomListItem,
innerRoom = fullRoom,
coroutineScope = coroutineScope,
sessionCoroutineScope = sessionCoroutineScope,
coroutineDispatchers = dispatchers,
systemClock = clock
)
}
@@ -231,10 +236,8 @@ class RustMatrixClient constructor(
}
override fun close() {
slidingSyncUpdateJob?.cancel()
stopSync()
rustRoomSummaryDataSource.close()
rustInvitesDataSource.close()
sessionCoroutineScope.cancel()
client.setDelegate(null)
verificationService.destroy()
roomList.destroy()

View File

@@ -52,7 +52,7 @@ import org.matrix.rustcomponents.sdk.AuthenticationService as RustAuthentication
class RustMatrixAuthenticationService @Inject constructor(
@ApplicationContext private val context: Context,
private val baseDirectory: File,
private val coroutineScope: CoroutineScope,
private val appCoroutineScope: CoroutineScope,
private val coroutineDispatchers: CoroutineDispatchers,
private val sessionStore: SessionStore,
private val clock: SystemClock,
@@ -179,7 +179,7 @@ class RustMatrixAuthenticationService @Inject constructor(
return RustMatrixClient(
client = client,
sessionStore = sessionStore,
coroutineScope = coroutineScope,
appCoroutineScope = appCoroutineScope,
dispatchers = coroutineDispatchers,
baseDirectory = baseDirectory,
baseCacheDirectory = context.cacheDir,

View File

@@ -7,10 +7,12 @@ import org.matrix.rustcomponents.sdk.RoomList
import org.matrix.rustcomponents.sdk.RoomListEntriesListener
import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate
import org.matrix.rustcomponents.sdk.RoomListEntry
import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.RoomListState
import org.matrix.rustcomponents.sdk.RoomListStateListener
import org.matrix.rustcomponents.sdk.SlidingSyncListLoadingState
import org.matrix.rustcomponents.sdk.SlidingSyncListStateObserver
import timber.log.Timber
fun RoomList.stateFlow(): Flow<RoomListState> =
mxCallbackFlow {
@@ -46,3 +48,11 @@ fun RoomList.roomListEntriesUpdateFlow(onInitialList: suspend (List<RoomListEntr
result.entriesStream
}
fun RoomList.roomOrNull(roomId: String): RoomListItem? {
return try {
room(roomId)
} catch (failure: Throwable) {
Timber.e(failure, "Failed finding room with id=$roomId")
return null
}
}

View File

@@ -17,6 +17,7 @@
package io.element.android.libraries.matrix.impl.room
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.coroutine.childScopeOf
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
@@ -30,22 +31,25 @@ import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
import io.element.android.libraries.matrix.api.room.StateEventType
import io.element.android.libraries.matrix.api.room.roomMembers
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.api.timeline.item.event.EventType
import io.element.android.libraries.matrix.impl.media.map
import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline
import io.element.android.libraries.matrix.impl.timeline.timelineDiffFlow
import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.RequiredState
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate
import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.RoomMember
import org.matrix.rustcomponents.sdk.RoomSubscription
import org.matrix.rustcomponents.sdk.genTransactionId
import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown
import java.io.File
@@ -54,40 +58,73 @@ class RustMatrixRoom(
override val sessionId: SessionId,
private val roomListItem: RoomListItem,
private val innerRoom: Room,
private val coroutineScope: CoroutineScope,
sessionCoroutineScope: CoroutineScope,
private val coroutineDispatchers: CoroutineDispatchers,
private val systemClock: SystemClock,
) : MatrixRoom {
override val roomId = RoomId(innerRoom.id())
private val roomCoroutineScope = childScopeOf(sessionCoroutineScope, coroutineDispatchers.main, "RoomScope-$roomId")
override val membersStateFlow: StateFlow<MatrixRoomMembersState>
get() = _membersStateFlow
private var _membersStateFlow = MutableStateFlow<MatrixRoomMembersState>(MatrixRoomMembersState.Unknown)
private val isInit = MutableStateFlow(false)
private val syncUpdateFlow = MutableStateFlow(systemClock.epochMillis())
private val timeline by lazy {
RustMatrixTimeline(
matrixRoom = this,
innerRoom = innerRoom,
roomListItem = roomListItem,
coroutineScope = coroutineScope,
coroutineScope = roomCoroutineScope,
coroutineDispatchers = coroutineDispatchers
)
}
override fun syncUpdateFlow(): Flow<Long> {
//TODO branch this somehow...
return emptyFlow()
return syncUpdateFlow
}
override fun timeline(): MatrixTimeline {
return timeline
}
override fun close() {
innerRoom.destroy()
roomListItem.destroy()
override fun open(): Result<Unit> {
if (isInit.value) return Result.failure(IllegalStateException("Listener already registered"))
val settings = RoomSubscription(
requiredState = listOf(
RequiredState(key = EventType.STATE_ROOM_CANONICAL_ALIAS, value = ""),
RequiredState(key = EventType.STATE_ROOM_TOPIC, value = ""),
RequiredState(key = EventType.STATE_ROOM_JOIN_RULES, value = ""),
RequiredState(key = EventType.STATE_ROOM_POWER_LEVELS, value = ""),
),
timelineLimit = null
)
roomListItem.subscribe(settings)
innerRoom.timelineDiffFlow { initialList ->
timeline.postItems(initialList)
}.onEach {
syncUpdateFlow.value = systemClock.epochMillis()
timeline.postDiff(it)
}.launchIn(roomCoroutineScope)
roomCoroutineScope.launch {
fetchMembers()
}
isInit.value = true
return Result.success(Unit)
}
override val roomId = RoomId(innerRoom.id())
override fun close() {
if(isInit.value) {
isInit.value = false
roomCoroutineScope.cancel()
roomListItem.unsubscribe()
innerRoom.destroy()
roomListItem.destroy()
}
}
override val name: String?
get() {
@@ -264,7 +301,7 @@ class RustMatrixRoom(
}
}
override suspend fun cancelSend(transactionId: String): Result<Unit> =
override suspend fun cancelSend(transactionId: String): Result<Unit> =
withContext(coroutineDispatchers.io) {
runCatching {
innerRoom.cancelSend(transactionId)
@@ -299,4 +336,10 @@ class RustMatrixRoom(
innerRoom.setTopic(topic)
}
}
private suspend fun fetchMembers() = withContext(coroutineDispatchers.io) {
runCatching {
innerRoom.fetchMembers()
}
}
}

View File

@@ -20,8 +20,6 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.room.RoomSummary
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.launchIn
@@ -33,24 +31,21 @@ import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate
import org.matrix.rustcomponents.sdk.RoomListEntry
import org.matrix.rustcomponents.sdk.RoomListInput
import org.matrix.rustcomponents.sdk.RoomListRange
import org.matrix.rustcomponents.sdk.SlidingSyncListLoadingState
import timber.log.Timber
import java.io.Closeable
import java.util.UUID
internal class RustRoomSummaryDataSource(
private val roomList: RoomList,
private val sessionCoroutineScope: CoroutineScope,
private val coroutineDispatchers: CoroutineDispatchers,
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
) : RoomSummaryDataSource, Closeable {
private val coroutineScope = CoroutineScope(SupervisorJob() + coroutineDispatchers.io)
) : RoomSummaryDataSource {
private val roomSummaries = MutableStateFlow<List<RoomSummary>>(emptyList())
private val loadingState = MutableStateFlow(RoomSummaryDataSource.LoadingState.NotLoaded)
fun subscribeIfNeeded() {
coroutineScope.launch {
sessionCoroutineScope.launch {
roomList.roomListEntriesUpdateFlow { roomListEntries ->
val summaries = roomListEntries.map(::buildSummaryForRoomListEntry)
updateRoomSummaries {
@@ -64,10 +59,6 @@ internal class RustRoomSummaryDataSource(
}
}
override fun close() {
coroutineScope.cancel()
}
override fun roomSummaries(): StateFlow<List<RoomSummary>> {
return roomSummaries
}
@@ -78,7 +69,7 @@ internal class RustRoomSummaryDataSource(
override fun setSlidingSyncRange(range: IntRange) {
Timber.v("setVisibleRange=$range")
coroutineScope.launch {
sessionCoroutineScope.launch {
val ranges = listOf(RoomListRange(range.first.toUInt(), range.last.toUInt()))
roomList.applyInput(
RoomListInput.Viewport(ranges)
@@ -148,7 +139,8 @@ internal class RustRoomSummaryDataSource(
}
private fun buildRoomSummaryForIdentifier(identifier: String): RoomSummary {
return roomList.room(identifier).use { roomListItem ->
val roomListItem = roomList.roomOrNull(identifier) ?: return RoomSummary.Empty(identifier)
return roomListItem.use {
roomListItem.fullRoom().use { fullRoom ->
RoomSummary.Filled(
details = roomSummaryDetailsFactory.create(roomListItem, fullRoom)

View File

@@ -36,7 +36,7 @@ internal class MatrixTimelineDiffProcessor(
private val timelineItemFactory: MatrixTimelineItemMapper,
) {
fun onUpdate(diff: TimelineDiff) {
fun postDiff(diff: TimelineDiff) {
coroutineScope.launch {
updateTimelineItems {
applyDiff(diff)
@@ -122,4 +122,5 @@ internal class MatrixTimelineDiffProcessor(
private fun TimelineItem.asMatrixTimelineItem(): MatrixTimelineItem {
return timelineItemFactory.map(this)
}
}

View File

@@ -21,40 +21,30 @@ import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.EventType
import io.element.android.libraries.matrix.impl.timeline.item.event.EventMessageMapper
import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper
import io.element.android.libraries.matrix.impl.timeline.item.event.TimelineEventContentMapper
import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTimelineItemMapper
import io.element.android.libraries.matrix.impl.util.TaskHandleBag
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.PaginationOptions
import org.matrix.rustcomponents.sdk.RequiredState
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.RoomSubscription
import org.matrix.rustcomponents.sdk.TimelineDiff
import org.matrix.rustcomponents.sdk.TimelineItem
import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean
class RustMatrixTimeline(
coroutineScope: CoroutineScope,
private val matrixRoom: MatrixRoom,
private val innerRoom: Room,
private val roomListItem: RoomListItem,
private val coroutineScope: CoroutineScope,
private val coroutineDispatchers: CoroutineDispatchers,
) : MatrixTimeline {
private val isInit = AtomicBoolean(false)
private val timelineItems: MutableStateFlow<List<MatrixTimelineItem>> =
MutableStateFlow(emptyList())
@@ -81,7 +71,6 @@ class RustMatrixTimeline(
timelineItemFactory = timelineItemFactory,
)
private val listenerTokens = TaskHandleBag()
override fun paginationState(): StateFlow<MatrixTimeline.PaginationState> {
return paginationState
}
@@ -91,38 +80,12 @@ class RustMatrixTimeline(
return timelineItems.sample(50)
}
override fun initialize() {
Timber.v("Init timeline for room ${matrixRoom.roomId}")
coroutineScope.launch {
subscribeAndAddListener(this)
.onSuccess {
isInit.set(true)
}
.onFailure {
Timber.e("Failed adding timeline listener on room with identifier: ${matrixRoom.roomId})")
}
}
internal fun postItems(items: List<TimelineItem>) {
timelineItems.value = items.map(timelineItemFactory::map)
}
override fun dispose() {
Timber.v("Dispose timeline for room ${matrixRoom.roomId}")
listenerTokens.dispose()
isInit.set(false)
}
/**
* @param message markdown message
*/
override suspend fun sendMessage(message: String): Result<Unit> {
return matrixRoom.sendMessage(message)
}
override suspend fun editMessage(originalEventId: EventId, message: String): Result<Unit> {
return matrixRoom.editMessage(originalEventId, message = message)
}
override suspend fun replyMessage(inReplyToEventId: EventId, message: String): Result<Unit> {
return matrixRoom.replyMessage(inReplyToEventId, message)
internal fun postDiff(timelineDiff: TimelineDiff) {
timelineDiffProcessor.postDiff(timelineDiff)
}
override suspend fun fetchDetailsForEvent(eventId: EventId): Result<Unit> = withContext(coroutineDispatchers.io) {
@@ -134,9 +97,6 @@ class RustMatrixTimeline(
override suspend fun paginateBackwards(requestSize: Int, untilNumberOfItems: Int): Result<Unit> = withContext(coroutineDispatchers.io) {
runCatching {
Timber.v("Start back paginating for room ${matrixRoom.roomId} ")
if (!isInit.get()) {
throw IllegalStateException("Timeline is not init yet")
}
val paginationOptions = PaginationOptions.UntilNumItems(
eventLimit = requestSize.toUShort(),
items = untilNumberOfItems.toUShort(),
@@ -149,30 +109,4 @@ class RustMatrixTimeline(
Timber.v("Success back paginating for room ${matrixRoom.roomId}")
}
}
private fun subscribeAndAddListener(coroutineScope: CoroutineScope): Result<Unit> {
return runCatching {
val settings = RoomSubscription(
requiredState = listOf(
RequiredState(key = EventType.STATE_ROOM_CANONICAL_ALIAS, value = ""),
RequiredState(key = EventType.STATE_ROOM_TOPIC, value = ""),
RequiredState(key = EventType.STATE_ROOM_JOIN_RULES, value = ""),
RequiredState(key = EventType.STATE_ROOM_POWER_LEVELS, value = ""),
),
timelineLimit = null
)
roomListItem.subscribe(settings)
innerRoom.timelineDiffFlow { initialList ->
timelineItems.value = initialList.map(timelineItemFactory::map)
}.onEach {
timelineDiffProcessor.onUpdate(it)
}.launchIn(coroutineScope)
}
}
private suspend fun fetchMembers() = withContext(coroutineDispatchers.io) {
runCatching {
innerRoom.fetchMembers()
}
}
}

View File

@@ -71,18 +71,6 @@ class FakeMatrixTimeline(
isInitialized = false
}
override suspend fun sendMessage(message: String): Result<Unit> {
return Result.success(Unit)
}
override suspend fun editMessage(originalEventId: EventId, message: String): Result<Unit> {
return Result.success(Unit)
}
override suspend fun replyMessage(inReplyToEventId: EventId, message: String): Result<Unit> {
return Result.success(Unit)
}
override suspend fun fetchDetailsForEvent(eventId: EventId): Result<Unit> {
return Result.success(Unit)
}

View File

@@ -42,7 +42,7 @@ class MainActivity : ComponentActivity() {
RustMatrixAuthenticationService(
context = applicationContext,
baseDirectory = baseDirectory,
coroutineScope = Singleton.appScope,
appCoroutineScope = Singleton.appScope,
coroutineDispatchers = Singleton.coroutineDispatchers,
sessionStore = InMemorySessionStore(),
clock = DefaultSystemClock()