Skip to content

Commit 69dad06

Browse files
authored
Merge pull request #259 from YAPP-Github/BOOK-432-feature/#258
feat: FCM 관련 서버 스펙 수정사항 반영
2 parents 488bb42 + 46da8e9 commit 69dad06

11 files changed

Lines changed: 157 additions & 8 deletions

File tree

src/Projects/BKData/Sources/API/UserAPI.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import Foundation
55
enum UserAPI {
66
case me
77
case termsAgreement(termsAgreed: Bool)
8-
case upsertFCMToken(fcmToken: String)
8+
case upsertFCMToken(fcmToken: String, deviceId: String)
99
case upsertNotificationSettings(notificationEnabled: Bool)
1010
}
1111

@@ -51,8 +51,8 @@ extension UserAPI: RequestTarget {
5151
switch self {
5252
case .termsAgreement(let termsAgreed):
5353
return TermsAgreementRequestDTO(termsAgreed: termsAgreed)
54-
case .upsertFCMToken(let fcmToken):
55-
return UpsertFCMTokenRequestDTO(fcmToken: fcmToken)
54+
case .upsertFCMToken(let fcmToken, let deviceId):
55+
return UpsertFCMTokenRequestDTO(fcmToken: fcmToken, deviceId: deviceId)
5656
case .upsertNotificationSettings(let notificationEnabled):
5757
return NotificationStatusRequestDTO(notificationEnabled: notificationEnabled)
5858
case .me:

src/Projects/BKData/Sources/DTO/Request/UpsertFCMTokenRequestDTO.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ import Foundation
44

55
struct UpsertFCMTokenRequestDTO: Encodable {
66
let fcmToken: String
7+
let deviceId: String
78
}

src/Projects/BKData/Sources/DataAssembly.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,11 @@ public struct DataAssembly: Assembly {
134134
type: NotificationRepository.self
135135
) { _ in
136136
@Autowired(name: "OAuth") var networkProvider: NetworkProvider
137-
return DefaultNotificationRepository(networkProvider: networkProvider)
137+
@Autowired var deviceIDProvider: DeviceIDProvider
138+
return DefaultNotificationRepository(
139+
networkProvider: networkProvider,
140+
deviceIDProvider: deviceIDProvider
141+
)
138142
}
139143

140144
container.register(
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright © 2025 Booket. All rights reserved
2+
3+
public protocol DeviceIDProvider {
4+
var deviceID: String? { get }
5+
func clearCache()
6+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright © 2025 Booket. All rights reserved
2+
3+
import BKDomain
4+
import Combine
5+
6+
public protocol DeviceIDStore {
7+
func getOrCreate() -> AnyPublisher<String, TokenError>
8+
func clear() -> AnyPublisher<Void, TokenError>
9+
}

src/Projects/BKData/Sources/Repository/DefaultNotificationRepository.swift

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,27 @@ import Combine
66

77
public struct DefaultNotificationRepository: NotificationRepository {
88
private let networkProvider: NetworkProvider
9-
10-
public init(networkProvider: NetworkProvider) {
9+
private let deviceIDProvider: DeviceIDProvider
10+
11+
public init(
12+
networkProvider: NetworkProvider,
13+
deviceIDProvider: DeviceIDProvider
14+
) {
1115
self.networkProvider = networkProvider
16+
self.deviceIDProvider = deviceIDProvider
1217
}
1318

1419
public func upsertFCMToken(
1520
fcmToken: String
1621
) -> AnyPublisher<Void, DomainError> {
17-
networkProvider.request(
18-
target: UserAPI.upsertFCMToken(fcmToken: fcmToken),
22+
guard let deviceID = deviceIDProvider.deviceID else {
23+
Log.error("Device ID not available", logger: AppLogger.storage)
24+
return Fail(error: DomainError.unknown)
25+
.eraseToAnyPublisher()
26+
}
27+
28+
return networkProvider.request(
29+
target: UserAPI.upsertFCMToken(fcmToken: fcmToken, deviceId: deviceID),
1930
type: UserProfileResponseDTO.self
2031
)
2132
.debugError(logger: AppLogger.network)

src/Projects/BKStorage/Sources/Constant/StorageKeys.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ public enum StorageKeys {
55
public static let refreshTokenKey = "refreshToken"
66
public static let fcmTokenKey = "fcmToken"
77
public static let isSyncNeededKey = "isSyncNeeded"
8+
public static let deviceIDKey = "deviceID"
89
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright © 2025 Booket. All rights reserved
2+
3+
import BKCore
4+
import BKData
5+
import OSLog
6+
7+
public final class KeychainDeviceIDProvider: DeviceIDProvider {
8+
private let storage: KeyValueStorage
9+
private var cachedDeviceID: String?
10+
11+
public init(storage: KeyValueStorage) {
12+
self.storage = storage
13+
}
14+
15+
public var deviceID: String? {
16+
if let cachedDeviceID {
17+
return cachedDeviceID
18+
}
19+
do {
20+
let id: String = try storage.load(for: StorageKeys.deviceIDKey)
21+
self.cachedDeviceID = id
22+
return id
23+
} catch {
24+
Log.error("Failed to load deviceID: \(error)", logger: AppLogger.storage)
25+
return nil
26+
}
27+
}
28+
29+
public func clearCache() {
30+
cachedDeviceID = nil
31+
}
32+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright © 2025 Booket. All rights reserved
2+
3+
import BKData
4+
import BKDomain
5+
import Combine
6+
import Foundation
7+
8+
public struct KeychainDeviceIDStore: DeviceIDStore {
9+
private let storage: KeyValueStorage
10+
11+
/// AppDelegate나 다른 곳에서 DI 없이 사용할 수 있도록 shared instance 제공
12+
public static let shared = KeychainDeviceIDStore(storage: KeychainKeyValueStorage())
13+
14+
public init(storage: KeyValueStorage) {
15+
self.storage = storage
16+
}
17+
18+
/// 디바이스 ID를 가져오거나, 없으면 새로 생성하여 저장
19+
/// 키체인에 저장되므로 앱 삭제 후 재설치해도 유지됨
20+
public func getOrCreate() -> AnyPublisher<String, TokenError> {
21+
do {
22+
if let existingID: String = try? storage.load(for: StorageKeys.deviceIDKey) {
23+
return Just(existingID)
24+
.setFailureType(to: TokenError.self)
25+
.eraseToAnyPublisher()
26+
}
27+
28+
let newDeviceID = UUID().uuidString
29+
try storage.save(newDeviceID, for: StorageKeys.deviceIDKey)
30+
31+
return Just(newDeviceID)
32+
.setFailureType(to: TokenError.self)
33+
.eraseToAnyPublisher()
34+
} catch {
35+
return Fail(error: TokenError.saveFailed(underlying: error))
36+
.eraseToAnyPublisher()
37+
}
38+
}
39+
40+
public func clear() -> AnyPublisher<Void, TokenError> {
41+
do {
42+
try storage.delete(for: StorageKeys.deviceIDKey)
43+
return Just(())
44+
.setFailureType(to: TokenError.self)
45+
.eraseToAnyPublisher()
46+
} catch {
47+
return Fail(error: TokenError.clearFailed(underlying: error))
48+
.eraseToAnyPublisher()
49+
}
50+
}
51+
}

src/Projects/BKStorage/Sources/StorageAssembly.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,24 @@ public struct StorageAssembly: Assembly {
5959
storage: keyValueStorage
6060
)
6161
}
62+
63+
container.register(
64+
type: DeviceIDProvider.self,
65+
scope: .singleton
66+
) { _ in
67+
@Autowired(name: "Keychain") var keyValueStorage: KeyValueStorage
68+
return KeychainDeviceIDProvider(
69+
storage: keyValueStorage
70+
)
71+
}
72+
73+
container.register(
74+
type: DeviceIDStore.self
75+
) { _ in
76+
@Autowired(name: "Keychain") var keyValueStorage: KeyValueStorage
77+
return KeychainDeviceIDStore(
78+
storage: keyValueStorage
79+
)
80+
}
6281
}
6382
}

0 commit comments

Comments
 (0)