diff --git a/WhereAreYou/WhereAreYou.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/WhereAreYou/WhereAreYou.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 8b0a48f..65e9db0 100644
--- a/WhereAreYou/WhereAreYou.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/WhereAreYou/WhereAreYou.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -1,5 +1,5 @@
{
- "originHash" : "fe26f57aef57400a7e879fac321c410367e7b6e5a32d724723e25088c1ff4ee2",
+ "originHash" : "c1c6e189988c67057039e01b7485b3d037d9e66a68a7715356b0558a08a8feae",
"pins" : [
{
"identity" : "abseil-cpp-binary",
@@ -15,8 +15,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/app-check.git",
"state" : {
- "revision" : "3e33dd27dd4c69bd81c7c81fe61d8ccf58846902",
- "version" : "11.3.1"
+ "revision" : "97f7d74dd0e8f3d0fe5fc75cc8957aa73bdc948a",
+ "version" : "11.3.2"
}
},
{
@@ -24,8 +24,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/firebase-ios-sdk",
"state" : {
- "revision" : "346daa9f46316aa372b35b317e18224acc2e9063",
- "version" : "12.18.0"
+ "revision" : "8c29ca981990a32e626c8617544fa65d22b1834f",
+ "version" : "12.19.2"
}
},
{
@@ -33,8 +33,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/googleads/google-ads-on-device-conversion-ios-sdk",
"state" : {
- "revision" : "dc39082d8881109d35b94b1c122164c0e8d08a55",
- "version" : "3.6.1"
+ "revision" : "0208e2681ec81f8a9c81084696f60fcce26cb7ee",
+ "version" : "3.7.0"
}
},
{
@@ -42,8 +42,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleAppMeasurement.git",
"state" : {
- "revision" : "f04760d460296cc0fa430935a7be212e5bd67fc5",
- "version" : "12.18.0"
+ "revision" : "b8455dd2ff55cc269fda7a4f9eac6c1e3588c53f",
+ "version" : "12.19.2"
}
},
{
@@ -60,8 +60,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleUtilities.git",
"state" : {
- "revision" : "9f183ae842be978784f2963a343682e0c46d8fb3",
- "version" : "8.1.2"
+ "revision" : "92c8f6dc3ac375d6febdfcb3db68bc3d10633db3",
+ "version" : "8.1.3"
}
},
{
@@ -132,8 +132,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/navermaps/SPM-NMapsMap",
"state" : {
- "revision" : "14e1cfa1e270458fc58b0cd8827c87b3cee905ab",
- "version" : "3.23.3"
+ "revision" : "ffee72c8d3ddac7995671d36702069b8a68c485f",
+ "version" : "3.24.0"
}
}
],
diff --git a/WhereAreYou/WhereAreYou/Core/AppDelegate.swift b/WhereAreYou/WhereAreYou/Core/AppDelegate.swift
index 4687732..d69433c 100644
--- a/WhereAreYou/WhereAreYou/Core/AppDelegate.swift
+++ b/WhereAreYou/WhereAreYou/Core/AppDelegate.swift
@@ -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
@@ -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)
}
}
@@ -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)
}
}
diff --git a/WhereAreYou/WhereAreYou/Core/Coordinator/Implementation/AppCoordinator.swift b/WhereAreYou/WhereAreYou/Core/Coordinator/Implementation/AppCoordinator.swift
index 8b8bd83..aa4e3f4 100644
--- a/WhereAreYou/WhereAreYou/Core/Coordinator/Implementation/AppCoordinator.swift
+++ b/WhereAreYou/WhereAreYou/Core/Coordinator/Implementation/AppCoordinator.swift
@@ -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() {
@@ -124,7 +124,7 @@ final class AppCoordinator: Coordinator {
UIApplication.shared.registerForRemoteNotifications()
}
case .denied:
- self?.deleteFCMToken()
+ self?.deleteFCMInstallationID()
default:
self?.requestNotificationPermission()
}
@@ -140,9 +140,9 @@ final class AppCoordinator: Coordinator {
}
}
- private func deleteFCMToken() {
+ private func deleteFCMInstallationID() {
Task {
- try? await fcmTokenService.deleteForCurrentUser()
+ try? await fcmInstallationIDService.deleteForCurrentUser()
}
}
diff --git a/WhereAreYou/WhereAreYou/Core/DI/Implementation/AppDIContainer+Register.swift b/WhereAreYou/WhereAreYou/Core/DI/Implementation/AppDIContainer+Register.swift
index 3fa81a1..8a65832 100644
--- a/WhereAreYou/WhereAreYou/Core/DI/Implementation/AppDIContainer+Register.swift
+++ b/WhereAreYou/WhereAreYou/Core/DI/Implementation/AppDIContainer+Register.swift
@@ -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)
))
// 세션 검증
diff --git a/WhereAreYou/WhereAreYou/Core/DI/Implementation/ScreenFactory+MyPage.swift b/WhereAreYou/WhereAreYou/Core/DI/Implementation/ScreenFactory+MyPage.swift
index 417cd64..ea76619 100644
--- a/WhereAreYou/WhereAreYou/Core/DI/Implementation/ScreenFactory+MyPage.swift
+++ b/WhereAreYou/WhereAreYou/Core/DI/Implementation/ScreenFactory+MyPage.swift
@@ -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)
diff --git a/WhereAreYou/WhereAreYou/Core/Info.plist b/WhereAreYou/WhereAreYou/Core/Info.plist
index 69df8bb..68dd34a 100644
--- a/WhereAreYou/WhereAreYou/Core/Info.plist
+++ b/WhereAreYou/WhereAreYou/Core/Info.plist
@@ -36,6 +36,8 @@
+ FirebaseMessagingInstallationIdEnabled
+
UIBackgroundModes
remote-notification
diff --git a/WhereAreYou/WhereAreYou/Data/FirestoreFCMTokenRepository.swift b/WhereAreYou/WhereAreYou/Data/FirestoreFCMInstallationIDRepository.swift
similarity index 57%
rename from WhereAreYou/WhereAreYou/Data/FirestoreFCMTokenRepository.swift
rename to WhereAreYou/WhereAreYou/Data/FirestoreFCMInstallationIDRepository.swift
index a6a8bfe..6f8afbd 100644
--- a/WhereAreYou/WhereAreYou/Data/FirestoreFCMTokenRepository.swift
+++ b/WhereAreYou/WhereAreYou/Data/FirestoreFCMInstallationIDRepository.swift
@@ -1,5 +1,5 @@
//
-// FirestoreFCMTokenRepository.swift
+// FirestoreFCMInstallationIDRepository.swift
// WhereAreYou
//
// Created by 이상유 on 2026-09-19.
@@ -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)
}
@@ -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)
}
diff --git a/WhereAreYou/WhereAreYou/Domain/Repository/FCMInstallationIDRepository.swift b/WhereAreYou/WhereAreYou/Domain/Repository/FCMInstallationIDRepository.swift
new file mode 100644
index 0000000..f52bc73
--- /dev/null
+++ b/WhereAreYou/WhereAreYou/Domain/Repository/FCMInstallationIDRepository.swift
@@ -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
+
+}
diff --git a/WhereAreYou/WhereAreYou/Domain/Repository/FCMTokenRepository.swift b/WhereAreYou/WhereAreYou/Domain/Repository/FCMTokenRepository.swift
deleted file mode 100644
index 6c8d104..0000000
--- a/WhereAreYou/WhereAreYou/Domain/Repository/FCMTokenRepository.swift
+++ /dev/null
@@ -1,17 +0,0 @@
-//
-// FCMTokenRepository.swift
-// WhereAreYou
-//
-// Created by 이상유 on 2026-09-19.
-//
-
-import Foundation
-
-/// FCM 디바이스 토큰 저장소 — Firestore 유저 문서의 fcmToken 필드 관리
-protocol FCMTokenRepository {
-
- func save(token: String, forUserID: String) async throws
-
- func delete(forUserID: String) async throws
-
-}
diff --git a/WhereAreYou/WhereAreYou/Domain/Service/DefaultFCMInstallationIDService.swift b/WhereAreYou/WhereAreYou/Domain/Service/DefaultFCMInstallationIDService.swift
new file mode 100644
index 0000000..5977fed
--- /dev/null
+++ b/WhereAreYou/WhereAreYou/Domain/Service/DefaultFCMInstallationIDService.swift
@@ -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)
+ }
+
+}
diff --git a/WhereAreYou/WhereAreYou/Domain/Service/DefaultFCMTokenService.swift b/WhereAreYou/WhereAreYou/Domain/Service/DefaultFCMTokenService.swift
deleted file mode 100644
index ead76b8..0000000
--- a/WhereAreYou/WhereAreYou/Domain/Service/DefaultFCMTokenService.swift
+++ /dev/null
@@ -1,31 +0,0 @@
-//
-// DefaultFCMTokenService.swift
-// WhereAreYou
-//
-// Created by 이상유 on 2026-09-28.
-//
-
-import Foundation
-
-/// FCMTokenService 구현 — 인증 저장소에서 현재 유저 ID를 얻어 FCM 토큰 저장소에 저장·삭제한다
-final class DefaultFCMTokenService: FCMTokenService {
-
- private let authRepository: AuthRepository
- private let fcmTokenRepository: FCMTokenRepository
-
- init(authRepository: AuthRepository, fcmTokenRepository: FCMTokenRepository) {
- self.authRepository = authRepository
- self.fcmTokenRepository = fcmTokenRepository
- }
-
- func save(token: String) async throws {
- guard let userID = authRepository.currentUserID else { return }
- try await fcmTokenRepository.save(token: token, forUserID: userID)
- }
-
- func deleteForCurrentUser() async throws {
- guard let userID = authRepository.currentUserID else { return }
- try await fcmTokenRepository.delete(forUserID: userID)
- }
-
-}
diff --git a/WhereAreYou/WhereAreYou/Domain/Service/FCMInstallationIDService.swift b/WhereAreYou/WhereAreYou/Domain/Service/FCMInstallationIDService.swift
new file mode 100644
index 0000000..a19ac1a
--- /dev/null
+++ b/WhereAreYou/WhereAreYou/Domain/Service/FCMInstallationIDService.swift
@@ -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
+
+}
diff --git a/WhereAreYou/WhereAreYou/Domain/Service/FCMTokenService.swift b/WhereAreYou/WhereAreYou/Domain/Service/FCMTokenService.swift
deleted file mode 100644
index d3fffb9..0000000
--- a/WhereAreYou/WhereAreYou/Domain/Service/FCMTokenService.swift
+++ /dev/null
@@ -1,17 +0,0 @@
-//
-// FCMTokenService.swift
-// WhereAreYou
-//
-// Created by 이상유 on 2026-09-20.
-//
-
-import Foundation
-
-/// FCM 토큰 서비스 — 현재 로그인된 유저의 FCM 디바이스 토큰 저장·삭제
-protocol FCMTokenService {
-
- func save(token: String) async throws
-
- func deleteForCurrentUser() async throws
-
-}
diff --git a/WhereAreYou/WhereAreYou/Domain/UseCase/SignOutUseCase.swift b/WhereAreYou/WhereAreYou/Domain/UseCase/SignOutUseCase.swift
index abe84c1..9dc223a 100644
--- a/WhereAreYou/WhereAreYou/Domain/UseCase/SignOutUseCase.swift
+++ b/WhereAreYou/WhereAreYou/Domain/UseCase/SignOutUseCase.swift
@@ -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()
}