From ef7b2fbecce7dcd760d16dfbbccaebbb9e224ff8 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Wed, 10 Jul 2024 16:04:18 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[IDLE-74]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=EC=8B=9C=20=ED=86=A0=ED=81=B0=20=EB=A1=9C=EC=BB=AC=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EB=B0=8F=20=EB=AA=A8=EB=93=A0=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=EC=97=90=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...DefaultAuthInputValidationRepository.swift | 2 +- .../Auth/DefaultAuthRepository.swift | 26 ++++++++++++++----- .../Service/Auth/CenterRegisterService.swift | 4 +-- .../Service/BaseNetworkService.swift | 2 +- .../Login/CenterLoginViewController.swift | 2 ++ 5 files changed, 26 insertions(+), 10 deletions(-) diff --git a/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthInputValidationRepository.swift b/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthInputValidationRepository.swift index a8bbb7ab..8e83b81d 100644 --- a/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthInputValidationRepository.swift +++ b/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthInputValidationRepository.swift @@ -13,7 +13,7 @@ import Entity public class DefaultAuthInputValidationRepository: AuthInputValidationRepository { - let networkService = CenterRegisterService() + let networkService = AuthService() public init() { } diff --git a/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthRepository.swift b/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthRepository.swift index 11b9cd25..84713c94 100644 --- a/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthRepository.swift +++ b/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthRepository.swift @@ -13,7 +13,7 @@ import Entity public class DefaultAuthRepository: AuthRepository { - let networkService = CenterRegisterService() + let networkService = AuthService() public init() { } @@ -52,12 +52,26 @@ public class DefaultAuthRepository: AuthRepository { guard let self = self else { return BoolResult.failure(.unknownError) } - switch response.statusCode { - case 200: - return .success(true) - default: - return .failure(self.decodeError(of: CenterRegisterError.self, data: response.data)) + if response.statusCode == 200 { + + if let dict = try JSONSerialization.jsonObject(with: response.data) as? [String: String], + let accessToken = dict["accessToken"], + let refreshToken = dict["refreshToken"] { + + // 토큰처리 + try! self.networkService.keyValueStore.saveAuthToken( + accessToken: accessToken, + refreshToken: refreshToken + ) + + #if DEBUG + print("\(#function) ✅ 토큰 저장성공") + #endif + + return .success(true) + } } + return .failure(self.decodeError(of: CenterRegisterError.self, data: response.data)) } } } diff --git a/project/Projects/Data/NetworkDataSource/Service/Auth/CenterRegisterService.swift b/project/Projects/Data/NetworkDataSource/Service/Auth/CenterRegisterService.swift index a98a9822..b9078c75 100644 --- a/project/Projects/Data/NetworkDataSource/Service/Auth/CenterRegisterService.swift +++ b/project/Projects/Data/NetworkDataSource/Service/Auth/CenterRegisterService.swift @@ -1,5 +1,5 @@ // -// CenterRegisterService.swift +// AuthService.swift // NetworkDataSource // // Created by choijunios on 7/8/24. @@ -7,6 +7,6 @@ import Foundation -public class CenterRegisterService: BaseNetworkService { +public class AuthService: BaseNetworkService { public init() { } } diff --git a/project/Projects/Data/NetworkDataSource/Service/BaseNetworkService.swift b/project/Projects/Data/NetworkDataSource/Service/BaseNetworkService.swift index 09211d2a..332c83b9 100644 --- a/project/Projects/Data/NetworkDataSource/Service/BaseNetworkService.swift +++ b/project/Projects/Data/NetworkDataSource/Service/BaseNetworkService.swift @@ -13,7 +13,7 @@ import RxMoya public class BaseNetworkService { - private let keyValueStore: KeyValueStore + public let keyValueStore: KeyValueStore init(keyValueStore: KeyValueStore = KeyChainList.shared) { self.keyValueStore = keyValueStore diff --git a/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterLoginViewController.swift b/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterLoginViewController.swift index d2cf074c..2bd103a9 100644 --- a/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterLoginViewController.swift +++ b/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterLoginViewController.swift @@ -246,6 +246,8 @@ public class CenterLoginViewController: DisposableViewController { .onCustomState { textField in textField.layer.borderColor = textField.normalBorderColor.cgColor } + + coordinator?.parent?.authFinished() } From 7f3982f9fdbe48ec876128a55af25e76282421d6 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Wed, 10 Jul 2024 16:47:06 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[IDLE-74]=20=ED=86=A0=ED=81=AC=EC=9D=84=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EB=8A=94=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=EA=B3=BC=20=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8A=94=20=EC=9A=94=EC=B2=AD=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존에는 로컬에 저장된 토큰을 무조건 사용하는 방향으로 구현하였지만, 토큰이 필요하지 않은 API도 토큰의 만료 여부를 판단합니다. 따라서 토큰을 사용하는 세션과 사용하지 않는 세션을 분리하였습니다. --- ...DefaultAuthInputValidationRepository.swift | 8 ++--- .../Auth/DefaultAuthRepository.swift | 28 +++++++++-------- .../Service/BaseNetworkService.swift | 30 +++++++++++++++++++ .../Util/KeyValueStore/KeyChainList.swift | 30 +++++++++++++++++-- .../Domain/Entity/Error/IdleError.swift | 8 +++++ 5 files changed, 86 insertions(+), 18 deletions(-) diff --git a/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthInputValidationRepository.swift b/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthInputValidationRepository.swift index 8e83b81d..8333e1d9 100644 --- a/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthInputValidationRepository.swift +++ b/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthInputValidationRepository.swift @@ -19,7 +19,7 @@ public class DefaultAuthInputValidationRepository: AuthInputValidationRepository public func requestPhoneNumberAuthentication(phoneNumber: String) -> RxSwift.Single { - networkService.request(api: .startPhoneNumberAuth(phoneNumber: phoneNumber)) + networkService.requestWithoutToken(api: .startPhoneNumberAuth(phoneNumber: phoneNumber)) .catch { [weak self] in .error(self?.filterNetworkConnection($0) ?? $0) } .map { [weak self] response in @@ -36,7 +36,7 @@ public class DefaultAuthInputValidationRepository: AuthInputValidationRepository public func authenticateAuthNumber(phoneNumber: String, authNumber: String) -> RxSwift.Single { - networkService.request(api: .checkAuthNumber(phoneNumber: phoneNumber, authNumber: authNumber)) + networkService.requestWithoutToken(api: .checkAuthNumber(phoneNumber: phoneNumber, authNumber: authNumber)) .catch { [weak self] in .error(self?.filterNetworkConnection($0) ?? $0) } .map { [weak self] response in @@ -53,7 +53,7 @@ public class DefaultAuthInputValidationRepository: AuthInputValidationRepository public func requestBusinessNumberAuthentication(businessNumber: String) -> RxSwift.Single { - networkService.request(api: .authenticateBusinessNumber(businessNumber: businessNumber)) + networkService.requestWithoutToken(api: .authenticateBusinessNumber(businessNumber: businessNumber)) .catch { [weak self] in .error(self?.filterNetworkConnection($0) ?? $0) } .map { [weak self] response in @@ -70,7 +70,7 @@ public class DefaultAuthInputValidationRepository: AuthInputValidationRepository } public func requestCheckingIdDuplication(id: String) -> RxSwift.Single { - networkService.request(api: .checkIdDuplication(id: id)) + networkService.requestWithoutToken(api: .checkIdDuplication(id: id)) .catch { [weak self] in .error(self?.filterNetworkConnection($0) ?? $0) } .map { [weak self] response in diff --git a/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthRepository.swift b/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthRepository.swift index 84713c94..2ef06b7d 100644 --- a/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthRepository.swift +++ b/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthRepository.swift @@ -29,7 +29,7 @@ public class DefaultAuthRepository: AuthRepository { let data = (try? JSONEncoder().encode(dto)) ?? Data() - return networkService.request(api: .registerCenterAccount(data: data)) + return networkService.requestWithoutToken(api: .registerCenterAccount(data: data)) .catch { [weak self] in .error(self?.filterNetworkConnection($0) ?? $0) } .map { [weak self] response in @@ -46,7 +46,7 @@ public class DefaultAuthRepository: AuthRepository { public func requestCenterLogin(id: String, password: String) -> RxSwift.Single { - return networkService.request(api: .centerLogin(id: id, password: password)) + return networkService.requestWithoutToken(api: .centerLogin(id: id, password: password)) .catch { [weak self] in .error(self?.filterNetworkConnection($0) ?? $0) } .map { [weak self] response in @@ -59,16 +59,20 @@ public class DefaultAuthRepository: AuthRepository { let refreshToken = dict["refreshToken"] { // 토큰처리 - try! self.networkService.keyValueStore.saveAuthToken( - accessToken: accessToken, - refreshToken: refreshToken - ) - - #if DEBUG - print("\(#function) ✅ 토큰 저장성공") - #endif - - return .success(true) + do { + try self.networkService.keyValueStore.saveAuthToken( + accessToken: accessToken, + refreshToken: refreshToken + ) + #if DEBUG + print("\(#function) ✅ 토큰 저장성공") + #endif + + return .success(true) + } catch { + + return .failure(.localSaveError) + } } } return .failure(self.decodeError(of: CenterRegisterError.self, data: response.data)) diff --git a/project/Projects/Data/NetworkDataSource/Service/BaseNetworkService.swift b/project/Projects/Data/NetworkDataSource/Service/BaseNetworkService.swift index 332c83b9..79b7a018 100644 --- a/project/Projects/Data/NetworkDataSource/Service/BaseNetworkService.swift +++ b/project/Projects/Data/NetworkDataSource/Service/BaseNetworkService.swift @@ -28,6 +28,13 @@ public class BaseNetworkService { return provider }() + private lazy var withoutTokenProvider: MoyaProvider = { + + let provider = MoyaProvider(session: sessionWithoutToken) + + return provider + }() + lazy var sessionWithToken: Session = { let configuration = URLSessionConfiguration.default @@ -81,6 +88,23 @@ public class BaseNetworkService { completion(.doNotRetry) } + /// Token을 요구하지 않는 요청이 사용한다. + let sessionWithoutToken: Session = { + + let configuration = URLSessionConfiguration.default + + // 단일 요청이 완료되는데 걸리는 최대 시간, 초과시 타임아웃 + configuration.timeoutIntervalForRequest = 10 + + // 하나의 리소스를 로드하는데 걸리는 시간, 재시도를 포함한다 초과시 타임아웃 + configuration.timeoutIntervalForResource = 10 + + // Cache policy: 로컬캐시를 무시하고 항상 새로운 데이터를 가져온다. + configuration.requestCachePolicy = .reloadIgnoringLocalCacheData + + return Session(configuration: configuration) + }() + } // MARK: DataRequest @@ -133,4 +157,10 @@ public extension BaseNetworkService { }) } } + + /// 토큰을 사용하지 않는 요청입니다. + func requestWithoutToken(api: TagetAPI) -> Single { + + self.withoutTokenProvider.rx.request(api) + } } diff --git a/project/Projects/Data/NetworkDataSource/Util/KeyValueStore/KeyChainList.swift b/project/Projects/Data/NetworkDataSource/Util/KeyValueStore/KeyChainList.swift index 287a6bb5..27b82870 100644 --- a/project/Projects/Data/NetworkDataSource/Util/KeyValueStore/KeyChainList.swift +++ b/project/Projects/Data/NetworkDataSource/Util/KeyValueStore/KeyChainList.swift @@ -20,18 +20,44 @@ class KeyChainList { extension KeyChainList: KeyValueStore { func save(key: String, value: String) throws { - try keyChain.set(value, key: key) + do { + try keyChain.set(value, key: key) + #if DEBUG + print("KeyChain Save Success: \(key)") + #endif + } catch { + #if DEBUG + print("UserDefaults Save Success: \(key)") + #endif + UserDefaults.standard.setValue(value, forKey: key) + } } func delete(key: String) throws { try keyChain.remove(key) + UserDefaults.standard.removeObject(forKey: key) } func removeAll() throws { try keyChain.removeAll() + + // UserDefaults의 경우 수동으로 정보를 삭제합니다. + UserDefaults.standard.removeObject(forKey: Key.Auth.kaccessToken) + UserDefaults.standard.removeObject(forKey: Key.Auth.krefreshToken) } func get(key: String) -> String? { - try? keyChain.get(key) + if let value = try? keyChain.get(key) { + #if DEBUG + print("get value from KeyChain: \(key)") + #endif + return value + } else if let value = UserDefaults.standard.string(forKey: key) { + #if DEBUG + print("get value from UserDefaults: \(key)") + #endif + return value + } + return nil } } diff --git a/project/Projects/Domain/Entity/Error/IdleError.swift b/project/Projects/Domain/Entity/Error/IdleError.swift index 33ecd1cf..35f2fc6a 100644 --- a/project/Projects/Domain/Entity/Error/IdleError.swift +++ b/project/Projects/Domain/Entity/Error/IdleError.swift @@ -51,6 +51,14 @@ public class IdleError: Error { ) } + public static var localSaveError: IdleError { + IdleError( + code: "Local-004", + message: "토큰을 로컬환경에 저장하는데 실패했습니다.", + timestamp: ISO8601DateFormatter().string(from: Date()) + ) + } + // MARK: static ServerError public static var systemError: IdleError { IdleError( From f4111d17f276840a888305a3a4689734d5f91bc3 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Thu, 11 Jul 2024 10:57:49 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[IDLE-74]=20=ED=86=A0=ED=81=B0=EC=9E=AC?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 에러타입 타입명 수정 네트워크 요청시 Moya Provider가 에러를 발생시키지 않는 스테이터스 코드 설정(200, 201, 204, 400), 현재까지 파악된 나머지는 500(서버에러) --- ...DefaultAuthInputValidationRepository.swift | 8 +-- .../Auth/DefaultAuthRepository.swift | 4 +- .../RepositoryBase+Extension.swift | 9 +++ .../Data/ConcretesTests/ConcretesTests.swift | 32 ++++++++-- .../NetworkDataSource/API/Auth/AuthAPI.swift | 9 +++ .../Data/NetworkDataSource/API/BaseAPI.swift | 11 ++++ .../Service/Auth/CenterRegisterService.swift | 5 ++ .../Service/BaseNetworkService.swift | 62 ++++++++++++++++++- .../Util/Error/DataSourceError.swift | 15 +++++ .../Domain/Entity/Error/Auth/AuthError.swift | 22 +++++++ ...Error.swift => InputValidationError.swift} | 6 +- 11 files changed, 166 insertions(+), 17 deletions(-) create mode 100644 project/Projects/Data/NetworkDataSource/Util/Error/DataSourceError.swift create mode 100644 project/Projects/Domain/Entity/Error/Auth/AuthError.swift rename project/Projects/Domain/Entity/Error/Auth/{CenterRegisterError.swift => InputValidationError.swift} (90%) diff --git a/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthInputValidationRepository.swift b/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthInputValidationRepository.swift index 8333e1d9..a99d3f7f 100644 --- a/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthInputValidationRepository.swift +++ b/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthInputValidationRepository.swift @@ -29,7 +29,7 @@ public class DefaultAuthInputValidationRepository: AuthInputValidationRepository case 204: return .success(true) default: - return .failure(self.decodeError(of: CenterRegisterError.self, data: response.data)) + return .failure(self.decodeError(of: InputValidationError.self, data: response.data)) } } } @@ -46,7 +46,7 @@ public class DefaultAuthInputValidationRepository: AuthInputValidationRepository case 204: return .success(true) default: - return .failure(self.decodeError(of: CenterRegisterError.self, data: response.data)) + return .failure(self.decodeError(of: InputValidationError.self, data: response.data)) } } } @@ -64,7 +64,7 @@ public class DefaultAuthInputValidationRepository: AuthInputValidationRepository let dto: BusinessInfoDTO = self.decodeData(data: response.data) return .success(dto.toEntity()) default: - return .failure(self.decodeError(of: CenterRegisterError.self, data: response.data)) + return .failure(self.decodeError(of: InputValidationError.self, data: response.data)) } } } @@ -82,7 +82,7 @@ public class DefaultAuthInputValidationRepository: AuthInputValidationRepository case 400: return .success(false) default: - return .failure(self.decodeError(of: CenterRegisterError.self, data: response.data)) + return .failure(self.decodeError(of: InputValidationError.self, data: response.data)) } } } diff --git a/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthRepository.swift b/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthRepository.swift index 2ef06b7d..16f7f287 100644 --- a/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthRepository.swift +++ b/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthRepository.swift @@ -39,7 +39,7 @@ public class DefaultAuthRepository: AuthRepository { case 201: return .success(true) default: - return .failure(self.decodeError(of: CenterRegisterError.self, data: response.data)) + return .failure(self.decodeError(of: AuthError.self, data: response.data)) } } } @@ -75,7 +75,7 @@ public class DefaultAuthRepository: AuthRepository { } } } - return .failure(self.decodeError(of: CenterRegisterError.self, data: response.data)) + return .failure(self.decodeError(of: AuthError.self, data: response.data)) } } } diff --git a/project/Projects/Data/ConcreteRepository/RepositoryBase+Extension.swift b/project/Projects/Data/ConcreteRepository/RepositoryBase+Extension.swift index 585a7f69..1d7549ff 100644 --- a/project/Projects/Data/ConcreteRepository/RepositoryBase+Extension.swift +++ b/project/Projects/Data/ConcreteRepository/RepositoryBase+Extension.swift @@ -9,6 +9,7 @@ import Foundation import Entity import RepositoryInterface import Moya +import NetworkDataSource extension RepositoryBase { @@ -34,6 +35,14 @@ extension RepositoryBase { func filterNetworkConnection(_ error: Error) -> URLError? { + // 데이터소스 에러 + if let dataSourceError = error as? DataSourceError { + #if DEBUG + print("데이터 소스 에러: \(error.localizedDescription)") + #endif + return nil + } + guard let moyaError = error as? MoyaError, case .underlying(let err, _) = moyaError, let afError = err.asAFError, afError.isSessionTaskError else { return nil } diff --git a/project/Projects/Data/ConcretesTests/ConcretesTests.swift b/project/Projects/Data/ConcretesTests/ConcretesTests.swift index dc7f3473..8494e179 100644 --- a/project/Projects/Data/ConcretesTests/ConcretesTests.swift +++ b/project/Projects/Data/ConcretesTests/ConcretesTests.swift @@ -14,17 +14,39 @@ final class ConcretesTests: XCTestCase { func testToken() { - // TODO: 토큰 API구현이후 테스트 코드 작성 예정 + // TODO: 로컬 맵핑 테스트 // let expectation = expectation(description: "Test function") // -// let testStore = TestKeyValueStore() +// let repo = AuthService( +// keyValueStore: TestKeyValueStore() +// ) // -// let testService = DefaultTestService(keyValueStore: testStore) +// let disposeBag = DisposeBag() // -// let single = testService.testRequest() +// let data = CenterRegistrationDTO( +// identifier: "test1234", +// password: "testpassword1234", +// phoneNumber: "010-4444-5555", +// managerName: "최준영", +// centerBusinessRegistrationNumber: "000-00-00000" +// ) // -// waitForExpectations(timeout: 10, handler: nil) +// +// repo +// .request(api: .centerLogin( +// id: "test1234", +// password: "testpassword1234") +// ) +// .subscribe { response in +// +// print(response) +// +// expectation.fulfill() +// } +// .disposed(by: disposeBag) +// +// waitForExpectations(timeout: 60, handler: nil) } func testAuth() { diff --git a/project/Projects/Data/NetworkDataSource/API/Auth/AuthAPI.swift b/project/Projects/Data/NetworkDataSource/API/Auth/AuthAPI.swift index 6dd35512..d9a08848 100644 --- a/project/Projects/Data/NetworkDataSource/API/Auth/AuthAPI.swift +++ b/project/Projects/Data/NetworkDataSource/API/Auth/AuthAPI.swift @@ -20,6 +20,7 @@ public enum AuthAPI { case checkIdDuplication(id: String) case registerCenterAccount(data: Data) case centerLogin(id: String, password: String) + case reissueToken(refreshToken: String) } extension AuthAPI: BaseAPI { @@ -41,6 +42,8 @@ extension AuthAPI: BaseAPI { return .post case .centerLogin: return .post + case .reissueToken: + return .post } } @@ -58,6 +61,8 @@ extension AuthAPI: BaseAPI { "center/join" case .centerLogin: "center/login" + case .reissueToken: + "center/refresh" } } @@ -72,6 +77,8 @@ extension AuthAPI: BaseAPI { case .centerLogin(let id, let password): params["identifier"] = id params["password"] = password + case .reissueToken(let refreshToken): + params["refreshToken"] = refreshToken default: break } @@ -95,6 +102,8 @@ extension AuthAPI: BaseAPI { return .requestData(data) case .centerLogin: return .requestParameters(parameters: bodyParameters ?? [:], encoding: parameterEncoding) + case .reissueToken: + return .requestParameters(parameters: bodyParameters ?? [:], encoding: parameterEncoding) default: return .requestPlain } diff --git a/project/Projects/Data/NetworkDataSource/API/BaseAPI.swift b/project/Projects/Data/NetworkDataSource/API/BaseAPI.swift index a3cde282..2800dc63 100644 --- a/project/Projects/Data/NetworkDataSource/API/BaseAPI.swift +++ b/project/Projects/Data/NetworkDataSource/API/BaseAPI.swift @@ -42,4 +42,15 @@ public extension BaseAPI { return ["Content-Type": "application/json"] } + + var validationType: ValidationType { + .customCodes( + [ + 200, + 201, + 204, + 400, + ] + ) + } } diff --git a/project/Projects/Data/NetworkDataSource/Service/Auth/CenterRegisterService.swift b/project/Projects/Data/NetworkDataSource/Service/Auth/CenterRegisterService.swift index b9078c75..352d0374 100644 --- a/project/Projects/Data/NetworkDataSource/Service/Auth/CenterRegisterService.swift +++ b/project/Projects/Data/NetworkDataSource/Service/Auth/CenterRegisterService.swift @@ -8,5 +8,10 @@ import Foundation public class AuthService: BaseNetworkService { + public init() { } + + public override init(keyValueStore: KeyValueStore) { + super.init(keyValueStore: keyValueStore) + } } diff --git a/project/Projects/Data/NetworkDataSource/Service/BaseNetworkService.swift b/project/Projects/Data/NetworkDataSource/Service/BaseNetworkService.swift index 79b7a018..5e731b74 100644 --- a/project/Projects/Data/NetworkDataSource/Service/BaseNetworkService.swift +++ b/project/Projects/Data/NetworkDataSource/Service/BaseNetworkService.swift @@ -75,17 +75,72 @@ public class BaseNetworkService { completion(.success(adaptedRequest)) } + private let tokenSession: Session = { + + let configuration = URLSessionConfiguration.default + + // 단일 요청이 완료되는데 걸리는 최대 시간, 초과시 타임아웃 + configuration.timeoutIntervalForRequest = 10 + + // 하나의 리소스를 로드하는데 걸리는 시간, 재시도를 포함한다 초과시 타임아웃 + configuration.timeoutIntervalForResource = 10 + + // Cache policy: 로컬캐시를 무시하고 항상 새로운 데이터를 가져온다. + configuration.requestCachePolicy = .reloadIgnoringLocalCacheData + + let session = Session(configuration: configuration) + + return session + }() + lazy var tokenRetrier = Retrier { [weak self] request, session, error, completion in if let httpResponse = request.response { if httpResponse.statusCode == 401 { - // TODO: 토큰 재발급후 요청 재시도 + guard let self, let (_, refreshToken) = self.keyValueStore.getAuthToken() else { + return completion(.doNotRetryWithError(DataSourceError.localStorageFetchFailure)) + } + + let provider = MoyaProvider(session: self.tokenSession) + + provider.rx + .request(.reissueToken(refreshToken: refreshToken)) + .subscribe(onSuccess: { [weak self] response in + if response.statusCode == 200 { + // 정상 응답 + if let dict = try? JSONSerialization.jsonObject(with: response.data) as? [String: String], + let accessToken = dict["accessToken"], + let refreshToken = dict["refreshToken"] { + do { + try self?.keyValueStore.saveAuthToken( + accessToken: accessToken, + refreshToken: refreshToken + ) + completion(.retry) + } catch { + // 로컬저장소 저장 에러 + completion(.doNotRetryWithError(DataSourceError.localStorageSaveFailure)) + } + } else { + // 디코딩 에러 + completion(.doNotRetryWithError(DataSourceError.decodingError)) + } + } else { + // 서버에러, 클라이언트 에러 + completion(.doNotRetryWithError(error)) + } + }, onFailure: { error in + // Validation을 통과하지 못한 에러 + completion(.doNotRetryWithError(error)) + }) + .disposed(by: self.disposeBag) + + } else { + completion(.doNotRetry) } } - - completion(.doNotRetry) } /// Token을 요구하지 않는 요청이 사용한다. @@ -105,6 +160,7 @@ public class BaseNetworkService { return Session(configuration: configuration) }() + let disposeBag = DisposeBag() } // MARK: DataRequest diff --git a/project/Projects/Data/NetworkDataSource/Util/Error/DataSourceError.swift b/project/Projects/Data/NetworkDataSource/Util/Error/DataSourceError.swift new file mode 100644 index 00000000..e1fac347 --- /dev/null +++ b/project/Projects/Data/NetworkDataSource/Util/Error/DataSourceError.swift @@ -0,0 +1,15 @@ +// +// DataSourceError.swift +// NetworkDataSource +// +// Created by choijunios on 7/10/24. +// + +import Foundation + +public enum DataSourceError: Error { + + case decodingError + case localStorageSaveFailure + case localStorageFetchFailure +} diff --git a/project/Projects/Domain/Entity/Error/Auth/AuthError.swift b/project/Projects/Domain/Entity/Error/Auth/AuthError.swift new file mode 100644 index 00000000..eabd809c --- /dev/null +++ b/project/Projects/Domain/Entity/Error/Auth/AuthError.swift @@ -0,0 +1,22 @@ +// +// AuthError.swift +// Entity +// +// Created by choijunios on 7/10/24. +// + +import Foundation + +public enum AuthError: String, CustomError { + + // undefinedError + case undefinedError="Err-000" + + public var message: String { + switch self { + // MARK: undefinedError + case .undefinedError: + "❌ 정의되지 않은 에러타입입니다. ❌" + } + } +} diff --git a/project/Projects/Domain/Entity/Error/Auth/CenterRegisterError.swift b/project/Projects/Domain/Entity/Error/Auth/InputValidationError.swift similarity index 90% rename from project/Projects/Domain/Entity/Error/Auth/CenterRegisterError.swift rename to project/Projects/Domain/Entity/Error/Auth/InputValidationError.swift index 04cc2b96..f67cc94b 100644 --- a/project/Projects/Domain/Entity/Error/Auth/CenterRegisterError.swift +++ b/project/Projects/Domain/Entity/Error/Auth/InputValidationError.swift @@ -1,13 +1,13 @@ // -// CenterRegisterError.swift +// InputValidationError.swift // Entity // -// Created by choijunios on 7/8/24. +// Created by choijunios on 7/10/24. // import Foundation -public enum CenterRegisterError: String, CustomError { +public enum InputValidationError: String, CustomError { case InvalidSmsVerificationNumber="SMS-001" case SmsVerificationNumberNotFound="SMS-002"