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
146 changes: 139 additions & 7 deletions SniffMeet/SniffMeet.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

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

25 changes: 19 additions & 6 deletions SniffMeet/SniffMeet/Resource/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,30 @@
<string>&apos;SniffMEET&apos;은 Nearby Interaction과 ARKit을 위해 카메라 접근이 필요합니다.</string>
<key>NSNearbyInteractionUsageDescription</key>
<string>&apos;SniffMEET&apos;은 연결된 기기와의 거리와 방향 측정을 위해 Nearby Interaction 접근이 필요합니다.</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>$(GOOGLE_REVERSED_CLIENT_ID)</string>
</array>
</dict>
</array>
<key>GOOGLE_CLIENT_ID</key>
<string>$(GOOGLE_CLIENT_ID)</string>
<key>NOTIFICATION_SERVER</key>
<string>$(NOTIFICATION_SERVER)</string>
<key>NSBonjourServices</key>
<array>
<string>_SniffMeet._tcp</string>
<string>_SniffMeet._udp</string>
</array>
<key>PUBLIC_KEY</key>
<string>$(PUBLIC_KEY)</string>
<key>SERVER_URL</key>
<string>$(SERVER_URL)</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
Expand All @@ -38,15 +57,9 @@
</array>
</dict>
</dict>
<key>SERVER_URL</key>
<string>$(SERVER_URL)</string>
<key>PUBLIC_KEY</key>
<string>$(PUBLIC_KEY)</string>
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
<key>NOTIFICATION_SERVER</key>
<string>$(NOTIFICATION_SERVER)</string>
</dict>
</plist>
4 changes: 4 additions & 0 deletions SniffMeet/SniffMeet/SniffMeet.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
</dict>
</plist>
12 changes: 12 additions & 0 deletions SniffMeet/SniffMeet/SniffMeetDebug.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
</dict>
</plist>
4 changes: 4 additions & 0 deletions SniffMeet/SniffMeet/Source/Common/Constant/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,8 @@ enum Environment {
static let sessionExpired = Notification.Name("sessionExpired")
static let profileDropFailed = Notification.Name("profileDropFailed")
}

enum GoogleSignIn {
static var clientID: String = Bundle.main.infoDictionary?["GOOGLE_CLIENT_ID"] as? String ?? ""
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ struct SupabaseSessionErrorHandler: ErrorHandler {
name: Environment.NotificationCenterName.sessionExpired,
object: self
)
case .loadSessionFailed, .refreshSessionFailed, .saveSessionFailed:
case .loadSessionFailed, .refreshSessionFailed, .saveSessionFailed, .deleteSessionFailed:
break
}
}
Expand Down
111 changes: 111 additions & 0 deletions SniffMeet/SniffMeet/Source/Core/OAuth/AppleAuthManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//
// AppleAuthManager.swift
// SniffMeet
//
// Created by sole on 5/2/25.
//

import AuthenticationServices

protocol AppleAuthManageable: AnyObject {
func signIn() async throws -> String
}

final class AppleAuthManager: NSObject, AppleAuthManageable {
private let provider: ASAuthorizationAppleIDProvider = ASAuthorizationAppleIDProvider()
private var authorizationController: ASAuthorizationController?
#if DEBUG
private var continuation: CheckedContinuation<String, Error>?
#else
private var continuation: UnsafeContinuation<String, Error>?
#endif

private override init() {
super.init()
}

func signIn() async throws -> String {
// 중복 로그인 요청 방지 및 continuation misuse/crash 방지
guard continuation == nil else { throw AppleAuthError.signInFailed }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P4.
아래에 signInAlreadyInProgress도 정의되어 있어서 signInFailed로 하신 이유가 궁금합니다!

let request = provider.createRequest()
authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController?.delegate = self
authorizationController?.presentationContextProvider = self
#if DEBUG
let idToken = try await withCheckedThrowingContinuation { [weak self] continuation in
self?.continuation = continuation
self?.authorizationController?.performRequests()
}
#else
let idToken = try await withUnsafeThrowingContinuation { [weak self] continuation in
self?.continuation = continuation
self?.authorizationController?.performRequests()
}
#endif
return idToken
}

private func resumeOnce(with result: Result<String, Error>) {
guard let continuation else { return }
self.continuation = nil
switch result {
case .success(let idToken):
continuation.resume(returning: idToken)
case .failure(let error):
continuation.resume(throwing: error)
}
}
Comment on lines +48 to +57
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍

}

