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 "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 => { 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(["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 }; };