/* eslint-disable react/display-name */ import { GoogleSigninButton } from "@react-native-google-signin/google-signin"; import * as AuthSession from "expo-auth-session"; import * as GoogleAuth from "expo-auth-session/providers/google"; import Constants from "expo-constants"; import * as WebBrowser from "expo-web-browser"; import React, { useEffect, useState } from "react"; import { ActivityIndicator, Platform, Pressable, StyleSheet, Text, View, } from "react-native"; import Svg, { Path } from "react-native-svg"; import { useGlobal } from "reactn"; import { background } from "../assets"; import GradientButton from "../components/GradientButton.js"; import { Input } from "../components/Input.js"; import ItemContainer from "../components/ItemContainer/ItemContainer"; import { handleFirebaseError } from "../actions/signupActions.js"; import firebase, { usersRef } from "../config/firebase"; import { GOOGLE_ANDROID_CLIENT_ID, GOOGLE_IOS_CLIENT_ID, GOOGLE_WEB_CLIENT_ID, } from "../data/keys"; import { isWeb } from "../hooks/useLayoutType"; import Page from "../layouts/Page.js"; import { Routes } from "../navigation"; import { navigate } from "../navigation/NavigationService.js"; import { FONT_FAMILY } from "../styles/Fonts.js"; import Palette from "../styles/Palette.js"; export default ({ navigation }) => { WebBrowser.maybeCompleteAuthSession(); const [, setTooltip] = useGlobal("_tooltip"); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [loading, setLoading] = useState(false); // Let the provider compute a compliant redirect URI for native (com.googleusercontent.apps.:/oauth2redirect) // Avoid forcing a custom scheme like musicland:// which Google can reject for native apps. const isExpoGo = Constants?.appOwnership === "expo"; const expoRedirectUri = AuthSession.makeRedirectUri({ useProxy: true }); const nativeRedirectUri = getNativeGoogleRedirectUri(); const redirectUri = isWeb || isExpoGo ? expoRedirectUri : nativeRedirectUri || expoRedirectUri; const [request, response, promptAsync] = GoogleAuth.useAuthRequest({ iosClientId: GOOGLE_IOS_CLIENT_ID, androidClientId: GOOGLE_ANDROID_CLIENT_ID, webClientId: GOOGLE_WEB_CLIENT_ID, expoClientId: GOOGLE_WEB_CLIENT_ID, redirectUri, // Use Authorization Code + PKCE to comply with Google OAuth for native apps responseType: AuthSession.ResponseType.Code, usePKCE: true, scopes: ["openid", "profile", "email"], }); const afterLoginNavigate = async () => { const uid = firebase.auth().currentUser?.uid; if (!uid) throw new Error("Aucun utilisateur après connexion"); const snap = await usersRef.doc(uid).get(); const hasUserName = !!snap.data()?.userName; if (hasUserName) { navigation.reset({ index: 0, routes: [{ name: Routes.BottomTab }] }); } else { navigation.reset({ index: 0, routes: [{ name: Routes.CreatePseudo }] }); } }; const onLogin = async () => { try { setLoading(true); await firebase.auth().signInWithEmailAndPassword(email.trim(), password); await afterLoginNavigate(); } catch (e) { console.log("Login error", e?.message); const message = e?.code && e.code.startsWith("auth/") ? handleFirebaseError(e.code) : e?.message || "Connexion impossible"; setTooltip({ text: message, type: "error" }); } finally { setLoading(false); } }; const onLoginWithGoogle = async () => { try { setLoading(true); if (isWeb) { const provider = new firebase.auth.GoogleAuthProvider(); provider.addScope("profile"); provider.addScope("email"); await firebase.auth().signInWithPopup(provider); await afterLoginNavigate(); setLoading(false); } else { // Use defaults from the request; don't override with proxy here const result = await promptAsync({ useProxy: isExpoGo }); if (result?.type !== "success") { // Cancelled or errored during the browser flow setLoading(false); } } } catch (e) { console.log("Google Login error", e?.message); const message = e?.code && e.code.startsWith("auth/") ? handleFirebaseError(e.code) : e?.message; setTooltip({ text: message || "Connexion Google impossible. Merci de réessayer.", type: "error", }); setLoading(false); } }; // Handle native Google OAuth response useEffect(() => { const handleNativeGoogleResponse = async () => { try { if (response?.type === "success") { // If using Expo proxy, tokens can be present already. let idToken = response?.authentication?.idToken || response?.params?.id_token; if (!idToken) { // Otherwise, exchange the authorization code for tokens using PKCE. const code = response?.params?.code || response?.authentication?.code; if (!code) throw new Error("Code d'autorisation Google manquant"); // Use the exact clientId used during the authorize request. const clientId = request?.clientId; const discovery = { authorizationEndpoint: "https://accounts.google.com/o/oauth2/v2/auth", tokenEndpoint: "https://oauth2.googleapis.com/token", revocationEndpoint: "https://oauth2.googleapis.com/revoke", }; // Light debug: helps identify redirect/client mismatches in dev. console.log("Google token exchange", { clientId, redirectUri: request?.redirectUri, hasCodeVerifier: !!request?.codeVerifier, }); const tokenResponse = await AuthSession.exchangeCodeAsync( { clientId, code, redirectUri: request?.redirectUri, extraParams: { code_verifier: request?.codeVerifier }, }, discovery ); idToken = tokenResponse?.id_token; } if (!idToken) throw new Error("Jeton Google manquant (échange/retour)"); const credential = firebase.auth.GoogleAuthProvider.credential(idToken); await firebase.auth().signInWithCredential(credential); await afterLoginNavigate(); } } catch (e) { console.log("Google native sign-in error", e?.message); const message = e?.code && e.code.startsWith("auth/") ? handleFirebaseError(e.code) : e?.message; setTooltip({ text: message || "Connexion Google impossible", type: "error", }); } finally { setLoading(false); } }; if (!isWeb && response) { handleNativeGoogleResponse(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [response]); return ( {"Bonjour !"} Nous sommes heureux de te revoir parmi nous. Prêt·e à reprendre le rythme ? navigate(Routes.ForgotPassword)}> Mot de passe oublié? navigate(Routes.Register)}> Pas encore de compte?{" "} Créer un compte ); }; const GoogleAuthButton = ({ onPress, disabled, loading }) => { if (isWeb) { return ( [ styles.webButton, pressed && !disabled ? styles.webButtonPressed : null, disabled ? styles.webButtonDisabled : null, ]} > Continuer avec Google {loading ? ( ) : ( )} ); } return ( {loading ? ( ) : null} ); }; const getNativeGoogleRedirectUri = () => { const clientId = Platform.select({ ios: GOOGLE_IOS_CLIENT_ID, android: GOOGLE_ANDROID_CLIENT_ID, default: null, }); if (!clientId || !clientId.includes(".apps.googleusercontent.com")) { return undefined; } const clientPrefix = clientId.replace(".apps.googleusercontent.com", ""); return `com.googleusercontent.apps.${clientPrefix}:/oauth2redirect`; }; const styles = StyleSheet.create({ webButton: { alignSelf: "center", width: "80%", height: 48, borderRadius: 4, backgroundColor: Palette.white, borderWidth: 1, borderColor: "#DADCE0", justifyContent: "center", shadowColor: "#000", shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.12, shadowRadius: 2, elevation: 1, }, webButtonPressed: { backgroundColor: "#F6F9FE", }, webButtonDisabled: { opacity: 0.7, }, webContent: { flexDirection: "row", alignItems: "center", justifyContent: "center", paddingHorizontal: 12, }, webIconContainer: { justifyContent: "center", alignItems: "center", marginRight: 12, }, webText: { flex: 1, textAlign: "center", fontSize: 14, color: "#3C4043", fontFamily: FONT_FAMILY.InterSemiBold, }, webRightSpacer: { width: 18, height: 18, }, nativeButtonWrapper: { alignSelf: "center", width: "80%", height: 50, justifyContent: "center", }, nativeButton: { width: "100%", height: 50, }, nativeLoadingOverlay: { ...StyleSheet.absoluteFillObject, alignItems: "center", justifyContent: "center", }, }); const GoogleLogo = () => ( );