// MARK: - AppleAuthManager+

extension AppleAuthManager {
static let shared: AppleAuthManager = AppleAuthManager()
}

extension AppleAuthManager: ASAuthorizationControllerPresentationContextProviding {
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
if let windowScene = UIApplication.shared.keyWindow?.windowScene {
ASPresentationAnchor(windowScene: windowScene)
} else {
ASPresentationAnchor()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 정말 개인적으로 한 고민인데요.
사실상 else 분기가 실행될 일이 없다고 생각하지만, 만에 하나 이런 경우엔 UI 리턴 vs. 크래시 어떤게 좋을까요? (크래시 정보 수집 vs. 크래시 안정성 유지? 인거 같아요)
windowScenenil이라는 건 둘 다 앱 정상 플로우는 기대하지 못하는 지점인데 뭐가 더 좋을지 잠깐 고민해봤어요. 🤔

}
}
}

extension AppleAuthManager: ASAuthorizationControllerDelegate {
func authorizationController(
controller: ASAuthorizationController,
didCompleteWithAuthorization authorization: ASAuthorization
) {
guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential,
let idTokenData = credential.identityToken,
let idToken = String(data: idTokenData, encoding: .utf8) else {
resumeOnce(with: .failure(AppleAuthError.idTokenNotFound))
return
}
resumeOnce(with: .success(idToken))
}
func authorizationController(
controller: ASAuthorizationController,
didCompleteWithError error: any Error
) {
resumeOnce(with: .failure(AppleAuthError.signInFailed))
}
}

// MARK: - AppleAuthError

enum AppleAuthError: LocalizedError {
case idTokenNotFound
case signInFailed
case signInAlreadyInProgress

var localizedDescription: String {
switch self {
case .idTokenNotFound: "idToken을 찾을 수 없습니다."
case .signInFailed: "애플 로그인에 실패했습니다."
case .signInAlreadyInProgress: "애플 로그인 진행 중입니다."
}
}
}
59 changes: 59 additions & 0 deletions SniffMeet/SniffMeet/Source/Core/OAuth/GoogleAuthManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// GoogleAuthManager.swift
// SniffMeet
//
// Created by sole on 5/2/25.
//

import GoogleSignIn

protocol GoogleAuthManageable {
@MainActor func signIn() async throws -> String
func signOut()
}

final class GoogleAuthManager: GoogleAuthManageable {
private init() {}

@MainActor
func signIn() async throws -> String {
guard let rootViewController = UIApplication.shared.keyWindow?.rootViewController else {
throw GoogleAuthError.viewHierarchyNotFound
}
do {
let result = try await GIDSignIn.sharedInstance.signIn(
withPresenting: rootViewController
)
guard let idToken = result.user.idToken?.tokenString else {
throw GoogleAuthError.idTokenNotFound
}
return idToken
} catch let error as GoogleAuthError {
throw error
} catch {
throw GoogleAuthError.signInFailed
}
}

func signOut() {
GIDSignIn.sharedInstance.signOut()
}
}

extension GoogleAuthManager {
static let shared: GoogleAuthManageable = GoogleAuthManager()
}

enum GoogleAuthError: Error {
case viewHierarchyNotFound
case idTokenNotFound
case signInFailed

var localizedDescription: String {
switch self {
case .viewHierarchyNotFound: "로그인 창을 띄울 뷰 계층을 찾을 수 없습니다."
case .idTokenNotFound: "idToken을 찾을 수 없습니다."
case .signInFailed: "구글 로그인에 실패했습니다."
}
}
}
Loading