Syncing rework

This commit is contained in:
Milan Paunovic
2024-11-26 21:13:54 +01:00
parent 5cfdc84055
commit f2af60111b
14 changed files with 960 additions and 595 deletions

View File

@ -2,7 +2,7 @@ import {useAuthContext} from "@/contexts/AuthContext";
import {useEffect} from "react";
import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData";
import {useFetchAndSaveGoogleEvents} from "@/hooks/useFetchAndSaveGoogleEvents";
import {useFetchAndSaveOutlookEvents} from "@/hooks/useFetchAndSaveOutlookEvents";
import {useFetchAndSaveMicrosoftEvents} from "@/hooks/useFetchAndSaveOutlookEvents";
import {useFetchAndSaveAppleEvents} from "@/hooks/useFetchAndSaveAppleEvents";
import * as WebBrowser from "expo-web-browser";
import * as Google from "expo-auth-session/providers/google";
@ -10,14 +10,12 @@ import * as AuthSession from "expo-auth-session";
import * as AppleAuthentication from "expo-apple-authentication";
import * as Notifications from 'expo-notifications';
import {useQueryClient} from "react-query";
import {AppleAccount, GoogleAccount, MicrosoftAccount} from "@/hooks/firebase/types/profileTypes";
const googleConfig = {
androidClientId:
"406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com",
iosClientId:
"406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com",
webClientId:
"406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com",
androidClientId: "406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com",
iosClientId: "406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com",
webClientId: "406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com",
scopes: [
"email",
"profile",
@ -39,18 +37,32 @@ const microsoftConfig = {
"Calendars.ReadWrite",
"User.Read",
],
authorizationEndpoint:
"https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
authorizationEndpoint: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
tokenEndpoint: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
};
interface SyncResponse {
success: boolean;
eventCount?: number;
error?: string;
}
interface CalendarSyncResult {
data: {
success: boolean;
eventCount: number;
message?: string;
error?: string;
}
}
export const useCalSync = () => {
const {profileData} = useAuthContext();
const queryClient = useQueryClient();
const {mutateAsync: updateUserData} = useUpdateUserData();
const {mutateAsync: fetchAndSaveGoogleEvents, isLoading: isSyncingGoogle} = useFetchAndSaveGoogleEvents();
const {mutateAsync: fetchAndSaveOutlookEvents, isLoading: isSyncingOutlook} = useFetchAndSaveOutlookEvents();
const {mutateAsync: fetchAndSaveOutlookEvents, isLoading: isSyncingOutlook} = useFetchAndSaveMicrosoftEvents();
const {mutateAsync: fetchAndSaveAppleEvents, isLoading: isSyncingApple} = useFetchAndSaveAppleEvents();
WebBrowser.maybeCompleteAuthSession();
@ -72,134 +84,106 @@ export const useCalSync = () => {
}
);
console.log(response)
const userInfo = await userInfoResponse.json();
const googleMail = userInfo.email;
let googleAccounts = profileData?.googleAccounts || {};
const updatedGoogleAccounts = {
...googleAccounts,
[googleMail]: {accessToken, refreshToken},
const googleAccount: GoogleAccount = {
accessToken,
refreshToken,
email: googleMail,
expiresAt: new Date(Date.now() + 3600 * 1000),
scope: googleConfig.scopes.join(' ')
};
console.log({refreshToken})
await updateUserData({
newUserData: {googleAccounts: updatedGoogleAccounts},
newUserData: {
googleAccounts: {
...profileData?.googleAccounts,
[googleMail]: googleAccount
}
},
});
await fetchAndSaveGoogleEvents({
token: accessToken,
refreshToken: refreshToken,
email: googleMail,
});
await fetchAndSaveGoogleEvents({email: googleMail});
}
} catch (error) {
console.error("Error during Google sign-in:", error);
throw error;
}
};
const handleMicrosoftSignIn = async () => {
try {
console.log("Starting Microsoft sign-in...");
const authRequest = new AuthSession.AuthRequest({
clientId: microsoftConfig.clientId,
scopes: microsoftConfig.scopes,
redirectUri: microsoftConfig.redirectUri,
responseType: AuthSession.ResponseType.Code,
usePKCE: true, // Enable PKCE
usePKCE: true,
});
console.log("Auth request created:", authRequest);
const authResult = await authRequest.promptAsync({
authorizationEndpoint: microsoftConfig.authorizationEndpoint,
});
console.log("Auth result:", authResult);
if (authResult.type === "success" && authResult.params?.code) {
const code = authResult.params.code;
console.log("Authorization code received:", code);
// Exchange authorization code for tokens
const tokenResponse = await fetch(microsoftConfig.tokenEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: `client_id=${
microsoftConfig.clientId
}&redirect_uri=${encodeURIComponent(
body: `client_id=${microsoftConfig.clientId}&redirect_uri=${encodeURIComponent(
microsoftConfig.redirectUri
)}&grant_type=authorization_code&code=${code}&code_verifier=${
authRequest.codeVerifier
}&scope=${encodeURIComponent(
"https://graph.microsoft.com/Calendars.ReadWrite offline_access User.Read"
)}`,
}&scope=${encodeURIComponent(microsoftConfig.scopes.join(' '))}`,
});
console.log("Token response status:", tokenResponse.status);
if (!tokenResponse.ok) {
const errorText = await tokenResponse.text();
console.error("Token exchange failed:", errorText);
return;
throw new Error(await tokenResponse.text());
}
const tokenData = await tokenResponse.json();
console.log("Token data received:", tokenData);
if (tokenData?.access_token) {
console.log("Access token received, fetching user info...");
// Fetch user info from Microsoft Graph API to get the email
const userInfoResponse = await fetch(
"https://graph.microsoft.com/v1.0/me",
{
headers: {
Authorization: `Bearer ${tokenData.access_token}`,
},
}
);
const userInfo = await userInfoResponse.json();
console.log("User info received:", userInfo);
if (userInfo.error) {
console.error("Error fetching user info:", userInfo.error);
} else {
const outlookMail = userInfo.mail || userInfo.userPrincipalName;
let microsoftAccounts = profileData?.microsoftAccounts;
const updatedMicrosoftAccounts = microsoftAccounts
? {...microsoftAccounts, [outlookMail]: tokenData.access_token}
: {[outlookMail]: tokenData.access_token};
await updateUserData({
newUserData: {microsoftAccounts: updatedMicrosoftAccounts},
});
await fetchAndSaveOutlookEvents(
tokenData.access_token,
outlookMail
);
console.log("User data updated successfully.");
const userInfoResponse = await fetch(
"https://graph.microsoft.com/v1.0/me",
{
headers: {
Authorization: `Bearer ${tokenData.access_token}`,
},
}
}
} else {
console.warn("Authentication was not successful:", authResult);
);
const userInfo = await userInfoResponse.json();
const outlookMail = userInfo.mail || userInfo.userPrincipalName;
const microsoftAccount: MicrosoftAccount = {
accessToken: tokenData.access_token,
refreshToken: tokenData.refresh_token,
email: outlookMail,
expiresAt: new Date(Date.now() + tokenData.expires_in * 1000),
};
await updateUserData({
newUserData: {
microsoftAccounts: {
...profileData?.microsoftAccounts,
[outlookMail]: microsoftAccount
}
},
});
await fetchAndSaveOutlookEvents({email: outlookMail});
}
} catch (error) {
console.error("Error during Microsoft sign-in:", error);
throw error;
}
};
const handleAppleSignIn = async () => {
try {
console.log("Starting Apple Sign-in...");
const credential = await AppleAuthentication.signInAsync({
requestedScopes: [
AppleAuthentication.AppleAuthenticationScope.EMAIL,
@ -207,117 +191,124 @@ export const useCalSync = () => {
],
});
console.log("Apple sign-in result:", credential);
alert(JSON.stringify(credential))
const appleToken = credential.identityToken;
const appleMail = credential.email!;
if (appleToken) {
console.log("Apple ID token received. Fetch user info if needed...");
const appleAccount: AppleAccount = {
accessToken: appleToken,
email: appleMail,
identityToken: credential.identityToken!,
expiresAt: new Date(Date.now() + 3600 * 1000)
};
let appleAcounts = profileData?.appleAccounts;
const updatedAppleAccounts = appleAcounts
? {...appleAcounts, [appleMail]: appleToken}
: {[appleMail]: appleToken};
const updatedAppleAccounts = {
...profileData?.appleAccounts,
[appleMail]: appleAccount
};
await updateUserData({
newUserData: {appleAccounts: updatedAppleAccounts},
});
console.log("User data updated with Apple ID token.");
await fetchAndSaveAppleEvents({token: appleToken, email: appleMail!});
} else {
console.warn(
"Apple authentication was not successful or email was hidden."
);
await fetchAndSaveAppleEvents({email: appleMail});
}
} catch (error) {
console.error("Error during Apple Sign-in:", error);
}
};
const resyncAllCalendars = async (): Promise<void> => {
try {
const syncPromises: Promise<void>[] = [];
const results: SyncResponse[] = [];
if (profileData?.googleAccounts) {
console.log(profileData.googleAccounts)
for (const [email, emailAcc] of Object.entries(profileData.googleAccounts)) {
if(emailAcc?.accessToken) {
syncPromises.push(fetchAndSaveGoogleEvents({ token: emailAcc?.accessToken, refreshToken: emailAcc?.refreshToken, email }));
for (const email of Object.keys(profileData.googleAccounts)) {
try {
const result = await fetchAndSaveGoogleEvents({email});
results.push({
success: result.success,
eventCount: result.eventCount
});
} catch (error: any) {
console.error(`Failed to sync Google calendar ${email}:`, error);
results.push({
success: false,
error: error.message
});
}
}
}
if (profileData?.microsoftAccounts) {
for (const [email, accessToken] of Object.entries(profileData.microsoftAccounts)) {
syncPromises.push(fetchAndSaveOutlookEvents(accessToken, email));
for (const email of Object.keys(profileData.microsoftAccounts)) {
try {
const result = await fetchAndSaveOutlookEvents({email});
results.push({
success: result.success,
eventCount: result.eventCount
});
} catch (error: any) {
console.error(`Failed to sync Microsoft calendar ${email}:`, error);
results.push({
success: false,
error: error.message
});
}
}
}
if (profileData?.appleAccounts) {
for (const [email, token] of Object.entries(profileData.appleAccounts)) {
syncPromises.push(fetchAndSaveAppleEvents({ token, email }));
for (const email of Object.keys(profileData.appleAccounts)) {
try {
const result = await fetchAndSaveAppleEvents({email});
results.push({
success: true,
});
} catch (error: any) {
console.error(`Failed to sync Apple calendar ${email}:`, error);
results.push({
success: false,
error: error.message
});
}
}
}
await Promise.all(syncPromises);
console.log("All calendars have been resynced.");
const successCount = results.filter(r => r.success).length;
const failCount = results.filter(r => !r.success).length;
const totalEvents = results.reduce((sum, r) => sum + (r.eventCount || 0), 0);
if (failCount > 0) {
console.error(`${failCount} calendar syncs failed, ${successCount} succeeded`);
results.filter(r => !r.success).forEach(r => {
console.error('Sync failed:', r.error);
});
} else if (successCount > 0) {
console.log(`Successfully synced ${successCount} calendars with ${totalEvents} total events`);
} else {
console.log("No calendars to sync");
}
} catch (error) {
console.error("Error resyncing calendars:", error);
console.error("Error in resyncAllCalendars:", error);
throw error;
}
};
let isConnectedToGoogle = false;
if (profileData?.googleAccounts) {
Object.values(profileData?.googleAccounts).forEach((item) => {
if (item !== null) {
isConnectedToGoogle = true;
return;
}
});
}
let isConnectedToMicrosoft = false;
const microsoftAccounts = profileData?.microsoftAccounts;
if (microsoftAccounts) {
Object.values(profileData?.microsoftAccounts).forEach((item) => {
if (item !== null) {
isConnectedToMicrosoft = true;
return;
}
});
}
let isConnectedToApple = false;
if (profileData?.appleAccounts) {
Object.values(profileData?.appleAccounts).forEach((item) => {
if (item !== null) {
isConnectedToApple = true;
return;
}
});
}
const isConnectedToGoogle = Object.values(profileData?.googleAccounts || {}).some(account => !!account);
const isConnectedToMicrosoft = Object.values(profileData?.microsoftAccounts || {}).some(account => !!account);
const isConnectedToApple = Object.values(profileData?.appleAccounts || {}).some(account => !!account);
useEffect(() => {
const handleNotification = async (notification: Notifications.Notification) => {
const eventId = notification?.request?.content?.data?.eventId;
// await resyncAllCalendars();
queryClient.invalidateQueries(["events"]);
};
const sub = Notifications.addNotificationReceivedListener(handleNotification);
return () => sub.remove();
}, []);
return {
handleAppleSignIn,
handleMicrosoftSignIn,
@ -334,5 +325,5 @@ export const useCalSync = () => {
isSyncingApple,
resyncAllCalendars,
isSyncing: isSyncingApple || isSyncingOutlook || isSyncingGoogle
}
}
};
};