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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ playground.xcworkspace
**/Package.resolved

*.xcconfig
Hambug/GoogleService-Info.plist
67 changes: 67 additions & 0 deletions 3rdParty/.swiftpm/xcode/xcshareddata/xcschemes/FCMService.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2620"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FCMService"
BuildableName = "FCMService"
BlueprintName = "FCMService"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FCMService"
BuildableName = "FCMService"
BlueprintName = "FCMService"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
67 changes: 67 additions & 0 deletions 3rdParty/.swiftpm/xcode/xcshareddata/xcschemes/KakaoLogin.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2620"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "KakaoLogin"
BuildableName = "KakaoLogin"
BlueprintName = "KakaoLogin"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "KakaoLogin"
BuildableName = "KakaoLogin"
BlueprintName = "KakaoLogin"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
20 changes: 20 additions & 0 deletions 3rdParty/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,19 @@ let package = Package(
name: "KakaoLogin",
targets: ["KakaoLogin"]
),
.library(
name: "FCMService",
targets: ["FCMService"]
),
],
dependencies: [
.package(url: "https://github.com/kakao/kakao-ios-sdk", branch: "master"),
.package(
url: "https://github.com/firebase/firebase-ios-sdk.git",
.upToNextMajor(from: "12.7.0")
),
.package(name: "Infrastructure", path: "../Infrastructure"),
.package(name: "Common", path: "../Common")
],
targets: [
.target(
Expand All @@ -24,6 +34,16 @@ let package = Package(
.product(name: "KakaoSDKUser", package: "kakao-ios-sdk"),
]
),
.target(
name: "FCMService",
dependencies: [
.product(name: "FirebaseAnalytics", package: "firebase-ios-sdk"),
.product(name: "FirebaseCore", package: "firebase-ios-sdk"),
.product(name: "FirebaseMessaging", package: "firebase-ios-sdk"),
.product(name: "NetworkInterface", package: "Infrastructure"),
.product(name: "Util", package: "Common")
]
)

]
)
32 changes: 32 additions & 0 deletions 3rdParty/Sources/FCMService/FCMEndpoint.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// FCMEndpoint.swift
// 3rdParty
//
// Created by 강동영 on 1/13/26.
//

import Foundation
import NetworkInterface

struct FCMEndpoint: Endpoint {
let baseURL: String = NetworkConfig.baseURL
let path: String = "/api/v1/fcm/tokens"

let method: NetworkInterface.HTTPMethod = .POST

var headers: [String : String] = [:]

let queryParameters: [String : Any] = [:]

let body: Data?

init(body: FCMRequest) {
let encoder = JSONEncoder()
self.body = try? encoder.encode(body)
}
}

struct FCMRequest: Sendable, Encodable {
let token: String
let platform: String = "ios"
}
96 changes: 96 additions & 0 deletions 3rdParty/Sources/FCMService/FCMManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//
// FCMManager.swift
// 3rdParty
//
// Created by 강동영 on 1/13/26.
//

import Combine
import Foundation

import NetworkInterface
import DataSources
import Util

import FirebaseCore
import FirebaseMessaging


public final class FCMManager: NSObject {
private let service: NetworkServiceInterface
private let storage: FCMTokenStorageable

public init(
service: NetworkServiceInterface,
storage: FCMTokenStorageable
) {
self.service = service
self.storage = storage
}

public func configure() {
FirebaseApp.configure()
Messaging.messaging().delegate = self
}

public func registAPNsToken(_ deviceToken: Data) {
let token = deviceToken.map { String(format: "%02.2hhx", $0)}.joined()
print("device Token: \(token)")
Messaging.messaging().apnsToken = deviceToken
}

public func sendPendingTokenToServer() async {
if let fcmToken = storage.load() {
await sendTokenToServer(fcmToken)
}
}

private func sendTokenToServer(_ fcmToken: String) async {
let endpoint = FCMEndpoint(body: FCMRequest(token: fcmToken))

do {
let isSuccess = try await service.request(
endpoint,
responseType: SuccessResponse<Bool>.self
).async()

print("FCM Token Send to Server Success: \(isSuccess)")
} catch {
print("FCM Token Send to Server Failed")
print("errorMessage: \(error.localizedDescription)")
}
}
}

extension FCMManager: MessagingDelegate {

// MARK: - 등록 토큰을 제공하는 메서드
// 호출 되는 시점: FCM SDK는 최초 앱 시작 시
//
// 토큰 업데이트 혹은 무효화될 때마다,
// 신규 또는 기존 토큰을 가져옴

// 등록 토큰 변경시점:
//
// 새 기기에서 앱 복원
// 사용자가 앱 제거/재설치
// 사용자가 앱 데이터 삭제
public func messaging(
_ messaging: Messaging,
didReceiveRegistrationToken fcmToken: String?
) {
guard let fcmToken = fcmToken else { return }
print("fcmToken: \(fcmToken)")

let storedToken = storage.load()
// 기존 토큰 확인 및 fcm 토큰 변경여부 확인
if let storedToken = storedToken, storedToken == fcmToken {
print("✅ FCM Token unchanged")
return
}

// 저장 된 토큰이 없거나, 기존 토큰과 다른 경우 저장만
// 로그인 정보가 없어서, 403떨어짐
try? storage.save(fcmToken)
}
}
38 changes: 38 additions & 0 deletions Common/Sources/DataSources/Keychain/FCMTokenStorage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// FCMTokenStorage.swift
// Common
//
// Created by 강동영 on 1/13/26.
//


public protocol FCMTokenStorageable {
func save(_ token: String) throws
func load() -> String?
func clear() throws
func exists() throws -> Bool
}

public final class FCMTokenStorage: KeychainTokenStorage, FCMTokenStorageable {
private let service: String

public override init(service: String = HambugKeychainKey.serviceID) {
self.service = service
}

public func save(_ token: String) throws {
try set(token, for: .fcmToken)
}

public func load() -> String? {
try? string(for: .fcmToken)
}

public func clear() throws {
try delete(.fcmToken)
}

public func exists() throws -> Bool {
try contains(.fcmToken)
}
}
26 changes: 26 additions & 0 deletions Common/Sources/DataSources/Keychain/HambugKeychainKey.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// HambugKeychainKey.swift
// Common
//
// Created by 강동영 on 1/13/26.
//

import Foundation

// MARK: - Supporting Types

public enum HambugKeychainKey {
public static let serviceID = Bundle.main.bundleIdentifier ?? "com.hambug"

case accessToken
case refreshToken
case fcmToken

var toString: String {
switch self {
case .accessToken: return "access_token"
case .refreshToken: return "refresh_token"
case .fcmToken: return "fcm_token"
}
}
}
Loading
Loading