diff --git a/Spon-Us/Auth/AuthPlugin.swift b/Spon-Us/Auth/AuthPlugin.swift index bee9b92..a696673 100644 --- a/Spon-Us/Auth/AuthPlugin.swift +++ b/Spon-Us/Auth/AuthPlugin.swift @@ -10,56 +10,74 @@ import Moya final class AuthPlugin: PluginType { var onRetrySuccess: (() -> Void)? + var onRetryFail: (() -> Void)? + + private var originalRequest: URLRequest? + + func willSend(_ request: RequestType, target: TargetType) { + originalRequest = request.request + } func didReceive(_ result: Result, target: TargetType) { switch result { case .success(let response): if response.statusCode == 401 { - // 토큰이 만료된 경우 -> Reissue - TokenManager.shared.refreshAccessToken { success in - if success { - // 원래 요청 재시도. - guard var newRequest = response.request else { - print("🤢 원래 요청을 생성할 수 없습니다.") - return - } - if let newToken = TokenManager.shared.accessToken { - newRequest.setValue("Bearer \(newToken)", forHTTPHeaderField: "Authorization") + handle401Error(for: response) + } + case .failure(let error): + if error.response?.statusCode == 401 { + handle401Error(for: error.response!) + } else { + print("Request failed with error: \(error)") + } + } + } + + private func handle401Error(for response: Response) { + // 토큰 재발급 시도 + TokenManager.shared.refreshAccessToken { success in + if success { + // 원래 요청 재시도 + guard var newRequest = self.originalRequest else { + print("🤢 원래 요청을 생성할 수 없습니다.") + self.onRetryFail?() + return + } + if let newToken = TokenManager.shared.accessToken { + newRequest.setValue("Bearer \(newToken)", forHTTPHeaderField: "Authorization") + } + let task = URLSession.shared.dataTask(with: newRequest) { data, response, error in + if let error = error { + print("🤢 재시도 요청 실패: \(error)") + DispatchQueue.main.async { + self.onRetryFail?() } - let task = URLSession.shared.dataTask(with: newRequest) { data, response, error in - if let error = error { - print("🤢 재시도 요청 실패: \(error)") - } else if let response = response as? HTTPURLResponse { - if (200...299).contains(response.statusCode) { - print("😊 재시도 요청 성공: \(response.statusCode)") - DispatchQueue.main.async { - self.onRetrySuccess?() - } - } else { - print("🤢 재시도 요청 실패: \(response.statusCode)") - } - } else { - print("🤢 알 수 없는 오류 발생") + } else if let response = response as? HTTPURLResponse { + if (200...299).contains(response.statusCode) { + print("😊 재시도 요청 성공: \(response.statusCode)") + DispatchQueue.main.async { + self.onRetrySuccess?() + } + } else { + print("🤢 재시도 요청 실패: \(response.statusCode)") + DispatchQueue.main.async { + self.onRetryFail?() } } - task.resume() } else { - print("🤢 refreshToken 만료!!") + print("🤢 알 수 없는 오류 발생") + DispatchQueue.main.async { + self.onRetryFail?() + } } } + task.resume() + } else { + print("🤢 refreshToken 만료!!") + DispatchQueue.main.async { + self.onRetryFail?() + } } - case .failure(let error): - print("Request failed with error: \(error)") } } } - - -// MARK: Plugin 사용법! -//let authPlugin = AuthPlugin() -// onRetrySuccess -> 작동 시킨 후 UI적으로 재시동시킬것 -//authPlugin.onRetrySuccess = { [weak self] in -// self?.fetchReservationAPI(for: self?.selectedDate ?? Date()) -// self?.fetchWeekReservationAPI(for: self?.selectedDate ?? Date()) -//} -//self.provider = MoyaProvider(plugins: [authPlugin]) diff --git a/Spon-Us/Auth/TokenManager.swift b/Spon-Us/Auth/TokenManager.swift index 3cc0d8a..2f0a7c4 100644 --- a/Spon-Us/Auth/TokenManager.swift +++ b/Spon-Us/Auth/TokenManager.swift @@ -6,10 +6,12 @@ // import Foundation +import Moya class TokenManager { static let shared = TokenManager() - + private let provider = MoyaProvider(plugins: [NetworkLoggerPlugin()]) + private init() { } var accessToken: String? { @@ -55,9 +57,39 @@ class TokenManager { UserDefaults.standard.removeObject(forKey: "fcmToken") } - // reissue API 붙이기 func refreshAccessToken(completion: @escaping (Bool) -> Void) { + guard let refreshToken = refreshToken else { + completion(false) + return + } + provider.request(.getReissue(refreshToken: refreshToken)) { result in + switch result { + case .success(let response): + if response.statusCode == 400 { + print("🚨토큰 재발급 네트워크 실패: 상태 코드 400 -> refresh 토큰 만료") + completion(false) + return + } + do { + if let loginResponse = try? response.map(LoginResponse.self) { + self.accessToken = loginResponse.content.accessToken + self.refreshToken = loginResponse.content.refreshToken + completion(true) + print("🚨reissue API 성공") + print(" access:\(loginResponse.content.accessToken)") + print(" refresh:\(loginResponse.content.refreshToken)") + } + } catch { + print("🚨reissue API 네트워크 매핑 실패") + completion(false) + } + case .failure(let error): + print("🚨reissue API 네트워크 실패") + print("refreshToken:\(refreshToken)") + completion(false) + } + } } // refreshToken 만료됐는지 확인하는 로직 붙이기 diff --git a/Spon-Us/Home/HomeViewModel.swift b/Spon-Us/Home/HomeViewModel.swift index 557aeb5..6eba123 100644 --- a/Spon-Us/Home/HomeViewModel.swift +++ b/Spon-Us/Home/HomeViewModel.swift @@ -10,8 +10,27 @@ import Moya @Observable final class HomeViewModel { - let provider: MoyaProvider = .init() + private let authPlugin = AuthPlugin() + private var provider: MoyaProvider! + init() { + setupProvider() + setupAuthPluginCallbacks() + } + + private func setupProvider() { + self.provider = MoyaProvider(plugins: [authPlugin]) + } + + private func setupAuthPluginCallbacks() { + authPlugin.onRetrySuccess = { [weak self] in + // 재시도 성공 시 필요한 동작 추가. + } + authPlugin.onRetryFail = { [weak self] in + // loginVM.logout() + } + } + var companies: [OrganizationModel] = [] var clubs: [OrganizationModel] = [] @@ -164,7 +183,7 @@ final class HomeViewModel { case .all: filteredClubs = clubs case .advertisingAndMarketing: - filteredClubs = clubs.filter { $0.subTypes.contains("AD_MARKETING") } + filteredClubs = clubs.filter { $0.subTypes.contains("AD_MARKETING") } case .design: filteredClubs = clubs.filter { $0.subTypes.contains("DESIGN") } case .iTAndSoftware: diff --git a/Spon-Us/Network/SponusAPI.swift b/Spon-Us/Network/SponusAPI.swift index eac6fff..ab43a9a 100644 --- a/Spon-Us/Network/SponusAPI.swift +++ b/Spon-Us/Network/SponusAPI.swift @@ -23,6 +23,7 @@ enum SponusAPI { case deleteKeyword(keyword: String) case deleteSearch case getVerifyEmail(email: String) + case getReissue(refreshToken: String) } extension SponusAPI: TargetType { @@ -60,6 +61,8 @@ extension SponusAPI: TargetType { return "/api/v2/auth/login" case .getVerifyEmail: return "/api/v2/auth/verify-email" + case .getReissue: + return "/api/v2/auth/reissue" } } @@ -93,6 +96,8 @@ extension SponusAPI: TargetType { return .post case .getVerifyEmail: return .get + case .getReissue: + return .get } } @@ -126,6 +131,8 @@ extension SponusAPI: TargetType { return Data() case .getVerifyEmail: return Data() + case .getReissue: + return Data() } } @@ -163,7 +170,9 @@ extension SponusAPI: TargetType { return .requestJSONEncodable(signUpDetails) case .postLogin(loginDetails: let loginDetails): return .requestJSONEncodable(loginDetails) - case .getVerifyEmail(email: let email): + case .getVerifyEmail: + return .requestPlain + case .getReissue: return .requestPlain } } @@ -205,6 +214,8 @@ extension SponusAPI: TargetType { return ["Content-Type": "application/json"] case .getVerifyEmail(email: let email): return ["email": email] + case .getReissue(refreshToken: let refreshToken): + return ["RefreshToken": "\(refreshToken)"] } } } diff --git a/Spon-Us/Spon_UsApp.swift b/Spon-Us/Spon_UsApp.swift index 945e1a1..37e4204 100644 --- a/Spon-Us/Spon_UsApp.swift +++ b/Spon-Us/Spon_UsApp.swift @@ -16,7 +16,9 @@ struct Spon_UsApp: App { var body: some Scene { WindowGroup() { if (vm.loginSuccess || (TokenManager.shared.isAutoLogin ?? false)) && !TokenManager.shared.isRefreshTokenExpired() { - ContentView() + ContentView().onAppear(perform: { + print("\(vm.loginSuccess), \(TokenManager.shared.isAutoLogin ?? false), \(!TokenManager.shared.isRefreshTokenExpired())") + }) } else { OnBoardingView() }