Skip to content

Commit

Permalink
Merge pull request #103 from 2rabs/rt/add-session-refresh
Browse files Browse the repository at this point in the history
👍 セッションのリフレッシュ処理を追加
  • Loading branch information
tatsutakein authored Dec 2, 2023
2 parents 3a638e1 + be1513e commit 170eb8f
Show file tree
Hide file tree
Showing 28 changed files with 229 additions and 159 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import club.nito.core.data.ScheduleRepository
import club.nito.core.domain.GetParticipantScheduleListUseCase
import club.nito.core.domain.GetRecentScheduleUseCase
import club.nito.core.domain.ModifyPasswordUseCase
import club.nito.core.domain.ObserveAuthStatusUseCase
import club.nito.core.domain.AuthStatusStreamUseCase
import club.nito.core.domain.ParticipateUseCase
import club.nito.core.domain.LoginUseCase
import club.nito.core.domain.LogoutUseCase
import club.nito.core.network.auth.AuthRemoteDataSource
import club.nito.core.network.participation.ParticipantRemoteDataSource
import club.nito.core.network.schedule.ScheduleRemoteDataSource
import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.gotrue.GoTrue
import io.github.jan.supabase.gotrue.Auth
import kotlinx.serialization.json.Json
import kotlin.test.Test
import kotlin.test.assertNotNull
Expand All @@ -26,7 +26,7 @@ class EntryPointTest {
// Check finer dependencies first to debug easily
assertNotNull(kmpEntryPoint.get<SupabaseClient>())
assertNotNull(kmpEntryPoint.get<Json>())
assertNotNull(kmpEntryPoint.get<GoTrue>())
assertNotNull(kmpEntryPoint.get<Auth>())

assertNotNull(kmpEntryPoint.get<AuthRemoteDataSource>())
assertNotNull(kmpEntryPoint.get<ScheduleRemoteDataSource>())
Expand All @@ -35,7 +35,7 @@ class EntryPointTest {
assertNotNull(kmpEntryPoint.get<AuthRepository>())
assertNotNull(kmpEntryPoint.get<ScheduleRepository>())

assertNotNull(kmpEntryPoint.get<ObserveAuthStatusUseCase>())
assertNotNull(kmpEntryPoint.get<AuthStatusStreamUseCase>())
assertNotNull(kmpEntryPoint.get<LoginUseCase>())
assertNotNull(kmpEntryPoint.get<ModifyPasswordUseCase>())
assertNotNull(kmpEntryPoint.get<LogoutUseCase>())
Expand Down
2 changes: 1 addition & 1 deletion app/ios/Modules/Sources/Auth/ComposeLoginScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public struct ComposeLoginScreen: UIViewControllerRepresentable {
public func makeUIViewController(context: Context) -> UIViewController {
return LoginScreen_iosKt.LoginRouteViewController(
viewModel: LoginScreenStateMachine(
observeAuthStatusUseCase: Container.shared.get(type: ObserveAuthStatusUseCase.self),
authStatusStream: Container.shared.get(type: AuthStatusStreamUseCase.self),
login: Container.shared.get(type: LoginUseCase.self),
userMessageStateHolder: Container.shared.get(type: UserMessageStateHolder.self)
),
Expand Down
4 changes: 2 additions & 2 deletions app/ios/Modules/Sources/Auth/LoginStateMachine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ enum LoginScreenEvent {
@MainActor
final class LoginStateMachine: ObservableObject {
@Dependency(\.loginUseCase) var loginUseCase
@Dependency(\.observeAuthStatusUseCase) var observeAuthStatusUseCase
@Dependency(\.authStatusStreamUseCase) var authStatusStream

@Published var state: LoginViewUIState = .init()
@Published var event: LoginScreenEvent? = nil
Expand All @@ -40,7 +40,7 @@ final class LoginStateMachine: ObservableObject {

loadTask = Task.detached { @MainActor in
do {
for try await authStatus in self.observeAuthStatusUseCase.execute() {
for try await authStatus in self.authStatusStream.execute() {
if case let status as FetchSingleResultSuccess<AuthStatus> = authStatus {
self.cachedAuthStatus = status.data
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Dependencies
import NitoKmp

public struct AuthStatusStreamUseCaseProvider {
private static var observeAuthStatusUseCase: AuthStatusStreamUseCase {
Container.shared.get(type: AuthStatusStreamUseCase.self)
}

public let execute: () -> AsyncThrowingStream<AuthStatus, Error>
}

extension AuthStatusStreamUseCaseProvider: DependencyKey {
@MainActor
static public var liveValue: AuthStatusStreamUseCaseProvider =
AuthStatusStreamUseCaseProvider(
execute: {
observeAuthStatusUseCase.invoke().stream()
}
)
}

extension DependencyValues {
public var authStatusStreamUseCase: AuthStatusStreamUseCaseProvider {
get { self[AuthStatusStreamUseCaseProvider.self] }
set { self[AuthStatusStreamUseCaseProvider.self] = newValue }
}
}

This file was deleted.

9 changes: 4 additions & 5 deletions app/ios/Modules/Sources/Navigation/RootStateMachine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ enum Routing: Hashable {
class RootStateMachine: ObservableObject {
@Published var state: RootViewUIState = .init()
@Published var path: NavigationPath = .init()
@Dependency(\.observeAuthStatusUseCase) var observeAuthStatusUseCase
@Dependency(\.authStatusStreamUseCase) var authStatusStream

private var cachedAuthStatus: AuthStatus? {
didSet {
Expand All @@ -49,10 +49,9 @@ class RootStateMachine: ObservableObject {

loadTask = Task.detached { @MainActor in
do {
for try await authStatus in self.observeAuthStatusUseCase.execute() {
if case let status as FetchSingleResultSuccess<AuthStatus> = authStatus {
self.cachedAuthStatus = status.data
}
for try await authStatus in self.authStatusStream.execute() {
self.cachedAuthStatus = authStatus
print(authStatus)
}
} catch let error {
self.state.authStatus = .failed(error)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public struct ComposeSettingsScreen: UIViewControllerRepresentable {
public func makeUIViewController(context: Context) -> UIViewController {
return SettingsScreen_iosKt.SettingsScreenUIViewController(
stateMachine: SettingsScreenStateMachine(
observeAuthStatus: Container.shared.get(type: ObserveAuthStatusUseCase.self),
authStatusStream: Container.shared.get(type: AuthStatusStreamUseCase.self),
modifyPassword: Container.shared.get(type: ModifyPasswordUseCase.self),
logout: Container.shared.get(type: LogoutUseCase.self),
userMessageStateHolder: Container.shared.get(type: UserMessageStateHolder.self)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package club.nito.app.shared

import club.nito.core.domain.ObserveAuthStatusUseCase
import club.nito.core.model.FetchSingleResult
import club.nito.core.domain.AuthStatusStreamUseCase
import club.nito.core.model.AuthStatus
import club.nito.core.ui.StateMachine
import club.nito.core.ui.buildUiState
import club.nito.core.ui.stateMachineScope
Expand All @@ -10,19 +10,24 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn

class NitoAppStateMachine(
observeAuthStatus: ObserveAuthStatusUseCase,
authStatusStream: AuthStatusStreamUseCase,
) : StateMachine() {
private val authStatus = observeAuthStatus().stateIn(
private val authStatus = authStatusStream().stateIn(
scope = stateMachineScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = FetchSingleResult.Loading,
initialValue = AuthStatus.Loading,
)

val uiState: StateFlow<NitoAppUiState> = buildUiState(authStatus) {
if (it !is FetchSingleResult.Success) {
return@buildUiState NitoAppUiState.Loading
}
when (it) {
AuthStatus.Loading -> NitoAppUiState.Loading

AuthStatus.NotAuthenticated,
is AuthStatus.Authenticated,
-> NitoAppUiState.Success(it)

NitoAppUiState.Success(it.data)
// TODO: 適切なハンドリングを行う
AuthStatus.NetworkError -> NitoAppUiState.Loading
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ private fun RouteBuilder.root(
),
),
)

else -> {}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import org.koin.dsl.module
val appModule = module {
factory {
NitoAppStateMachine(
observeAuthStatus = get(),
authStatusStream = get(),
)
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package club.nito.core.data

import club.nito.core.model.AuthStatus
import club.nito.core.model.FetchSingleResult
import club.nito.core.model.UserInfo
import kotlinx.coroutines.flow.Flow

Expand All @@ -12,7 +11,7 @@ public sealed interface AuthRepository {
/**
* 認証情報の状態
*/
public val authStatus: Flow<FetchSingleResult<AuthStatus>>
public val authStatus: Flow<AuthStatus>

/**
* ログインする
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package club.nito.core.data

import club.nito.core.model.AuthStatus
import club.nito.core.model.FetchSingleResult
import club.nito.core.model.UserInfo
import club.nito.core.network.auth.AuthRemoteDataSource
import kotlinx.coroutines.flow.Flow

public class DefaultAuthRepository(
private val remoteDataSource: AuthRemoteDataSource,
) : AuthRepository {
override val authStatus: Flow<FetchSingleResult<AuthStatus>> = remoteDataSource.authStatus
override val authStatus: Flow<AuthStatus> = remoteDataSource.authStatus

override suspend fun login(email: String, password: String): Unit = remoteDataSource.login(
email = email,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package club.nito.core.domain

import club.nito.core.data.AuthRepository
import club.nito.core.model.AuthStatus
import kotlinx.coroutines.flow.Flow

/**
* 認証状態を購読するユースケース
*/
public sealed interface AuthStatusStreamUseCase {
public operator fun invoke(): Flow<AuthStatus>
}

public class AuthStatusStreamExecutor(
private val authRepository: AuthRepository,
) : AuthStatusStreamUseCase {
override fun invoke(): Flow<AuthStatus> = authRepository.authStatus
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package club.nito.core.domain.di

import club.nito.core.domain.AuthStatusStreamExecutor
import club.nito.core.domain.AuthStatusStreamUseCase
import club.nito.core.domain.GetParticipantScheduleListExecutor
import club.nito.core.domain.GetParticipantScheduleListUseCase
import club.nito.core.domain.GetRecentScheduleExecutor
Expand All @@ -10,8 +12,6 @@ import club.nito.core.domain.LogoutExecutor
import club.nito.core.domain.LogoutUseCase
import club.nito.core.domain.ModifyPasswordExecutor
import club.nito.core.domain.ModifyPasswordUseCase
import club.nito.core.domain.ObserveAuthStatusExecutor
import club.nito.core.domain.ObserveAuthStatusUseCase
import club.nito.core.domain.ParticipateExecutor
import club.nito.core.domain.ParticipateUseCase
import org.koin.core.module.Module
Expand All @@ -20,7 +20,7 @@ import org.koin.dsl.bind
import org.koin.dsl.module

public val useCaseModule: Module = module {
singleOf(::ObserveAuthStatusExecutor) bind ObserveAuthStatusUseCase::class
singleOf(::AuthStatusStreamExecutor) bind AuthStatusStreamUseCase::class
singleOf(::LoginExecutor) bind LoginUseCase::class
singleOf(::ModifyPasswordExecutor) bind ModifyPasswordUseCase::class
singleOf(::LogoutExecutor) bind LogoutUseCase::class
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package club.nito.core.model

public sealed interface AuthStatus {
public data object Loading : AuthStatus

public data object NotAuthenticated : AuthStatus

public data class Authenticated(val session: UserSession) : AuthStatus

public data object NetworkError : AuthStatus
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package club.nito.core.network

import club.nito.core.model.ApiException
import club.nito.core.model.NitoError
import club.nito.core.network.auth.AuthRemoteDataSource
import io.ktor.client.network.sockets.SocketTimeoutException
import io.ktor.client.plugins.HttpRequestTimeoutException
import io.ktor.client.plugins.ResponseException
import io.ktor.util.cio.ChannelReadException
import kotlinx.coroutines.TimeoutCancellationException

public class NetworkService(
public val authRemoteDataSource: AuthRemoteDataSource,
) {
public suspend inline operator fun <reified T : Any> invoke(
block: () -> T,
): T = try {
authRemoteDataSource.authIfNeeded()
block()
} catch (e: Throwable) {
throw e.toNitoError()
}
}

public fun Throwable.toNitoError(): NitoError = when (this) {
is NitoError -> this

is ResponseException -> ApiException.ServerException(this)

is ChannelReadException -> ApiException.NetworkException(this)

is TimeoutCancellationException,
is HttpRequestTimeoutException,
is SocketTimeoutException,
-> ApiException.TimeoutException(this)

else -> ApiException.UnknownException(this)
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package club.nito.core.network.auth

import club.nito.core.model.AuthStatus
import club.nito.core.model.FetchSingleResult
import club.nito.core.model.UserInfo
import kotlinx.coroutines.flow.Flow

public sealed interface AuthRemoteDataSource {
public val authStatus: Flow<FetchSingleResult<AuthStatus>>
public val authStatus: Flow<AuthStatus>

public suspend fun login(email: String, password: String)
public suspend fun logout()
public suspend fun modifyAuthUser(email: String?, password: String?): UserInfo
public suspend fun authIfNeeded()
}
Loading

0 comments on commit 170eb8f

Please sign in to comment.