/* eslint-disable react/display-name */ import * as AuthSession from "expo-auth-session"; import * as GoogleAuth from "expo-auth-session/providers/google"; import * as WebBrowser from "expo-web-browser"; import React, { useEffect, useState } from "react"; import { Platform, Pressable, Text, View } from "react-native"; 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.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 [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, // 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); setTooltip({ text: e?.message || "Connexion impossible", 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(); if (result?.type !== "success") { // Cancelled or errored during the browser flow setLoading(false); } } } catch (e) { console.log("Google Login error", e?.message); setTooltip({ text: e?.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); setTooltip({ text: e?.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 ); };