mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-05 01:39:08 +00:00
authentication error handling
This commit is contained in:
parent
b5ae55da9e
commit
36d93c270a
3 changed files with 100 additions and 12 deletions
|
|
@ -11,6 +11,33 @@ import { useToast } from '../contexts/ToastContext';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
|
|
||||||
const { width, height } = Dimensions.get('window');
|
const { width, height } = Dimensions.get('window');
|
||||||
|
const EMAIL_CONFIRMATION_REQUIRED_PREFIX = '__EMAIL_CONFIRMATION__';
|
||||||
|
|
||||||
|
const normalizeAuthErrorMessage = (input: string): string => {
|
||||||
|
const raw = (input || '').trim();
|
||||||
|
if (!raw) return 'Authentication failed';
|
||||||
|
|
||||||
|
let parsed: any = null;
|
||||||
|
if (raw.startsWith('{') && raw.endsWith('}')) {
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(raw);
|
||||||
|
} catch {
|
||||||
|
parsed = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const code = (parsed?.error_code || parsed?.code || '').toString().toLowerCase();
|
||||||
|
const message = (parsed?.msg || parsed?.message || raw).toString();
|
||||||
|
|
||||||
|
if (code === 'invalid_credentials' || /invalid login credentials/i.test(message)) {
|
||||||
|
return 'Invalid email or password';
|
||||||
|
}
|
||||||
|
if (code === 'email_not_confirmed' || /email not confirmed/i.test(message)) {
|
||||||
|
return 'Email not confirmed. Check your inbox or Spam/Junk folder, verify your account, then sign in.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
};
|
||||||
|
|
||||||
const AuthScreen: React.FC = () => {
|
const AuthScreen: React.FC = () => {
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
|
|
@ -168,8 +195,19 @@ const AuthScreen: React.FC = () => {
|
||||||
setError(null);
|
setError(null);
|
||||||
const err = mode === 'signin' ? await signIn(email.trim(), password) : await signUp(email.trim(), password);
|
const err = mode === 'signin' ? await signIn(email.trim(), password) : await signUp(email.trim(), password);
|
||||||
if (err) {
|
if (err) {
|
||||||
setError(err);
|
if (mode === 'signup' && err.startsWith(EMAIL_CONFIRMATION_REQUIRED_PREFIX)) {
|
||||||
showError('Authentication Failed', err);
|
setError(null);
|
||||||
|
setMode('signin');
|
||||||
|
setPassword('');
|
||||||
|
setConfirmPassword('');
|
||||||
|
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {});
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanError = normalizeAuthErrorMessage(err);
|
||||||
|
setError(cleanError);
|
||||||
|
showError('Authentication Failed', cleanError);
|
||||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {});
|
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {});
|
||||||
} else {
|
} else {
|
||||||
const msg = mode === 'signin' ? 'Logged in successfully' : 'Sign up successful';
|
const msg = mode === 'signin' ? 'Logged in successfully' : 'Sign up successful';
|
||||||
|
|
@ -261,7 +299,24 @@ const AuthScreen: React.FC = () => {
|
||||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
keyboardVerticalOffset={Platform.OS === 'ios' ? headerHeight : 0}
|
keyboardVerticalOffset={Platform.OS === 'ios' ? headerHeight : 0}
|
||||||
>
|
>
|
||||||
<Animated.View style={styles.centerContainer}>
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
styles.centerContainer,
|
||||||
|
keyboardHeight > 0
|
||||||
|
? {
|
||||||
|
paddingTop: Platform.OS === 'ios' ? 6 : 10,
|
||||||
|
transform: [
|
||||||
|
{
|
||||||
|
translateY:
|
||||||
|
Platform.OS === 'ios'
|
||||||
|
? -Math.min(120, keyboardHeight * 0.35)
|
||||||
|
: -Math.min(84, keyboardHeight * 0.22),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
]}
|
||||||
|
>
|
||||||
<Animated.View style={[styles.card, {
|
<Animated.View style={[styles.card, {
|
||||||
backgroundColor: Platform.OS === 'android' ? '#121212' : 'rgba(255,255,255,0.02)',
|
backgroundColor: Platform.OS === 'android' ? '#121212' : 'rgba(255,255,255,0.02)',
|
||||||
borderColor: Platform.OS === 'android' ? '#1f1f1f' : 'rgba(255,255,255,0.06)',
|
borderColor: Platform.OS === 'android' ? '#1f1f1f' : 'rgba(255,255,255,0.06)',
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ export type AuthUser = {
|
||||||
|
|
||||||
const USER_DATA_KEY = '@user:data';
|
const USER_DATA_KEY = '@user:data';
|
||||||
const USER_SCOPE_KEY = '@user:current';
|
const USER_SCOPE_KEY = '@user:current';
|
||||||
|
const EMAIL_CONFIRMATION_REQUIRED_PREFIX = '__EMAIL_CONFIRMATION__';
|
||||||
|
|
||||||
class AccountService {
|
class AccountService {
|
||||||
private static instance: AccountService;
|
private static instance: AccountService;
|
||||||
|
|
@ -37,11 +38,18 @@ class AccountService {
|
||||||
|
|
||||||
async signUpWithEmail(email: string, password: string): Promise<{ user?: AuthUser; error?: string }> {
|
async signUpWithEmail(email: string, password: string): Promise<{ user?: AuthUser; error?: string }> {
|
||||||
const result = await supabaseSyncService.signUpWithEmail(email, password);
|
const result = await supabaseSyncService.signUpWithEmail(email, password);
|
||||||
if (result.error || !result.user) {
|
if (result.error) {
|
||||||
return { error: result.error || 'Sign up failed' };
|
return { error: result.error };
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapped = this.mapSupabaseUser(result.user);
|
const sessionUser = supabaseSyncService.getCurrentSessionUser();
|
||||||
|
if (!sessionUser) {
|
||||||
|
return {
|
||||||
|
error: `${EMAIL_CONFIRMATION_REQUIRED_PREFIX}Account created. Check your email to verify, then sign in.`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapped = this.mapSupabaseUser(sessionUser);
|
||||||
await this.persistUser(mapped);
|
await this.persistUser(mapped);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -193,10 +193,8 @@ class SupabaseSyncService {
|
||||||
await this.setSession(response.session);
|
await this.setSession(response.session);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response.user) {
|
// In email-confirmation mode, Supabase may not establish a session immediately.
|
||||||
return { error: 'Signup failed: user not returned by Supabase' };
|
// Treat this as a successful signup attempt and let caller handle next UX step.
|
||||||
}
|
|
||||||
|
|
||||||
return { user: response.user };
|
return { user: response.user };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return { error: this.extractErrorMessage(error, 'Signup failed') };
|
return { error: this.extractErrorMessage(error, 'Signup failed') };
|
||||||
|
|
@ -768,7 +766,11 @@ class SupabaseSyncService {
|
||||||
|
|
||||||
private buildRequestError(status: number, parsed: unknown, raw: string): Error {
|
private buildRequestError(status: number, parsed: unknown, raw: string): Error {
|
||||||
if (parsed && typeof parsed === 'object') {
|
if (parsed && typeof parsed === 'object') {
|
||||||
const message = (parsed as any).message || (parsed as any).error_description || (parsed as any).error;
|
const message =
|
||||||
|
(parsed as any).message ||
|
||||||
|
(parsed as any).msg ||
|
||||||
|
(parsed as any).error_description ||
|
||||||
|
(parsed as any).error;
|
||||||
if (typeof message === 'string' && message.trim().length > 0) {
|
if (typeof message === 'string' && message.trim().length > 0) {
|
||||||
return new Error(message);
|
return new Error(message);
|
||||||
}
|
}
|
||||||
|
|
@ -781,7 +783,30 @@ class SupabaseSyncService {
|
||||||
|
|
||||||
private extractErrorMessage(error: unknown, fallback: string): string {
|
private extractErrorMessage(error: unknown, fallback: string): string {
|
||||||
if (error instanceof Error && error.message) {
|
if (error instanceof Error && error.message) {
|
||||||
return error.message;
|
const raw = error.message.trim();
|
||||||
|
|
||||||
|
let parsed: any = null;
|
||||||
|
if (raw.startsWith('{') && raw.endsWith('}')) {
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(raw);
|
||||||
|
} catch {
|
||||||
|
parsed = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorCode = (parsed?.error_code || parsed?.code || '').toString().toLowerCase();
|
||||||
|
const message = (parsed?.msg || parsed?.message || raw).toString().trim();
|
||||||
|
|
||||||
|
if (errorCode === 'invalid_credentials') {
|
||||||
|
return 'Invalid email or password';
|
||||||
|
}
|
||||||
|
if (errorCode === 'email_not_confirmed') {
|
||||||
|
return 'Email not confirmed. Check your inbox or Spam/Junk folder, verify your account, then sign in.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.length > 0) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue