Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce AuthenticatedXrpcBlueskyApi convenience implementation #14

Merged
merged 2 commits into from
Dec 26, 2024
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 @@ -9,15 +9,12 @@ import io.ktor.client.plugins.logging.Logging
import io.ktor.http.takeFrom
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.yield
import me.tatarka.inject.annotations.Inject
import sh.christian.ozone.BlueskyApi
import sh.christian.ozone.XrpcBlueskyApi
import sh.christian.ozone.app.Supervisor
import sh.christian.ozone.di.SingleInApp
import sh.christian.ozone.login.LoginRepository
Expand All @@ -29,29 +26,27 @@ class ApiProvider(
private val apiRepository: ServerRepository,
private val loginRepository: LoginRepository,
) : Supervisor() {

private val apiHost = MutableStateFlow(apiRepository.server.host)
private val auth = MutableStateFlow(loginRepository.auth)
private val tokens = MutableStateFlow(loginRepository.auth?.toTokens())

private val client = HttpClient(engine) {
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.NONE
}

install(XrpcAuthPlugin) {
authTokens = tokens
}

install(DefaultRequest) {
url.takeFrom(apiHost.value)
}

expectSuccess = false
}

val api: BlueskyApi = XrpcBlueskyApi(client)
private val _api = AuthenticatedXrpcBlueskyApi(
httpClient = client,
initialTokens = loginRepository.auth?.toTokens(),
)

val api: BlueskyApi = _api

override suspend fun CoroutineScope.onStart() {
coroutineScope {
Expand All @@ -62,15 +57,7 @@ class ApiProvider(
}

launch(OzoneDispatchers.IO) {
loginRepository.authFlow().collect {
tokens.value = it?.toTokens()
yield()
auth.value = it
}
}

launch(OzoneDispatchers.IO) {
tokens.collect { tokens ->
_api.authTokens.collect { tokens ->
if (tokens != null) {
loginRepository.auth = loginRepository.auth?.withTokens(tokens)
} else {
Expand All @@ -81,11 +68,13 @@ class ApiProvider(
}
}

fun auth(): Flow<AuthInfo?> = auth
fun signOut() {
_api.clearCredentials()
}

private fun AuthInfo.toTokens() = Tokens(accessJwt, refreshJwt)
private fun AuthInfo.toTokens() = BlueskyAuthPlugin.Tokens(accessJwt, refreshJwt)

private fun AuthInfo.withTokens(tokens: Tokens) = copy(
private fun AuthInfo.withTokens(tokens: BlueskyAuthPlugin.Tokens) = copy(
accessJwt = tokens.auth,
refreshJwt = tokens.refresh,
)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class AppWorkflow(
context: RenderContext,
): AppScreen = when (renderState) {
is ShowingLogin -> {
context.runningWorker(apiProvider.auth().filterNotNull().asWorker(), "has-auth") { auth ->
context.runningWorker(loginRepository.authFlow().filterNotNull().asWorker(), "has-auth") { auth ->
action {
state = ShowingLoggedIn(HomeProps(auth, 0))
}
Expand All @@ -68,7 +68,7 @@ class AppWorkflow(
state = ShowingLoggedIn(renderState.props.copy(unreadNotificationCount = unread))
}
}
context.runningWorker(apiProvider.auth().filter { it == null }.asWorker(), "no-auth") {
context.runningWorker(loginRepository.authFlow().filter { it == null }.asWorker(), "no-auth") {
action {
state = ShowingLogin
}
Expand All @@ -78,7 +78,7 @@ class AppWorkflow(
action {
when (output) {
is HomeOutput.CloseApp -> setOutput(Unit)
is HomeOutput.SignOut -> loginRepository.auth = null
is HomeOutput.SignOut -> apiProvider.signOut()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import sh.christian.ozone.app.Supervisor
import sh.christian.ozone.di.SingleInApp
import sh.christian.ozone.error.ErrorProps
import sh.christian.ozone.error.toErrorProps
import sh.christian.ozone.login.LoginRepository
import sh.christian.ozone.model.Notifications
import sh.christian.ozone.model.TimelinePost
import sh.christian.ozone.model.toNotification
Expand All @@ -43,6 +44,7 @@ import kotlin.time.Duration.Companion.minutes
@SingleInApp
class NotificationsRepository(
private val apiProvider: ApiProvider,
private val loginRepository: LoginRepository,
) : Supervisor() {
private val latest: MutableStateFlow<Notifications> = MutableStateFlow(EMPTY_VALUE)
private val loadErrors: MutableSharedFlow<ErrorProps> = MutableSharedFlow()
Expand All @@ -59,7 +61,7 @@ class NotificationsRepository(

@OptIn(ExperimentalCoroutinesApi::class)
override suspend fun CoroutineScope.onStart() {
apiProvider.auth()
loginRepository.authFlow()
.flatMapLatest { auth ->
if (auth != null) {
flow {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ import sh.christian.ozone.app.Supervisor
import sh.christian.ozone.di.SingleInApp
import sh.christian.ozone.error.ErrorProps
import sh.christian.ozone.error.toErrorProps
import sh.christian.ozone.login.LoginRepository
import sh.christian.ozone.model.Timeline
import sh.christian.ozone.util.toReadOnlyList

@Inject
@SingleInApp
class TimelineRepository(
private val apiProvider: ApiProvider,
private val loginRepository: LoginRepository,
): Supervisor() {
private val latest: MutableStateFlow<Timeline?> = MutableStateFlow(null)
private val loadErrors: MutableSharedFlow<ErrorProps> = MutableSharedFlow()
Expand All @@ -29,7 +31,7 @@ class TimelineRepository(
val errors: Flow<ErrorProps> = loadErrors

override suspend fun CoroutineScope.onStart() {
apiProvider.auth().filter { it == null }.collect {
loginRepository.authFlow().filter { it == null }.collect {
latest.value = null
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,21 @@ import me.tatarka.inject.annotations.Inject
import sh.christian.ozone.api.ApiProvider
import sh.christian.ozone.app.Supervisor
import sh.christian.ozone.di.SingleInApp
import sh.christian.ozone.login.LoginRepository
import sh.christian.ozone.model.FullProfile

@Inject
@SingleInApp
class MyProfileRepository(
private val apiProvider: ApiProvider,
private val userDatabase: UserDatabase,
private val loginRepository: LoginRepository,
) : Supervisor() {
private val profileFlow = MutableStateFlow<FullProfile?>(null)

@OptIn(ExperimentalCoroutinesApi::class)
override suspend fun CoroutineScope.onStart() {
apiProvider.auth().flatMapLatest { auth ->
loginRepository.authFlow().flatMapLatest { auth ->
auth?.did
?.let { did -> userDatabase.profile(UserDid(did)) }
?: flowOf(null)
Expand Down
Loading
Loading