-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGoogleAuthenticationService.swift
More file actions
148 lines (111 loc) · 4.78 KB
/
Copy pathGoogleAuthenticationService.swift
File metadata and controls
148 lines (111 loc) · 4.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
//
// GoogleAuthenticationService.swift
// DevLog
//
// Created by opfic on 6/4/25.
//
import FirebaseAuth
import FirebaseFirestore
import FirebaseFunctions
import FirebaseMessaging
import Foundation
import GoogleSignIn
final class GoogleAuthenticationService: AuthenticationService {
private let store = Firestore.firestore()
private let functions = Functions.functions(region: "asia-northeast3")
private let messaging = Messaging.messaging()
private var user: User? { Auth.auth().currentUser }
private let provider = TopViewControllerProvider()
@MainActor
func signIn() async throws -> AuthenticationDataResponse {
guard let topViewController = provider.topViewController() else {
throw UIError.notFoundTopViewController
}
let signIn = try await GIDSignIn.sharedInstance.signIn(withPresenting: topViewController)
guard let idToken = signIn.user.idToken?.tokenString else {
throw URLError(.badServerResponse)
}
let accessToken = signIn.user.accessToken.tokenString
let credential = GoogleAuthProvider.credential(withIDToken: idToken, accessToken: accessToken)
let result = try await Auth.auth().signIn(with: credential)
if let photoURL = signIn.user.profile?.imageURL(withDimension: 200) {
let changeRequest = result.user.createProfileChangeRequest()
changeRequest.photoURL = photoURL
changeRequest.displayName = signIn.user.profile?.name
try await changeRequest.commitChanges()
}
let fcmToken = try await messaging.token()
return result.user.toData(providerID: .google, fcmToken: fcmToken)
}
func signOut(_ uid: String) async throws {
let infoRef = store.document("users/\(uid)/userData/tokens")
let doc = try await infoRef.getDocument()
if doc.exists {
try await infoRef.updateData(["fcmToken": FieldValue.delete()])
}
GIDSignIn.sharedInstance.signOut()
try await GIDSignIn.sharedInstance.disconnect()
try await messaging.deleteToken()
try Auth.auth().signOut()
}
func deleteAuth(_ uid: String) async throws {
let deleteFunction = functions.httpsCallable("deleteUserFirestoreData")
_ = try await deleteFunction.call(["uid": uid])
try await signOut(uid)
try await Auth.auth().currentUser?.delete()
}
func link(uid: String, email: String) async throws {
let topViewController = await MainActor.run {
provider.topViewController()
}
guard let topViewController = topViewController else {
throw UIError.notFoundTopViewController
}
if GIDSignIn.sharedInstance.hasPreviousSignIn() {
GIDSignIn.sharedInstance.signOut()
}
let signIn = try await GIDSignIn.sharedInstance.signIn(withPresenting: topViewController)
guard let googleEmail = signIn.user.profile?.email else {
throw EmailFetchError.emailNotFound
}
if googleEmail != email {
throw EmailFetchError.emailMismatch
}
guard let idToken = signIn.user.idToken?.tokenString else {
throw URLError(.badServerResponse)
}
let accessToken = signIn.user.accessToken.tokenString
let credential = GoogleAuthProvider.credential(withIDToken: idToken, accessToken: accessToken)
try await user?.link(with: credential)
}
func unlink(_ uid: String) async throws {
GIDSignIn.sharedInstance.signOut()
try await GIDSignIn.sharedInstance.disconnect()
_ = try await user?.unlink(fromProvider: AuthProviderID.google.rawValue)
}
}
final class TopViewControllerProvider {
@MainActor
func topViewController() -> UIViewController? {
let keyWindow = UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
.first { $0.isKeyWindow }
let rootController = keyWindow?.rootViewController
return topViewController(controller: rootController)
}
@MainActor
private func topViewController(controller: UIViewController?) -> UIViewController? {
if let navigationController = controller as? UINavigationController {
return topViewController(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController,
let selectedController = tabController.selectedViewController {
return topViewController(controller: selectedController)
}
if let presentedController = controller?.presentedViewController {
return topViewController(controller: presentedController)
}
return controller
}
}