-
Notifications
You must be signed in to change notification settings - Fork 2
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
자동로그인 오류 수정 및 토큰 재발급 로직 구현 #314
Conversation
// MARK: - UIApplicationDelegate | ||
|
||
extension AppDelegate { | ||
|
||
let gcmMessageIDKey = "gcm.message_id" | ||
|
||
let mainFlowStore = QappleApp.mainFlowStore | ||
|
||
// 앱이 켜졌을 때 | ||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { | ||
|
||
// 기본 서버 설정 | ||
/// 앱이 켜졌을 때 | ||
func application( | ||
_ application: UIApplication, | ||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil | ||
) -> Bool { | ||
RepositoryService.shared.configureServer(to: .test) | ||
|
||
UIApplication.shared.registerForRemoteNotifications() | ||
|
||
// 파이어베이스 설정 | ||
FirebaseApp.configure() | ||
|
||
// Setting Up Notifications... | ||
// 원격 알림 등록 | ||
if #available(iOS 10.0, *) { | ||
// For iOS 10 display notification (sent via APNS) | ||
UNUserNotificationCenter.current().delegate = self | ||
|
||
let authOption: UNAuthorizationOptions = [.alert, .badge, .sound] | ||
UNUserNotificationCenter.current().requestAuthorization( | ||
options: authOption, | ||
completionHandler: {_, _ in }) | ||
} else { | ||
let settings: UIUserNotificationSettings = | ||
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil) | ||
application.registerUserNotificationSettings(settings) | ||
} | ||
|
||
// APNs에 기기 등록을 요청 | ||
application.registerForRemoteNotifications() | ||
|
||
|
||
// Setting Up Cloud Messaging... | ||
// 메세징 델리겟 | ||
Messaging.messaging().delegate = self | ||
|
||
UNUserNotificationCenter.current().delegate = self | ||
|
||
setupPushNotification(application) | ||
setupFirebase() | ||
return true | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AppDelegate 파일을 정리했습니다. 같은 목적의 코드는 함수로 묶어 분리했습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UNUserNotificationCenterDelegate 부분은 따로 파일로 분리했습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
appDelegate 부분은 간결해졌네요!!
let response = try await AnswerAPI.fetchListOfMine( | ||
threshold: threshold, | ||
pageSize: 30, | ||
server: repositoryService.server, | ||
accessToken: accessToken() | ||
) | ||
let response = try await RepositoryService.shared.request { server, accessToken in | ||
try await AnswerAPI.fetchListOfMine( | ||
threshold: threshold, | ||
pageSize: 30, | ||
server: server, | ||
accessToken: accessToken | ||
) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
기존 API 호출 방식이 모두 수정되었습니다.
- RepositoryService 싱글톤 객체 내의
request
함수를 사용해 설정된server
와accessToken
을 설정합니다. - 이로 인해 동일하게 작성되고 있던 KeychainService 코드가 삭제되었습니다.
/// AccessToken 및 Server 설정, 토큰 재발급 로직을 추가해 API를 호출합니다. | ||
func request<T: Decodable>( | ||
handler: @escaping (Server, AccessToken) async throws -> T | ||
) async throws -> T { | ||
let accessToken = try keychainService.fetchData(.accessToken) | ||
do { | ||
// 1. 네트워킹 성공 시, 기존 Token 값 사용 | ||
return try await handler(server, accessToken) | ||
} catch NetworkError.authenticationFailed { | ||
|
||
do { | ||
// 2-1. 네트워킹 실패(403 에러 발생)시, Token 재발급 | ||
let refresh = try await TokenAPI.refresh( | ||
server: server, | ||
accessToken: accessToken | ||
) | ||
|
||
// 2-2. Keychain 내 기존 Token값 업데이트 | ||
try keychainService.createData(.accessToken, refresh.accessToken) | ||
try keychainService.createData(.refreshToken, refresh.refreshToken) | ||
|
||
// 2-3. 재발급 받은 Token으로 API 재호출 | ||
return try await handler(server, refresh.accessToken) | ||
} catch { | ||
|
||
// 2-4. 만약 토큰 재발급에도 실패할 시, 로그인 화면으로 이동 | ||
await QappleApp.mainFlowStore.send(.refreshTokenFailed) | ||
throw error | ||
} | ||
} catch { | ||
// 3. 이외의 네트워킹 오류 발생시 그대로 던지기 | ||
throw error | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
새롭게 구현된 request(handler:)
함수입니다.
- RepositoryService 객체 내에
server
값이 있기 때문에 해당 값을 이용해 handler 클로저로 넘겨줍니다. - AccessToken 역시 keychainService를 이용해 handler 클로저로 넘겨줍니다.
- 기존 토큰을 사용해 API 호출 성공 시, 그대로 사용 / 실패 시, 토큰 재발급 / 토큰 재발급도 실패 시, 로그인 화면으로 이동
enum QappleAPI { | ||
|
||
typealias TotalCount = Int | ||
|
||
struct PaginationInfo: Equatable { | ||
var threshold: String | ||
var hasNext: Bool | ||
} | ||
|
||
struct Token { | ||
let accessToken: String | ||
let refreshToken: String | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
기존 Legacy코드를 삭제하면서 Entity로 분리해두었습니다.
Reduce { state, action in | ||
switch action { | ||
case .onAppear: | ||
guard state.isFirstLaunch else { return .none } | ||
return .run { send in | ||
do { | ||
try await appleLoginService.autoLogin() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
첫 번째 실행일 때만 자동로그인을 호출할 수 있도록 업데이트했습니다.
case .refreshTokenFailed: | ||
state.$isSignIn.withLock { $0 = false } | ||
state.path.removeAll() | ||
return .none |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
토큰 재발급에 실패할 시, @Shared
로 공유 받은 isSignIn
값을 업데이트하고, 모든 네비게이션 스택을 지워줍니다.
.onChange(of: store.questionTab) { _, _ in | ||
HapticService.impact(style: .light) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
탭바가 전환될 때 햅틱 이벤트를 추가했습니다. 22
do { | ||
let result = try await notificationRepository.fetchNotificationList(nil) | ||
await send(.fetchNotifications(result.0, result.1)) | ||
} catch { | ||
await send(.networkingFailed) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요기 에러 처리가 안되어서 TCA에서 보라색 에러를 내고 있길래 수정했습니다!
|
||
private init() {} | ||
enum HapticService { | ||
|
||
private static let notificationGenerator = UINotificationFeedbackGenerator() | ||
private static let impactGenerator = UIImpactFeedbackGenerator(style: .medium) | ||
|
||
static func notification(type: UINotificationFeedbackGenerator.FeedbackType) { | ||
notificationGenerator.notificationOccurred(type) | ||
} | ||
|
||
static func impact(style: UIImpactFeedbackGenerator.FeedbackStyle) { | ||
impactGenerator.impactOccurred() | ||
UIImpactFeedbackGenerator(style: style).impactOccurred() | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
바보 같은 실수를,, 하고 있었던 부분이라 공유드립니다.
매번 인스턴스를 재생성 하는게 이상해보여서 기존의 impactGenerator
타입 프로퍼티를 만들어 재사용하고 있었습니다. 문제는,,, 위의 notificationGenerator
는 notificationOccurred
함수를 실행할 때 style을 인자로 넘길 수 있는 것에 반해, impactGenerator
는 style을 생성할 때 말고는 넘겨줄 수가 없더라구요,,,?
결국 인스턴스를 재생성하더라도 기능을 구현하는 것이 우선이기 때문에 위와 같이 코드를 업데이트해주었습니다.(어쩐지 전에 모든 햅틱이 다 medium으로 오는 것 같은 기분이더라구요,,,,,🤣)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그 감각이 medium이었군요.. 확인했습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ㅋ̄̈ㅋ꙼̈ㅋ̆̎ㅋ̐̈ㅋ̊̈ 전 다르다고 느끼고 있었는데 제 손가락이 미친거였군요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
짜잘짜잘한거 많이 캐치하셨군요..!
코드가 많이 깔끔해진거 같아요! refresh 로직도 👏🏻👏🏿
LGTM!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
캐플이 정상화 되어가고 있군요! 고생하셨습니다!!!
Co-authored-by: 문인범 <[email protected]>
close #313
TO-DO
상세 설명
Server
및AccessToken
값을 단일 함수로 편하게 묶어 사용할 수 있도록 리팩토링했습니다.