mirror of
https://github.com/urosran/cally.git
synced 2025-07-10 07:07:16 +00:00
329 lines
12 KiB
TypeScript
329 lines
12 KiB
TypeScript
import {useAuthContext} from "@/contexts/AuthContext";
|
|
import {useEffect} from "react";
|
|
import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData";
|
|
import {useFetchAndSaveGoogleEvents} from "@/hooks/useFetchAndSaveGoogleEvents";
|
|
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";
|
|
import * as AuthSession from "expo-auth-session";
|
|
import * as AppleAuthentication from "expo-apple-authentication";
|
|
import * as Notifications from 'expo-notifications';
|
|
import {useQueryClient} from "@tanstack/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",
|
|
scopes: [
|
|
"email",
|
|
"profile",
|
|
"https://www.googleapis.com/auth/calendar.events.owned",
|
|
],
|
|
extraParams: {
|
|
access_type: "offline",
|
|
},
|
|
};
|
|
|
|
const microsoftConfig = {
|
|
clientId: "13c79071-1066-40a9-9f71-b8c4b138b4af",
|
|
redirectUri: AuthSession.makeRedirectUri({path: "settings"}),
|
|
scopes: [
|
|
"openid",
|
|
"profile",
|
|
"email",
|
|
"offline_access",
|
|
"Calendars.ReadWrite",
|
|
"User.Read",
|
|
],
|
|
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} = useFetchAndSaveMicrosoftEvents();
|
|
const {mutateAsync: fetchAndSaveAppleEvents, isLoading: isSyncingApple} = useFetchAndSaveAppleEvents();
|
|
|
|
WebBrowser.maybeCompleteAuthSession();
|
|
const [_, response, promptAsync] = Google.useAuthRequest(googleConfig);
|
|
|
|
useEffect(() => {
|
|
signInWithGoogle();
|
|
}, [response]);
|
|
|
|
const signInWithGoogle = async () => {
|
|
try {
|
|
if (response?.type === "success") {
|
|
const {accessToken, refreshToken} = response?.authentication!;
|
|
|
|
const userInfoResponse = await fetch(
|
|
"https://www.googleapis.com/oauth2/v3/userinfo",
|
|
{
|
|
headers: {Authorization: `Bearer ${accessToken}`},
|
|
}
|
|
);
|
|
|
|
const userInfo = await userInfoResponse.json();
|
|
const googleMail = userInfo.email;
|
|
|
|
const googleAccount: GoogleAccount = {
|
|
accessToken,
|
|
refreshToken,
|
|
email: googleMail,
|
|
expiresAt: new Date(Date.now() + 3600 * 1000),
|
|
scope: googleConfig.scopes.join(' ')
|
|
};
|
|
|
|
await updateUserData({
|
|
newUserData: {
|
|
googleAccounts: {
|
|
...profileData?.googleAccounts,
|
|
[googleMail]: googleAccount
|
|
}
|
|
},
|
|
});
|
|
|
|
await fetchAndSaveGoogleEvents({email: googleMail});
|
|
}
|
|
} catch (error) {
|
|
console.error("Error during Google sign-in:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const handleMicrosoftSignIn = async () => {
|
|
try {
|
|
const authRequest = new AuthSession.AuthRequest({
|
|
clientId: microsoftConfig.clientId,
|
|
scopes: microsoftConfig.scopes,
|
|
redirectUri: microsoftConfig.redirectUri,
|
|
responseType: AuthSession.ResponseType.Code,
|
|
usePKCE: true,
|
|
});
|
|
|
|
const authResult = await authRequest.promptAsync({
|
|
authorizationEndpoint: microsoftConfig.authorizationEndpoint,
|
|
});
|
|
|
|
if (authResult.type === "success" && authResult.params?.code) {
|
|
const code = authResult.params.code;
|
|
|
|
const tokenResponse = await fetch(microsoftConfig.tokenEndpoint, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
},
|
|
body: `client_id=${microsoftConfig.clientId}&redirect_uri=${encodeURIComponent(
|
|
microsoftConfig.redirectUri
|
|
)}&grant_type=authorization_code&code=${code}&code_verifier=${
|
|
authRequest.codeVerifier
|
|
}&scope=${encodeURIComponent(microsoftConfig.scopes.join(' '))}`,
|
|
});
|
|
|
|
if (!tokenResponse.ok) {
|
|
throw new Error(await tokenResponse.text());
|
|
}
|
|
|
|
const tokenData = await tokenResponse.json();
|
|
const userInfoResponse = await fetch(
|
|
"https://graph.microsoft.com/v1.0/me",
|
|
{
|
|
headers: {
|
|
Authorization: `Bearer ${tokenData.access_token}`,
|
|
},
|
|
}
|
|
);
|
|
|
|
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 {
|
|
const credential = await AppleAuthentication.signInAsync({
|
|
requestedScopes: [
|
|
AppleAuthentication.AppleAuthenticationScope.EMAIL,
|
|
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
|
|
],
|
|
});
|
|
|
|
const appleToken = credential.identityToken;
|
|
const appleMail = credential.email!;
|
|
|
|
if (appleToken) {
|
|
const appleAccount: AppleAccount = {
|
|
accessToken: appleToken,
|
|
email: appleMail,
|
|
identityToken: credential.identityToken!,
|
|
expiresAt: new Date(Date.now() + 3600 * 1000)
|
|
};
|
|
|
|
const updatedAppleAccounts = {
|
|
...profileData?.appleAccounts,
|
|
[appleMail]: appleAccount
|
|
};
|
|
|
|
await updateUserData({
|
|
newUserData: {appleAccounts: updatedAppleAccounts},
|
|
});
|
|
|
|
await fetchAndSaveAppleEvents({email: appleMail});
|
|
}
|
|
} catch (error) {
|
|
console.error("Error during Apple Sign-in:", error);
|
|
}
|
|
};
|
|
const resyncAllCalendars = async (): Promise<void> => {
|
|
try {
|
|
const results: SyncResponse[] = [];
|
|
|
|
if (profileData?.googleAccounts) {
|
|
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 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 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
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
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 in resyncAllCalendars:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
|
|
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) => {
|
|
queryClient.invalidateQueries({queryKey: ["events"]});
|
|
};
|
|
|
|
const sub = Notifications.addNotificationReceivedListener(handleNotification);
|
|
return () => sub.remove();
|
|
}, []);
|
|
|
|
return {
|
|
handleAppleSignIn,
|
|
handleMicrosoftSignIn,
|
|
handleGoogleSignIn: signInWithGoogle,
|
|
handleStartGoogleSignIn: promptAsync,
|
|
fetchAndSaveOutlookEvents,
|
|
fetchAndSaveAppleEvents,
|
|
fetchAndSaveGoogleEvents,
|
|
isConnectedToApple,
|
|
isConnectedToMicrosoft,
|
|
isConnectedToGoogle,
|
|
isSyncingOutlook,
|
|
isSyncingGoogle,
|
|
isSyncingApple,
|
|
resyncAllCalendars,
|
|
isSyncing: isSyncingApple || isSyncingOutlook || isSyncingGoogle
|
|
};
|
|
}; |