feat: add OAuth redirect handling and update Google OAuth integration

This commit is contained in:
2026-03-19 21:19:49 -03:00
parent 34cbd4a861
commit 39f024f98d
5 changed files with 31 additions and 23 deletions
+3 -1
View File
@@ -22,7 +22,9 @@
"Bash(source:*)", "Bash(source:*)",
"Bash(npm install:*)", "Bash(npm install:*)",
"Bash(npx tsc:*)", "Bash(npx tsc:*)",
"Bash(npx expo:*)" "Bash(npx expo:*)",
"Bash(find /var/home/felipecn/DEV/MeowSpool/mobile -name .env* -o -name *.env*)",
"Bash(grep -r \"useURL\\\\|useDeepLinking\\\\|Linking.parse\\\\|useLinkTo\" /var/home/felipecn/DEV/MeowSpool/mobile/app --include=*.tsx)"
] ]
} }
} }
-4
View File
@@ -7,7 +7,6 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod'; import { z } from 'zod';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import * as Google from 'expo-auth-session/providers/google'; import * as Google from 'expo-auth-session/providers/google';
import * as WebBrowser from 'expo-web-browser';
import { Screen } from '@presentation/components/layout/Screen'; import { Screen } from '@presentation/components/layout/Screen';
import { Input } from '@presentation/components/ui/Input'; import { Input } from '@presentation/components/ui/Input';
import { Button } from '@presentation/components/ui/Button'; import { Button } from '@presentation/components/ui/Button';
@@ -16,9 +15,6 @@ import { loginUseCase, googleLoginUseCase } from '@infrastructure/container';
import { useAuthStore } from '@store/authStore'; import { useAuthStore } from '@store/authStore';
import { CatIcon } from '@presentation/components/icons/CatIcon'; import { CatIcon } from '@presentation/components/icons/CatIcon';
// Necessário para fechar o browser após redirect OAuth no Android/iOS
WebBrowser.maybeCompleteAuthSession();
const schema = z.object({ const schema = z.object({
email: z.string().email('E-mail inválido'), email: z.string().email('E-mail inválido'),
password: z.string().min(1, 'Senha obrigatória'), password: z.string().min(1, 'Senha obrigatória'),
+11
View File
@@ -0,0 +1,11 @@
import * as WebBrowser from 'expo-web-browser';
// Esta tela é o destino do redirect OAuth (meowspool://oauthredirect?code=...).
// maybeCompleteAuthSession() detecta os parâmetros da URL, sinaliza o
// expo-auth-session para fechar o browser e entregar o resultado ao hook
// useAuthRequest em login.tsx.
WebBrowser.maybeCompleteAuthSession();
export default function OAuthRedirect(): null {
return null;
}
+16 -17
View File
@@ -2,16 +2,15 @@
* Plugin local: injeta o intent filter do Google OAuth no AndroidManifest.xml * Plugin local: injeta o intent filter do Google OAuth no AndroidManifest.xml
* durante o `expo prebuild`. * durante o `expo prebuild`.
* *
* Sem esse intent filter, o Android não sabe que o MeowSpool deve interceptar * expo-auth-session v5+ usa ${Application.applicationId}:/oauthredirect como
* o redirect do Google OAuth (scheme com.googleusercontent.apps.*), fazendo o * redirect URI nativo (ex: com.meowspool.app:/oauthredirect).
* Chrome tratar a URL como pesquisa. * Sem esse intent filter, o Android não intercepta o redirect e o Chrome
* * trata a URL como pesquisa, redirecionando para o google.com.
* O scheme é o reverse do Android OAuth 2.0 client ID registrado no Google Cloud Console.
*/ */
const { withAndroidManifest } = require('@expo/config-plugins'); const { withAndroidManifest } = require("@expo/config-plugins");
const GOOGLE_REVERSE_CLIENT_ID = // Scheme gerado por expo-auth-session v7: applicationId do app
'com.googleusercontent.apps.724520558909-bg5a7e4u24jmis8lgg41ucs0kv0nfp1v'; const APP_SCHEME = "com.meowspool.app";
module.exports = function withGoogleOAuth(config) { module.exports = function withGoogleOAuth(config) {
return withAndroidManifest(config, (config) => { return withAndroidManifest(config, (config) => {
@@ -19,29 +18,29 @@ module.exports = function withGoogleOAuth(config) {
const activities = manifest.manifest.application?.[0]?.activity ?? []; const activities = manifest.manifest.application?.[0]?.activity ?? [];
const mainActivity = activities.find( const mainActivity = activities.find(
(a) => a.$?.['android:name'] === '.MainActivity', (a) => a.$?.["android:name"] === ".MainActivity",
); );
if (!mainActivity) return config; if (!mainActivity) return config;
// Evita duplicar o intent filter em rebuilds consecutivos // Evita duplicar o intent filter em rebuilds consecutivos
const alreadyAdded = (mainActivity['intent-filter'] ?? []).some((filter) => const alreadyAdded = (mainActivity["intent-filter"] ?? []).some((filter) =>
(filter.data ?? []).some( (filter.data ?? []).some(
(d) => d.$?.['android:scheme'] === GOOGLE_REVERSE_CLIENT_ID, (d) => d.$?.["android:scheme"] === APP_SCHEME,
), ),
); );
if (alreadyAdded) return config; if (alreadyAdded) return config;
mainActivity['intent-filter'] = [ mainActivity["intent-filter"] = [
...(mainActivity['intent-filter'] ?? []), ...(mainActivity["intent-filter"] ?? []),
{ {
action: [{ $: { 'android:name': 'android.intent.action.VIEW' } }], action: [{ $: { "android:name": "android.intent.action.VIEW" } }],
category: [ category: [
{ $: { 'android:name': 'android.intent.category.DEFAULT' } }, { $: { "android:name": "android.intent.category.DEFAULT" } },
{ $: { 'android:name': 'android.intent.category.BROWSABLE' } }, { $: { "android:name": "android.intent.category.BROWSABLE" } },
], ],
data: [{ $: { 'android:scheme': GOOGLE_REVERSE_CLIENT_ID } }], data: [{ $: { "android:scheme": APP_SCHEME } }],
}, },
]; ];
@@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { import {
SafeAreaView,
ScrollView, ScrollView,
KeyboardAvoidingView, KeyboardAvoidingView,
View, View,
@@ -8,6 +7,7 @@ import {
StyleSheet, StyleSheet,
type ViewStyle, type ViewStyle,
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { colors, spacing } from '@shared/theme'; import { colors, spacing } from '@shared/theme';
interface ScreenProps { interface ScreenProps {