-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix name not populating during sign-in with Apple (#59)
- Loading branch information
1 parent
c8a1f5b
commit 5b468a4
Showing
8 changed files
with
174 additions
and
92 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -154,33 +154,22 @@ final class AuthenticationManager: NSObject, AuthenticationManaging { | |
private func listenForAuth() { | ||
Auth.auth() | ||
.authStateDidChangePublisher() | ||
.sink(withUnretained: self) { strongSelf, firebaseUser in | ||
.sinkAsync { [weak self] firebaseUser in | ||
guard let firebaseUser = firebaseUser else { | ||
strongSelf.currentUser = nil | ||
strongSelf.userListener = nil | ||
self?.currentUser = nil | ||
self?.userListener = nil | ||
return | ||
} | ||
|
||
Task { | ||
try await self.updateFirestoreUserWithAuthUser(firebaseUser) | ||
let user = try await self.database.document("users/\(firebaseUser.uid)").getDocument().decoded(as: User.self) | ||
DispatchQueue.main.async { | ||
self.currentUser = user | ||
self._emailVerified.send(firebaseUser.isEmailVerified || firebaseUser.email == "[email protected]") | ||
} | ||
} | ||
try await self?.updateFirestoreUserWithAuthUser(firebaseUser) | ||
} | ||
.store(in: &cancellables) | ||
} | ||
|
||
private func sha256(_ input: String) -> String { | ||
let inputData = Data(input.utf8) | ||
let hashedData = SHA256.hash(data: inputData) | ||
let hashString = hashedData.compactMap { | ||
return String(format: "%02x", $0) | ||
}.joined() | ||
|
||
return hashString | ||
SHA256.hash(data: Data(input.utf8)) | ||
.compactMap { String(format: "%02x", $0) } | ||
.joined() | ||
} | ||
|
||
private func registerUserManager(with user: User) { | ||
|
@@ -211,53 +200,39 @@ final class AuthenticationManager: NSObject, AuthenticationManaging { | |
let user = User(id: firebaseUser.uid, email: email, name: name) | ||
try await database.document("users/\(user.id)").setDataEncodable(user) | ||
} | ||
|
||
let user = try await self.database.document("users/\(firebaseUser.uid)").getDocument().decoded(as: User.self) | ||
DispatchQueue.main.async { | ||
self.currentUser = user | ||
self._emailVerified.send(firebaseUser.isEmailVerified || firebaseUser.email == "[email protected]") | ||
} | ||
} | ||
} | ||
|
||
extension AuthenticationManager: ASAuthorizationControllerDelegate { | ||
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { | ||
guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential, | ||
let tokenData = appleIDCredential.identityToken, let identityToken = String(data: tokenData, encoding: .utf8) | ||
let tokenData = appleIDCredential.identityToken, | ||
let idToken = String(data: tokenData, encoding: .utf8) | ||
else { return } | ||
|
||
let appleIDName = [appleIDCredential.fullName?.givenName, appleIDCredential.fullName?.familyName] | ||
.compactMap { $0 } | ||
.joined(separator: " ") | ||
|
||
let credential = OAuthProvider.credential(withProviderID: "apple.com", idToken: identityToken, rawNonce: currentNonce) | ||
let credential = OAuthProvider.credential(withProviderID: "apple.com", idToken: idToken, rawNonce: currentNonce) | ||
|
||
// Sign in with Firebase. | ||
Auth.auth().signIn(with: credential) { [weak self] authResult, error in | ||
if let error { | ||
// Error. If error.code == .MissingOrInvalidNonce, make sure | ||
// you're sending the SHA256-hashed nonce as a hex string with | ||
// your request to Apple. | ||
print(error) | ||
return | ||
} | ||
|
||
let name = [appleIDCredential.fullName?.givenName, appleIDCredential.fullName?.familyName] | ||
.compactMap { $0 } | ||
.joined(separator: " ") | ||
|
||
let firebaseUser = Auth.auth().currentUser | ||
|
||
let displayName = name.isEmpty ? | ||
firebaseUser?.displayName ?? "" : | ||
name | ||
|
||
let changeRequest = firebaseUser?.createProfileChangeRequest() | ||
changeRequest?.displayName = displayName | ||
changeRequest?.commitChanges(completion: nil) | ||
|
||
guard let firebaseUser = firebaseUser, let email = appleIDCredential.email ?? firebaseUser.email else { return } | ||
let userJson = [ | ||
"id": firebaseUser.uid, | ||
"email": email, | ||
"name": displayName | ||
] | ||
self?.database.document("users/\(firebaseUser.uid)").updateData(userJson) { error in | ||
guard let nsError = error as NSError?, | ||
nsError.domain == "FIRFirestoreErrorDomain", | ||
nsError.code == 5 else { return } | ||
let user = User(id: firebaseUser.uid, email: email, name: displayName) | ||
try? self?.database.document("users/\(user.id)").setDataEncodable(user) | ||
guard let firebaseUser = authResult?.user else { return } | ||
let displayName = appleIDName.nilIfEmpty ?? firebaseUser.displayName ?? "" | ||
guard firebaseUser.displayName != displayName else { return } | ||
|
||
let changeRequest = firebaseUser.createProfileChangeRequest() | ||
changeRequest.displayName = displayName | ||
changeRequest.commitChanges { [weak self] error in | ||
Task { [weak self] in | ||
try await self?.updateFirestoreUserWithAuthUser(firebaseUser) | ||
} | ||
} | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
20 changes: 0 additions & 20 deletions
20
firebase/functions/src/Handlers/competitions/handleCompetitionRequest.ts
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import * as admin from "firebase-admin"; | ||
import * as functions from "firebase-functions"; | ||
import * as moment from "moment"; | ||
import { deleteAccount } from "./Handlers/account/deleteAccount"; | ||
import { deleteCompetition } from "./Handlers/competitions/deleteCompetition"; | ||
import { updateCompetitionStandings } from "./Handlers/competitions/updateCompetitionStandings"; | ||
import { deleteFriend } from "./Handlers/friends/deleteFriend"; | ||
import { FriendRequestAction, handleFriendRequest } from "./Handlers/friends/handleFriendRequest"; | ||
import { ActivitySummary } from "./Models/ActivitySummary"; | ||
import { Competition } from "./Models/Competition"; | ||
import { Standing } from "./Models/Standing"; | ||
import { User } from "./Models/User"; | ||
import * as notifications from "./notifications"; | ||
import { getFirestore } from "./Utilities/firstore"; | ||
|
||
admin.initializeApp(); | ||
const firestore = getFirestore(); | ||
|
||
// Account | ||
|
||
exports.deleteAccount = functions.https.onCall(async (_data, context) => { | ||
const userID = context.auth?.uid; | ||
if (userID == null) return; | ||
await deleteAccount(userID); | ||
}); | ||
|
||
// Competitions | ||
|
||
exports.deleteCompetition = functions.https.onCall(data => { | ||
const competitionID = data.competitionID; | ||
return deleteCompetition(competitionID); | ||
}); | ||
|
||
exports.updateCompetitionStandings = functions.https.onCall((_data, context) => { | ||
const userID = context.auth?.uid; | ||
if (userID == null) return Promise.resolve(); | ||
return updateCompetitionStandings(userID); | ||
}); | ||
|
||
// Friends | ||
|
||
exports.sendFriendRequest = functions.https.onCall((data, context) => { | ||
const requesterID = context.auth?.uid; | ||
const requesteeID = data.userID; | ||
if (requesterID == null) return Promise.resolve(); | ||
return handleFriendRequest(requesterID, requesteeID, FriendRequestAction.create); | ||
}); | ||
|
||
exports.respondToFriendRequest = functions.https.onCall((data, context) => { | ||
const requesterID = context.auth?.uid; | ||
const requesteeID = data.userID; | ||
const accept = data.accept; | ||
if (requesterID == null) return Promise.resolve(); | ||
return handleFriendRequest(requesterID, requesteeID, accept ? FriendRequestAction.accept : FriendRequestAction.decline); | ||
}); | ||
|
||
exports.deleteFriend = functions.https.onCall((data, context) => { | ||
const userID = context.auth?.uid; | ||
const friendID = data.userID; | ||
if (userID == null) return Promise.resolve(); | ||
return deleteFriend(userID, friendID); | ||
}); | ||
|
||
// Jobs | ||
|
||
exports.cleanStaleActivitySummaries = functions.pubsub.schedule("every day 02:00") | ||
.timeZone("America/Toronto") | ||
.onRun(async () => { | ||
const users = (await firestore.collection("users").get()).docs.map(doc => new User(doc)); | ||
const competitions = (await firestore.collection("competitions").get()).docs.map(doc => new Competition(doc)); | ||
const cleanUsers = users.map(async user => { | ||
const participatingCompetitions = competitions.filter(competition => competition.participants.includes(user.id)); | ||
const activitySummariesToDelete = (await firestore.collection(`users/${user.id}/activitySummaries`).get()) | ||
.docs | ||
.filter(doc => { | ||
const activitySummary = new ActivitySummary(doc); | ||
const matchingCompetition = participatingCompetitions.find(competition => activitySummary.isIncludedInCompetition(competition)); | ||
return matchingCompetition == null || matchingCompetition == undefined; | ||
}) | ||
.map(doc => firestore.doc(`users/${user.id}/activitySummaries/${doc.id}`).delete()); | ||
|
||
return Promise.all(activitySummariesToDelete); | ||
}); | ||
|
||
return Promise.all(cleanUsers); | ||
}); | ||
|
||
exports.sendCompetitionCompleteNotifications = functions.pubsub.schedule("every day 12:00") | ||
.timeZone("America/Toronto") | ||
.onRun(async () => { | ||
const competitionsRef = await firestore.collection("competitions").get(); | ||
const competitionPromises = competitionsRef.docs.map(async doc => { | ||
const competition = new Competition(doc); | ||
|
||
const competitionEnd = moment(competition.end); | ||
const yesterday = moment().utc().subtract(1, "day"); | ||
if (yesterday.dayOfYear() != competitionEnd.dayOfYear() || yesterday.year() != competitionEnd.year()) return; | ||
|
||
const standingsRef = await firestore.collection(`competitions/${competition.id}/standings`).get(); | ||
const standings = standingsRef.docs.map(doc => new Standing(doc)); | ||
const notificationPromises = competition.participants | ||
.filter(participantId => !participantId.startsWith("Anonymous")) | ||
.map(async participantId => { | ||
const userRef = await firestore.doc(`users/${participantId}`).get(); | ||
const user = new User(userRef); | ||
const standing = standings.find(standing => standing.userId == user.id); | ||
if (standing == null) return null; | ||
|
||
const rank = standing.rank; | ||
const ordinal = ["st", "nd", "rd"][((rank+90)%100-10)%10-1] || "th"; | ||
return notifications | ||
.sendNotificationsToUser( | ||
user, | ||
"Competition complete!", | ||
`You placed ${rank}${ordinal} in ${competition.name}!` | ||
) | ||
.then(() => user.updateStatisticsWithNewRank(rank)); | ||
}); | ||
|
||
return Promise | ||
.all(notificationPromises) | ||
.then(async () => { | ||
await competition.updateRepeatingCompetition(); | ||
await competition.updateStandings(); | ||
}); | ||
}); | ||
|
||
return Promise.all(competitionPromises); | ||
}); |