From c4cd6183b6beea542eff428cc27d4a8e9ef8d1a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=80=ED=98=84?= <102128060+wlgusqkr@users.noreply.github.com> Date: Mon, 5 Jan 2026 00:07:36 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20fcm=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- today-s-sound/App/TodaySSoundApp.swift | 104 ++++++++++++++++-- .../Core/AppState/SessionStore.swift | 48 +++++++- .../Core/Network/Service/APIService.swift | 29 +++++ .../Core/Network/Targets/UserAPI.swift | 14 +++ today-s-sound/Data/Models/Subscription.swift | 2 + today-s-sound/Data/Models/User.swift | 6 + .../Features/Main/Home/HomeView.swift | 2 +- .../Component/SubscriptionCardView.swift | 7 +- .../SubscriptionListView.swift | 2 +- .../SubscriptionListViewModel.swift | 18 ++- 10 files changed, 211 insertions(+), 21 deletions(-) diff --git a/today-s-sound/App/TodaySSoundApp.swift b/today-s-sound/App/TodaySSoundApp.swift index 544de7d..36cd46c 100644 --- a/today-s-sound/App/TodaySSoundApp.swift +++ b/today-s-sound/App/TodaySSoundApp.swift @@ -5,12 +5,20 @@ // Created by 하승연 on 9/28/25. // +import Combine import FirebaseCore import FirebaseMessaging import SwiftUI import UserNotifications class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate, MessagingDelegate { // 3. Delegate 프로토콜 3개 추가 + + // APNs 등록 상태 추적 + private var hasRegisteredForRemoteNotifications = false + + // FCM 토큰 업데이트를 위한 API 서비스 + private let apiService = APIService() + private var cancellables = Set() func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool @@ -28,7 +36,23 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele ) // 6. APNs에 기기 등록 요청 - application.registerForRemoteNotifications() + // FCM 토큰이 있으면 = 이미 APNs 등록 완료 + FCM 토큰 생성 완료 + // 따라서 APNs를 다시 등록할 필요 없음 + let hasFCMToken = Keychain.getString(for: KeychainKey.fcmToken) != nil + + if hasFCMToken { + // FCM 토큰이 있으면 이미 APNs도 등록되어 있고 FCM 토큰도 생성되어 있음 + // APNs를 다시 등록할 필요 없음 + print("ℹ️ [APNs] FCM 토큰이 이미 있으므로 APNs 등록 생략 (이미 등록 완료)") + } else if !hasRegisteredForRemoteNotifications { + // FCM 토큰이 없고, 아직 등록 요청하지 않았으면 등록 요청 + // APNs 등록 → deviceToken → FCM 토큰 생성 순서로 진행됨 + print("📱 [APNs] 기기 등록 요청 (FCM 토큰이 없으므로 등록 필요)") + application.registerForRemoteNotifications() + } else { + // 이미 등록 요청했지만 아직 완료되지 않음 + print("ℹ️ [APNs] 이미 등록 요청했으므로 대기 중") + } // 7. FCM 메시징 대리자 설정 Messaging.messaging().delegate = self @@ -39,26 +63,88 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele // 8. FCM 토큰을 수신했을 때 호출되는 함수 (이 토큰을 Firebase 콘솔에 입력!) func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { print("====================================") - print("Firebase (FCM) 등록 토큰: \(fcmToken ?? "토큰 없음")") + print("🔔 [FCM] 토큰 수신 콜백 호출") print("====================================") - - // FCM 토큰을 키체인에 저장 - if let fcmToken { - Keychain.setString(fcmToken, for: KeychainKey.fcmToken) - print("✅ FCM 토큰을 키체인에 저장했습니다") + print("Firebase (FCM) 등록 토큰: \(fcmToken ?? "토큰 없음")") + + guard let fcmToken else { + print("⚠️ FCM 토큰이 nil이므로 저장하지 않음") + print("====================================\n") + return + } + + // 기존 토큰 확인 + let existingToken = Keychain.getString(for: KeychainKey.fcmToken) + + #if DEBUG + if let existingToken = existingToken { + print("📋 [FCM] 저장 전 기존 토큰: \(existingToken.prefix(50))...") + } else { + print("📋 [FCM] 저장 전 기존 토큰: (없음)") + } + #endif + + // 등록된 사용자인지 먼저 확인 + let userId = Keychain.getString(for: KeychainKey.userId) + let deviceSecret = Keychain.getString(for: KeychainKey.deviceSecret) + let isRegistered = userId != nil && deviceSecret != nil + + // 등록되지 않은 사용자면 FCM 토큰 저장하지 않음 (앱 초기화 후 상태) + guard isRegistered else { + print("ℹ️ [FCM] 등록되지 않은 사용자 - FCM 토큰 저장하지 않음 (앱 초기화 상태)") + print("====================================\n") + return + } + + // 토큰이 실제로 변경되었는지 확인 + let isTokenChanged = existingToken != fcmToken + + if isTokenChanged { + // 토큰이 변경되었을 때만 저장 + let saved = Keychain.setString(fcmToken, for: KeychainKey.fcmToken) + if saved { + print("✅ FCM 토큰이 변경되어 키체인에 저장했습니다") + + // 이미 등록된 사용자이므로 서버에 토큰 업데이트 + if let userId = userId, let deviceSecret = deviceSecret { + print("📤 [FCM] 서버에 FCM 토큰 업데이트 요청 (userId: \(userId))") + apiService.updateFCMToken(userId: userId, deviceSecret: deviceSecret, fcmToken: fcmToken) + .sink( + receiveCompletion: { completion in + switch completion { + case .finished: + print("✅ [FCM] 서버 토큰 업데이트 성공") + case let .failure(error): + print("❌ [FCM] 서버 토큰 업데이트 실패: \(error)") + } + }, + receiveValue: { _ in } + ) + .store(in: &cancellables) + } + } else { + print("❌ FCM 토큰 저장 실패!") + } + } else { + // 동일한 토큰이면 저장 생략 + print("ℹ️ [FCM] 동일한 토큰이므로 저장 생략") } + + print("====================================\n") } // 9. APNs 등록에 성공하여 deviceToken을 받았을 때 // (FCM이 APNs 토큰을 자동으로 FCM 토큰으로 매핑하므로 이 함수 자체는 필수) func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { - print("APNs device token: \(deviceToken)") + print("✅ [APNs] 기기 등록 성공") + hasRegisteredForRemoteNotifications = true Messaging.messaging().apnsToken = deviceToken } // 10. APNs 등록에 실패했을 때 func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { - print("APNs 등록 실패: \(error.localizedDescription)") + print("❌ [APNs] 기기 등록 실패: \(error.localizedDescription)") + // 실패해도 다음에 다시 시도할 수 있도록 플래그는 유지 } } diff --git a/today-s-sound/Core/AppState/SessionStore.swift b/today-s-sound/Core/AppState/SessionStore.swift index 1303355..b3b3d4c 100644 --- a/today-s-sound/Core/AppState/SessionStore.swift +++ b/today-s-sound/Core/AppState/SessionStore.swift @@ -147,9 +147,51 @@ final class SessionStore: ObservableObject { /// 로그아웃 (키체인 초기화) func logout() { - Keychain.delete(for: KeychainKey.userId) - Keychain.delete(for: KeychainKey.deviceSecret) - Keychain.delete(for: KeychainKey.fcmToken) + #if DEBUG + print("━━━━━━━━━━━━━━━━━━━━━━━━━━") + print("🚪 [logout] 키체인 삭제 시작") + print("━━━━━━━━━━━━━━━━━━━━━━━━━━") + #endif + + // 삭제 전 상태 확인 + #if DEBUG + let userIdBefore = Keychain.getString(for: KeychainKey.userId) + let deviceSecretBefore = Keychain.getString(for: KeychainKey.deviceSecret) + let fcmTokenBefore = Keychain.getString(for: KeychainKey.fcmToken) + + print("📋 [logout] 삭제 전 키체인 상태:") + print(" - userId: \(userIdBefore != nil ? "있음" : "(없음)")") + print(" - deviceSecret: \(deviceSecretBefore != nil ? "있음" : "(없음)")") + print(" - fcmToken: \(fcmTokenBefore != nil ? "있음" : "(없음)")") + + #endif + + // 삭제 실행 + let userIdDeleted = Keychain.delete(for: KeychainKey.userId) + let deviceSecretDeleted = Keychain.delete(for: KeychainKey.deviceSecret) + let fcmTokenDeleted = Keychain.delete(for: KeychainKey.fcmToken) + + #if DEBUG + print("📋 [logout] 삭제 결과:") + print(" - userId 삭제: \(userIdDeleted ? "✅ 성공" : "❌ 실패")") + print(" - deviceSecret 삭제: \(deviceSecretDeleted ? "✅ 성공" : "❌ 실패")") + print(" - fcmToken 삭제: \(fcmTokenDeleted)") + + // 삭제 후 상태 확인 + let userIdAfter = Keychain.getString(for: KeychainKey.userId) + let deviceSecretAfter = Keychain.getString(for: KeychainKey.deviceSecret) + let fcmTokenAfter = Keychain.getString(for: KeychainKey.fcmToken) + + print("📋 [logout] 삭제 후 키체인 상태:") + print(" - userId: \(userIdAfter != nil ? "⚠️ 여전히 존재!" : "✅ 삭제됨")") + print(" - deviceSecret: \(deviceSecretAfter != nil ? "⚠️ 여전히 존재!" : "✅ 삭제됨")") + print(" - fcmToken: \(fcmTokenAfter != nil ? "⚠️ 여전히 존재!" : "✅ 삭제됨")") + + if fcmTokenAfter != nil { + print(" ⚠️⚠️⚠️ fcmToken 삭제 실패! 값: \(fcmTokenAfter!.prefix(50))...") + } + print("━━━━━━━━━━━━━━━━━━━━━━━━━━\n") + #endif userId = nil isRegistered = false diff --git a/today-s-sound/Core/Network/Service/APIService.swift b/today-s-sound/Core/Network/Service/APIService.swift index 38ccac3..d3fdc4e 100644 --- a/today-s-sound/Core/Network/Service/APIService.swift +++ b/today-s-sound/Core/Network/Service/APIService.swift @@ -7,6 +7,7 @@ protocol APIServiceType { func request(_ target: some TargetType) -> AnyPublisher func registerAnonymous(request: RegisterAnonymousRequest) -> AnyPublisher func withdrawUser(userId: String, deviceSecret: String) -> AnyPublisher + func updateFCMToken(userId: String, deviceSecret: String, fcmToken: String) -> AnyPublisher func getSubscriptions( userId: String, deviceSecret: String, page: Int, size: Int ) -> AnyPublisher @@ -186,6 +187,34 @@ class APIService: APIServiceType { .eraseToAnyPublisher() } + func updateFCMToken(userId: String, deviceSecret: String, fcmToken: String) -> AnyPublisher { + let request = UpdateFCMTokenRequest(fcmToken: fcmToken) + return userProvider.requestPublisher(.updateFCMToken(userId: userId, deviceSecret: deviceSecret, request: request)) + .mapError { moyaError -> NetworkError in + .requestFailed(moyaError) + } + .tryMap { response in + // 상태 코드만 확인 (200-299면 성공) + guard (200 ... 299).contains(response.statusCode) else { + throw NetworkError.serverError(statusCode: response.statusCode) + } + + #if DEBUG + print("✅ FCM 토큰 업데이트 성공") + #endif + + return () + } + .mapError { error -> NetworkError in + if let networkError = error as? NetworkError { + return networkError + } else { + return .requestFailed(error) + } + } + .eraseToAnyPublisher() + } + // MARK: - Subscription API func getSubscriptions( diff --git a/today-s-sound/Core/Network/Targets/UserAPI.swift b/today-s-sound/Core/Network/Targets/UserAPI.swift index 5ae1872..fc31a56 100644 --- a/today-s-sound/Core/Network/Targets/UserAPI.swift +++ b/today-s-sound/Core/Network/Targets/UserAPI.swift @@ -11,6 +11,7 @@ import Moya enum UserAPI { case registerAnonymous(request: RegisterAnonymousRequest) case withdraw(userId: String, deviceSecret: String) + case updateFCMToken(userId: String, deviceSecret: String, request: UpdateFCMTokenRequest) } extension UserAPI: APITargetType { @@ -20,6 +21,8 @@ extension UserAPI: APITargetType { "/api/users/anonymous" case .withdraw: "/api/users/withdraw" + case .updateFCMToken: + "/api/fcm" } } @@ -29,6 +32,8 @@ extension UserAPI: APITargetType { .post case .withdraw: .delete + case .updateFCMToken: + .put } } @@ -38,6 +43,8 @@ extension UserAPI: APITargetType { .requestJSONEncodable(request) case .withdraw: .requestPlain + case let .updateFCMToken(_, _, request): + .requestJSONEncodable(request) } } @@ -55,6 +62,13 @@ extension UserAPI: APITargetType { "X-User-ID": userId, "X-Device-Secret": deviceSecret ] + case let .updateFCMToken(userId, deviceSecret, _): + [ + "Content-Type": "application/json", + "Accept": "application/json", + "X-User-ID": userId, + "X-Device-Secret": deviceSecret + ] } } } diff --git a/today-s-sound/Data/Models/Subscription.swift b/today-s-sound/Data/Models/Subscription.swift index b143620..7ff3d0e 100644 --- a/today-s-sound/Data/Models/Subscription.swift +++ b/today-s-sound/Data/Models/Subscription.swift @@ -53,6 +53,7 @@ struct SubscriptionItem: Codable, Identifiable { let url: String let alias: String let isUrgent: Bool + let isAlarmEnabled: Bool let keywords: [KeywordItem] enum CodingKeys: String, CodingKey { @@ -60,6 +61,7 @@ struct SubscriptionItem: Codable, Identifiable { case url case alias case isUrgent + case isAlarmEnabled case keywords } } diff --git a/today-s-sound/Data/Models/User.swift b/today-s-sound/Data/Models/User.swift index b452e6b..e02aa4e 100644 --- a/today-s-sound/Data/Models/User.swift +++ b/today-s-sound/Data/Models/User.swift @@ -22,6 +22,12 @@ struct AnonymousUserResult: Codable { /// 익명 사용자 등록 응답 typealias RegisterAnonymousResponse = APIResponse +// MARK: - FCM 토큰 업데이트 + +struct UpdateFCMTokenRequest: Codable { + let fcmToken: String +} + // MARK: - 에러 응답 struct APIErrorResponse: Codable, Error { diff --git a/today-s-sound/Presentation/Features/Main/Home/HomeView.swift b/today-s-sound/Presentation/Features/Main/Home/HomeView.swift index ac38051..30b3dc5 100644 --- a/today-s-sound/Presentation/Features/Main/Home/HomeView.swift +++ b/today-s-sound/Presentation/Features/Main/Home/HomeView.swift @@ -70,7 +70,7 @@ struct HomeView: View { .accessibilityElement() .accessibilityLabel("피드를 불러오는 중입니다") } else if viewModel.currentCategoryName.isEmpty { - Text("등록된 페이지 없음") + Text("새로운 글 없음") .font(.KoddiExtraBold32) .foregroundColor(.white) .padding(.horizontal, 32) diff --git a/today-s-sound/Presentation/Features/SubscriptionList/Component/SubscriptionCardView.swift b/today-s-sound/Presentation/Features/SubscriptionList/Component/SubscriptionCardView.swift index e5a9658..0bfca2f 100644 --- a/today-s-sound/Presentation/Features/SubscriptionList/Component/SubscriptionCardView.swift +++ b/today-s-sound/Presentation/Features/SubscriptionList/Component/SubscriptionCardView.swift @@ -61,13 +61,13 @@ struct SubscriptionCardView: View { Spacer() - // 긴급 알림 아이콘 + // 알림 설정 아이콘 Button(action: { onToggleAlarm?(subscription) }, label: { - Image(subscription.isUrgent ? "Bell" : "Bell off") + Image(subscription.isAlarmEnabled ? "Bell" : "Bell off") .frame(width: 40, height: 40) - .accessibilityLabel(subscription.isUrgent ? "페이지 알림 설정됨" : "페이지 알림 해제됨") + .accessibilityLabel(subscription.isAlarmEnabled ? "페이지 알림 설정됨" : "페이지 알림 해제됨") }) .accessibilityHint("탭하여 이 페이지의 구독 알림 설정을 변경합니다") } @@ -89,6 +89,7 @@ struct SubscriptionCardView_Previews: PreviewProvider { url: "https://newsroom.apple.com", alias: "애플 뉴스룸", isUrgent: false, + isAlarmEnabled: true, keywords: [ KeywordItem(id: 1, name: "아이폰"), KeywordItem(id: 2, name: "접근성"), diff --git a/today-s-sound/Presentation/Features/SubscriptionList/SubscriptionListView.swift b/today-s-sound/Presentation/Features/SubscriptionList/SubscriptionListView.swift index 2638295..ebc61a9 100644 --- a/today-s-sound/Presentation/Features/SubscriptionList/SubscriptionListView.swift +++ b/today-s-sound/Presentation/Features/SubscriptionList/SubscriptionListView.swift @@ -73,7 +73,7 @@ struct SubscriptionListView: View { subscription: subscription, theme: appTheme.theme, onToggleAlarm: { sub in - if sub.isUrgent { + if sub.isAlarmEnabled { viewModel.blockAlarm(sub) } else { viewModel.unblockAlarm(sub) diff --git a/today-s-sound/Presentation/Features/SubscriptionList/SubscriptionListViewModel.swift b/today-s-sound/Presentation/Features/SubscriptionList/SubscriptionListViewModel.swift index b9d98dc..3fb4bc9 100644 --- a/today-s-sound/Presentation/Features/SubscriptionList/SubscriptionListViewModel.swift +++ b/today-s-sound/Presentation/Features/SubscriptionList/SubscriptionListViewModel.swift @@ -97,6 +97,12 @@ class SubscriptionListViewModel: ObservableObject { let newItems = response.subscriptions + // 🔍 서버 응답 상세 로깅 + print("📥 서버 응답 상세:") + for item in newItems { + print(" - id: \(item.id), alias: \(item.alias), isUrgent: \(item.isUrgent), isAlarmEnabled: \(item.isAlarmEnabled)") + } + // 기존 목록에 추가 (서버에서 이미 정렬됨!) subscriptions.append(contentsOf: newItems) @@ -215,12 +221,13 @@ class SubscriptionListViewModel: ObservableObject { // 차단 성공 시 로컬 상태 업데이트 (종 모양 변경) if let index = subscriptions.firstIndex(where: { $0.id == subscription.id }) { let updated = subscriptions[index] - // isUrgent를 false로 변경 (구조체이므로 새로 생성) + // isAlarmEnabled를 false로 변경 (구조체이므로 새로 생성) subscriptions[index] = SubscriptionItem( id: updated.id, url: updated.url, alias: updated.alias, - isUrgent: false, + isUrgent: updated.isUrgent, + isAlarmEnabled: false, keywords: updated.keywords ) } @@ -280,12 +287,13 @@ class SubscriptionListViewModel: ObservableObject { // 차단 해제 성공 시 로컬 상태 업데이트 (종 모양 변경) if let index = subscriptions.firstIndex(where: { $0.id == subscription.id }) { let updated = subscriptions[index] - // isUrgent를 true로 변경 + // isAlarmEnabled를 true로 변경 subscriptions[index] = SubscriptionItem( id: updated.id, url: updated.url, alias: updated.alias, - isUrgent: true, + isUrgent: updated.isUrgent, + isAlarmEnabled: true, keywords: updated.keywords ) } @@ -329,6 +337,7 @@ class SubscriptionListViewModel: ObservableObject { url: "https://newsroom.apple.com", alias: "애플 뉴스룸", isUrgent: false, + isAlarmEnabled: true, keywords: [ KeywordItem(id: 1, name: "아이폰"), KeywordItem(id: 2, name: "애플워치") @@ -339,6 +348,7 @@ class SubscriptionListViewModel: ObservableObject { url: "https://blog.naver.com/accessibility", alias: "접근성 블로그", isUrgent: true, + isAlarmEnabled: false, keywords: [ KeywordItem(id: 3, name: "시각"), KeywordItem(id: 4, name: "보이스오버"), From b5fc1cff843c881d732044a955773cf1bcf9c613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=80=ED=98=84?= <102128060+wlgusqkr@users.noreply.github.com> Date: Mon, 5 Jan 2026 00:18:35 +0900 Subject: [PATCH 2/2] fix; --- today-s-sound/App/TodaySSoundApp.swift | 28 +++++++++---------- .../Core/AppState/SessionStore.swift | 16 +++++------ .../Features/Main/Home/HomeView.swift | 1 - 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/today-s-sound/App/TodaySSoundApp.swift b/today-s-sound/App/TodaySSoundApp.swift index 36cd46c..0138d04 100644 --- a/today-s-sound/App/TodaySSoundApp.swift +++ b/today-s-sound/App/TodaySSoundApp.swift @@ -12,10 +12,10 @@ import SwiftUI import UserNotifications class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate, MessagingDelegate { // 3. Delegate 프로토콜 3개 추가 - + // APNs 등록 상태 추적 private var hasRegisteredForRemoteNotifications = false - + // FCM 토큰 업데이트를 위한 API 서비스 private let apiService = APIService() private var cancellables = Set() @@ -39,7 +39,7 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele // FCM 토큰이 있으면 = 이미 APNs 등록 완료 + FCM 토큰 생성 완료 // 따라서 APNs를 다시 등록할 필요 없음 let hasFCMToken = Keychain.getString(for: KeychainKey.fcmToken) != nil - + if hasFCMToken { // FCM 토큰이 있으면 이미 APNs도 등록되어 있고 FCM 토큰도 생성되어 있음 // APNs를 다시 등록할 필요 없음 @@ -66,47 +66,47 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele print("🔔 [FCM] 토큰 수신 콜백 호출") print("====================================") print("Firebase (FCM) 등록 토큰: \(fcmToken ?? "토큰 없음")") - + guard let fcmToken else { print("⚠️ FCM 토큰이 nil이므로 저장하지 않음") print("====================================\n") return } - + // 기존 토큰 확인 let existingToken = Keychain.getString(for: KeychainKey.fcmToken) - + #if DEBUG - if let existingToken = existingToken { + if let existingToken { print("📋 [FCM] 저장 전 기존 토큰: \(existingToken.prefix(50))...") } else { print("📋 [FCM] 저장 전 기존 토큰: (없음)") } #endif - + // 등록된 사용자인지 먼저 확인 let userId = Keychain.getString(for: KeychainKey.userId) let deviceSecret = Keychain.getString(for: KeychainKey.deviceSecret) let isRegistered = userId != nil && deviceSecret != nil - + // 등록되지 않은 사용자면 FCM 토큰 저장하지 않음 (앱 초기화 후 상태) guard isRegistered else { print("ℹ️ [FCM] 등록되지 않은 사용자 - FCM 토큰 저장하지 않음 (앱 초기화 상태)") print("====================================\n") return } - + // 토큰이 실제로 변경되었는지 확인 let isTokenChanged = existingToken != fcmToken - + if isTokenChanged { // 토큰이 변경되었을 때만 저장 let saved = Keychain.setString(fcmToken, for: KeychainKey.fcmToken) if saved { print("✅ FCM 토큰이 변경되어 키체인에 저장했습니다") - + // 이미 등록된 사용자이므로 서버에 토큰 업데이트 - if let userId = userId, let deviceSecret = deviceSecret { + if let userId, let deviceSecret { print("📤 [FCM] 서버에 FCM 토큰 업데이트 요청 (userId: \(userId))") apiService.updateFCMToken(userId: userId, deviceSecret: deviceSecret, fcmToken: fcmToken) .sink( @@ -129,7 +129,7 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele // 동일한 토큰이면 저장 생략 print("ℹ️ [FCM] 동일한 토큰이므로 저장 생략") } - + print("====================================\n") } diff --git a/today-s-sound/Core/AppState/SessionStore.swift b/today-s-sound/Core/AppState/SessionStore.swift index b3b3d4c..bedfb24 100644 --- a/today-s-sound/Core/AppState/SessionStore.swift +++ b/today-s-sound/Core/AppState/SessionStore.swift @@ -152,41 +152,41 @@ final class SessionStore: ObservableObject { print("🚪 [logout] 키체인 삭제 시작") print("━━━━━━━━━━━━━━━━━━━━━━━━━━") #endif - + // 삭제 전 상태 확인 #if DEBUG let userIdBefore = Keychain.getString(for: KeychainKey.userId) let deviceSecretBefore = Keychain.getString(for: KeychainKey.deviceSecret) let fcmTokenBefore = Keychain.getString(for: KeychainKey.fcmToken) - + print("📋 [logout] 삭제 전 키체인 상태:") print(" - userId: \(userIdBefore != nil ? "있음" : "(없음)")") print(" - deviceSecret: \(deviceSecretBefore != nil ? "있음" : "(없음)")") print(" - fcmToken: \(fcmTokenBefore != nil ? "있음" : "(없음)")") - + #endif - + // 삭제 실행 let userIdDeleted = Keychain.delete(for: KeychainKey.userId) let deviceSecretDeleted = Keychain.delete(for: KeychainKey.deviceSecret) let fcmTokenDeleted = Keychain.delete(for: KeychainKey.fcmToken) - + #if DEBUG print("📋 [logout] 삭제 결과:") print(" - userId 삭제: \(userIdDeleted ? "✅ 성공" : "❌ 실패")") print(" - deviceSecret 삭제: \(deviceSecretDeleted ? "✅ 성공" : "❌ 실패")") print(" - fcmToken 삭제: \(fcmTokenDeleted)") - + // 삭제 후 상태 확인 let userIdAfter = Keychain.getString(for: KeychainKey.userId) let deviceSecretAfter = Keychain.getString(for: KeychainKey.deviceSecret) let fcmTokenAfter = Keychain.getString(for: KeychainKey.fcmToken) - + print("📋 [logout] 삭제 후 키체인 상태:") print(" - userId: \(userIdAfter != nil ? "⚠️ 여전히 존재!" : "✅ 삭제됨")") print(" - deviceSecret: \(deviceSecretAfter != nil ? "⚠️ 여전히 존재!" : "✅ 삭제됨")") print(" - fcmToken: \(fcmTokenAfter != nil ? "⚠️ 여전히 존재!" : "✅ 삭제됨")") - + if fcmTokenAfter != nil { print(" ⚠️⚠️⚠️ fcmToken 삭제 실패! 값: \(fcmTokenAfter!.prefix(50))...") } diff --git a/today-s-sound/Presentation/Features/Main/Home/HomeView.swift b/today-s-sound/Presentation/Features/Main/Home/HomeView.swift index eb2e6f1..5576577 100644 --- a/today-s-sound/Presentation/Features/Main/Home/HomeView.swift +++ b/today-s-sound/Presentation/Features/Main/Home/HomeView.swift @@ -61,7 +61,6 @@ struct HomeView: View { .foregroundColor(Color.text(appTheme.theme)) .accessibilityHidden(true) - Group { if viewModel.isLoading { Text("불러오는 중...")