Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 11 additions & 14 deletions WhereAreYou/WhereAreYou/Core/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ import AppIntents
@main
class AppDelegate: UIResponder, UIApplicationDelegate {

private var latestFCMToken: String?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let clientId = Bundle.main.object(forInfoDictionaryKey: "NMFClientId") as? String {
NMFAuthManager.shared().ncpKeyId = clientId
Expand Down Expand Up @@ -82,17 +80,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
/// APNs 서버에 디바이스 등록이 성공했을 때 시스템이 호출
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
if let token = latestFCMToken {
saveFCMToken(token)
}
// 이미 등록된 상태에서 불러도 대리자 didReceiveRegistration이 기존 설치 ID로 다시 호출된다.
// APNs 등록은 앱 실행·로그인 때마다 일어나므로, 여기서 등록을 요청해 그때마다 설치 ID를 저장한다
Messaging.messaging().register { _ in }
}

// MARK: - FCM 토큰 저장
// MARK: - FCM 설치 ID 저장

private func saveFCMToken(_ token: String) {
let fcmTokenService: FCMTokenService = DIContainer.shared.resolve()
private func saveFCMInstallationID(_ installationID: String) {
let fcmInstallationIDService: FCMInstallationIDService = DIContainer.shared.resolve()
Task {
try? await fcmTokenService.save(token: token)
try? await fcmInstallationIDService.save(installationID: installationID)
}
}

Expand All @@ -117,11 +115,10 @@ extension AppDelegate: UNUserNotificationCenterDelegate {

extension AppDelegate: MessagingDelegate {

/// FCM 토큰이 새로 발급되거나 갱신될 때 호출
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
guard let token = fcmToken else { return }
latestFCMToken = token
saveFCMToken(token)
/// FCM 등록이 생성·갱신되거나 register(completion:)을 직접 호출했을 때 호출 — 설치 ID를 서버 발송 대상으로 저장
func messaging(_ messaging: Messaging, didReceiveRegistration installationId: String?) {
guard let installationID = installationId else { return }
saveFCMInstallationID(installationID)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ final class AppCoordinator: Coordinator {
private let window: UIWindow
private let screenFactory: ScreenFactory
private let sessionValidationService: SessionValidationService
private let fcmTokenService: FCMTokenService
private let fcmInstallationIDService: FCMInstallationIDService
private var tabBarCoordinator: TabBarCoordinator?

init(window: UIWindow, screenFactory: ScreenFactory = .shared) {
self.window = window
self.screenFactory = screenFactory
self.sessionValidationService = screenFactory.container.resolve()
self.fcmTokenService = screenFactory.container.resolve()
self.fcmInstallationIDService = screenFactory.container.resolve()
}

func start() {
Expand Down Expand Up @@ -124,7 +124,7 @@ final class AppCoordinator: Coordinator {
UIApplication.shared.registerForRemoteNotifications()
}
case .denied:
self?.deleteFCMToken()
self?.deleteFCMInstallationID()
default:
self?.requestNotificationPermission()
}
Expand All @@ -140,9 +140,9 @@ final class AppCoordinator: Coordinator {
}
}

private func deleteFCMToken() {
private func deleteFCMInstallationID() {
Task {
try? await fcmTokenService.deleteForCurrentUser()
try? await fcmInstallationIDService.deleteForCurrentUser()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ extension DIContainer {
))

// 푸시 알림
register(FCMTokenRepository.self, instance: FirestoreFCMTokenRepository())
register(FCMTokenService.self, instance: DefaultFCMTokenService(
register(FCMInstallationIDRepository.self, instance: FirestoreFCMInstallationIDRepository())
register(FCMInstallationIDService.self, instance: DefaultFCMInstallationIDService(
authRepository: resolve(AuthRepository.self),
fcmTokenRepository: resolve(FCMTokenRepository.self)
fcmInstallationIDRepository: resolve(FCMInstallationIDRepository.self)
))

// 세션 검증
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ extension ScreenFactory {
),
signOutUseCase: SignOutUseCase(
authRepository: container.resolve(AuthRepository.self),
fcmTokenRepository: container.resolve(FCMTokenRepository.self)
fcmInstallationIDRepository: container.resolve(FCMInstallationIDRepository.self)
)
)
return MyPageViewController(viewModel: viewModel)
Expand Down
2 changes: 2 additions & 0 deletions WhereAreYou/WhereAreYou/Core/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
</array>
</dict>
</array>
<key>FirebaseMessagingInstallationIdEnabled</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// FirestoreFCMTokenRepository.swift
// FirestoreFCMInstallationIDRepository.swift
// WhereAreYou
//
// Created by 이상유 on 2026-09-19.
Expand All @@ -8,15 +8,15 @@
import Foundation
import FirebaseFirestore

/// FCMTokenRepository의 Firestore 구현체
final class FirestoreFCMTokenRepository: FCMTokenRepository {
/// FCMInstallationIDRepository의 Firestore 구현체
final class FirestoreFCMInstallationIDRepository: FCMInstallationIDRepository {

private let db = Firestore.firestore()

func save(token: String, forUserID userID: String) async throws {
func save(installationID: String, forUserID userID: String) async throws {
do {
try await db.collection("users").document(userID)
.updateData(["fcmToken": token])
.updateData(["fcmInstallationID": installationID])
} catch {
throw FirebaseErrorMapper.map(error)
}
Expand All @@ -25,7 +25,7 @@ final class FirestoreFCMTokenRepository: FCMTokenRepository {
func delete(forUserID userID: String) async throws {
do {
try await db.collection("users").document(userID)
.updateData(["fcmToken": FieldValue.delete()])
.updateData(["fcmInstallationID": FieldValue.delete()])
} catch {
throw FirebaseErrorMapper.map(error)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// FCMInstallationIDRepository.swift
// WhereAreYou
//
// Created by 이상유 on 2026-09-19.
//

import Foundation

/// FCM 설치 ID 저장소 — Firestore 유저 문서의 fcmInstallationID 필드 관리.
/// 서버는 이 값을 FCM 발송 대상(fid)으로 쓴다
protocol FCMInstallationIDRepository {

func save(installationID: String, forUserID: String) async throws

func delete(forUserID: String) async throws

}
17 changes: 0 additions & 17 deletions WhereAreYou/WhereAreYou/Domain/Repository/FCMTokenRepository.swift

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// DefaultFCMInstallationIDService.swift
// WhereAreYou
//
// Created by 이상유 on 2026-09-28.
//

import Foundation

/// FCMInstallationIDService 구현 — 인증 저장소에서 현재 유저 ID를 얻어 FCM 설치 ID 저장소에 저장·삭제한다
final class DefaultFCMInstallationIDService: FCMInstallationIDService {

private let authRepository: AuthRepository
private let fcmInstallationIDRepository: FCMInstallationIDRepository

init(authRepository: AuthRepository, fcmInstallationIDRepository: FCMInstallationIDRepository) {
self.authRepository = authRepository
self.fcmInstallationIDRepository = fcmInstallationIDRepository
}

func save(installationID: String) async throws {
guard let userID = authRepository.currentUserID else { return }
try await fcmInstallationIDRepository.save(installationID: installationID, forUserID: userID)
}

func deleteForCurrentUser() async throws {
guard let userID = authRepository.currentUserID else { return }
try await fcmInstallationIDRepository.delete(forUserID: userID)
}

}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// FCMInstallationIDService.swift
// WhereAreYou
//
// Created by 이상유 on 2026-09-20.
//

import Foundation

/// FCM 설치 ID 서비스 — 현재 로그인된 유저의 FCM 설치 ID(푸시 발송 대상) 저장·삭제
protocol FCMInstallationIDService {

func save(installationID: String) async throws

func deleteForCurrentUser() async throws

}
17 changes: 0 additions & 17 deletions WhereAreYou/WhereAreYou/Domain/Service/FCMTokenService.swift

This file was deleted.

10 changes: 5 additions & 5 deletions WhereAreYou/WhereAreYou/Domain/UseCase/SignOutUseCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@

import Foundation

/// 로그아웃 — FCM 토큰 삭제 후 Firebase Auth 세션 해제
/// 로그아웃 — FCM 설치 ID 삭제 후 Firebase Auth 세션 해제
final class SignOutUseCase {

private let authRepository: AuthRepository
private let fcmTokenRepository: FCMTokenRepository
private let fcmInstallationIDRepository: FCMInstallationIDRepository

init(authRepository: AuthRepository, fcmTokenRepository: FCMTokenRepository) {
init(authRepository: AuthRepository, fcmInstallationIDRepository: FCMInstallationIDRepository) {
self.authRepository = authRepository
self.fcmTokenRepository = fcmTokenRepository
self.fcmInstallationIDRepository = fcmInstallationIDRepository
}

func execute() async throws {
if let userID = authRepository.currentUserID {
try? await fcmTokenRepository.delete(forUserID: userID)
try? await fcmInstallationIDRepository.delete(forUserID: userID)
}
try authRepository.signOut()
}
Expand Down
Loading