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

자동로그인 오류 수정 및 토큰 재발급 로직 구현 #314

Merged
merged 10 commits into from
Feb 23, 2025

Conversation

thinkySide
Copy link
Contributor

@thinkySide thinkySide commented Feb 21, 2025

close #313

TO-DO

  • 토큰 재발급 로직 추가(자동 로그인 오류 수정)
  • Legacy API 호출 코드 정리
  • AppDelegate 파일 정리
  • UIComponent 네이밍 컨벤션 통일
  • HapticService 객체 수정

상세 설명

  • 토큰 재발급 로직을 추가했습니다. 기존에는 어떤 에러가 발생해도 네트워크 오류 Alert를 띄워주고만 있었는데, 이제는 403에러 발생 시, 토큰 재발급 로직을 한번 더 호출한 후 API를 다시 호출할 수 있도록 업데이트했습니다.(실패할 경우는 로그인 화면으로 이동하게 구현했습니다!)
  • 또한 위 과정에서 ServerAccessToken값을 단일 함수로 편하게 묶어 사용할 수 있도록 리팩토링했습니다.
  • 그 외 짜잘한 코드 정리 및 수정 진행했습니다!
  • 코드가 삭제된 것이 더 많아서 File Filter로 지워진거 빼고 보시면 코드 리뷰하기 더 편하실 것 같습니닷,,!

@thinkySide thinkySide added Feat 기능 구현 PR-Reviewing 현재 리뷰 중인 PR labels Feb 21, 2025
@thinkySide thinkySide self-assigned this Feb 21, 2025
@thinkySide thinkySide linked an issue Feb 21, 2025 that may be closed by this pull request
Comment on lines +22 to 35
// 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
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppDelegate 파일을 정리했습니다. 같은 목적의 코드는 함수로 묶어 분리했습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UNUserNotificationCenterDelegate 부분은 따로 파일로 분리했습니다.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

appDelegate 부분은 간결해졌네요!!

Comment on lines -38 to +37
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
)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기존 API 호출 방식이 모두 수정되었습니다.

  • RepositoryService 싱글톤 객체 내의 request 함수를 사용해 설정된 serveraccessToken을 설정합니다.
  • 이로 인해 동일하게 작성되고 있던 KeychainService 코드가 삭제되었습니다.

Comment on lines +41 to +74
/// 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
}
}
Copy link
Contributor Author

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 호출 성공 시, 그대로 사용 / 실패 시, 토큰 재발급 / 토큰 재발급도 실패 시, 로그인 화면으로 이동

Comment on lines +10 to +23
enum QappleAPI {

typealias TotalCount = Int

struct PaginationInfo: Equatable {
var threshold: String
var hasNext: Bool
}

struct Token {
let accessToken: String
let refreshToken: String
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기존 Legacy코드를 삭제하면서 Entity로 분리해두었습니다.

Comment on lines 39 to 45
Reduce { state, action in
switch action {
case .onAppear:
guard state.isFirstLaunch else { return .none }
return .run { send in
do {
try await appleLoginService.autoLogin()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

첫 번째 실행일 때만 자동로그인을 호출할 수 있도록 업데이트했습니다.

Comment on lines +120 to +123
case .refreshTokenFailed:
state.$isSignIn.withLock { $0 = false }
state.path.removeAll()
return .none
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

토큰 재발급에 실패할 시, @Shared로 공유 받은 isSignIn값을 업데이트하고, 모든 네비게이션 스택을 지워줍니다.

Comment on lines +38 to +40
.onChange(of: store.questionTab) { _, _ in
HapticService.impact(style: .light)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

탭바가 전환될 때 햅틱 이벤트를 추가했습니다. 22

Comment on lines +61 to +66
do {
let result = try await notificationRepository.fetchNotificationList(nil)
await send(.fetchNotifications(result.0, result.1))
} catch {
await send(.networkingFailed)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요기 에러 처리가 안되어서 TCA에서 보라색 에러를 내고 있길래 수정했습니다!

Comment on lines -11 to 21

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()
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

바보 같은 실수를,, 하고 있었던 부분이라 공유드립니다.

매번 인스턴스를 재생성 하는게 이상해보여서 기존의 impactGenerator 타입 프로퍼티를 만들어 재사용하고 있었습니다. 문제는,,, 위의 notificationGeneratornotificationOccurred함수를 실행할 때 style을 인자로 넘길 수 있는 것에 반해, impactGenerator는 style을 생성할 때 말고는 넘겨줄 수가 없더라구요,,,?

결국 인스턴스를 재생성하더라도 기능을 구현하는 것이 우선이기 때문에 위와 같이 코드를 업데이트해주었습니다.(어쩐지 전에 모든 햅틱이 다 medium으로 오는 것 같은 기분이더라구요,,,,,🤣)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그 감각이 medium이었군요.. 확인했습니다!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ㅋ̄̈ㅋ꙼̈ㅋ̆̎ㅋ̐̈ㅋ̊̈ 전 다르다고 느끼고 있었는데 제 손가락이 미친거였군요

@thinkySide thinkySide requested review from mooninbeom and OhMyungJin and removed request for mooninbeom February 21, 2025 16:18
Copy link
Contributor

@OhMyungJin OhMyungJin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

짜잘짜잘한거 많이 캐치하셨군요..!
코드가 많이 깔끔해진거 같아요! refresh 로직도 👏🏻👏🏿
LGTM!!

Copy link
Contributor

@mooninbeom mooninbeom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM
캐플이 정상화 되어가고 있군요! 고생하셨습니다!!!

@thinkySide thinkySide merged commit 7b09e53 into develop Feb 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feat 기능 구현 PR-Reviewing 현재 리뷰 중인 PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[FIX] 코드 정리 및 자동로그인 오류 수정
3 participants