mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-25 05:17:56 +00:00
fix(mobile): critical security and error handling improvements
🔐 SECURITY FIXES: - Fixed CRITICAL seed storage vulnerability * Changed from AsyncStorage to SecureStore for wallet seeds * Seeds now encrypted in hardware-backed secure storage * Affects: PolkadotContext.tsx (lines 166, 189) 🛡️ ERROR HANDLING: - Added global ErrorBoundary component * Catches unhandled React errors * Shows user-friendly error UI * Integrated into App.tsx provider hierarchy * Files: ErrorBoundary.tsx (new), App.tsx, components/index.ts 🧹 PRODUCTION READINESS: - Protected all 47 console statements with __DEV__ checks * console.log: 12 statements * console.error: 32 statements * console.warn: 1 statement * Files affected: 16 files across contexts, screens, i18n * Production builds will strip these out 📦 PROVIDER HIERARCHY: - Added BiometricAuthProvider to App.tsx - Updated provider order: ErrorBoundary → Polkadot → Language → BiometricAuth → Navigator Files modified: 18 New files: 1 (ErrorBoundary.tsx) This commit resolves 3 P0 critical issues from production readiness audit.
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
// ========================================
|
||||
// Error Boundary Component (React Native)
|
||||
// ========================================
|
||||
// Catches React errors and displays fallback UI
|
||||
|
||||
import React, { Component, ErrorInfo, ReactNode } from 'react';
|
||||
import { View, Text, TouchableOpacity, ScrollView, StyleSheet } from 'react-native';
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
fallback?: ReactNode;
|
||||
onError?: (error: Error, errorInfo: ErrorInfo) => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
hasError: boolean;
|
||||
error: Error | null;
|
||||
errorInfo: ErrorInfo | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Global Error Boundary for React Native
|
||||
* Catches unhandled errors in React component tree
|
||||
*
|
||||
* @example
|
||||
* <ErrorBoundary>
|
||||
* <App />
|
||||
* </ErrorBoundary>
|
||||
*/
|
||||
export class ErrorBoundary extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
hasError: false,
|
||||
error: null,
|
||||
errorInfo: null,
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): Partial<State> {
|
||||
// Update state so next render shows fallback UI
|
||||
return {
|
||||
hasError: true,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
|
||||
// Log error to console
|
||||
if (__DEV__) {
|
||||
console.error('ErrorBoundary caught an error:', error, errorInfo);
|
||||
}
|
||||
|
||||
// Update state with error details
|
||||
this.setState({
|
||||
error,
|
||||
errorInfo,
|
||||
});
|
||||
|
||||
// Call custom error handler if provided
|
||||
if (this.props.onError) {
|
||||
this.props.onError(error, errorInfo);
|
||||
}
|
||||
|
||||
// In production, you might want to log to an error reporting service
|
||||
// Example: Sentry.captureException(error);
|
||||
}
|
||||
|
||||
handleReset = (): void => {
|
||||
this.setState({
|
||||
hasError: false,
|
||||
error: null,
|
||||
errorInfo: null,
|
||||
});
|
||||
};
|
||||
|
||||
render(): ReactNode {
|
||||
if (this.state.hasError) {
|
||||
// Use custom fallback if provided
|
||||
if (this.props.fallback) {
|
||||
return this.props.fallback;
|
||||
}
|
||||
|
||||
// Default error UI for React Native
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
>
|
||||
<View style={styles.card}>
|
||||
{/* Error Icon */}
|
||||
<View style={styles.iconContainer}>
|
||||
<Text style={styles.iconText}>⚠️</Text>
|
||||
</View>
|
||||
|
||||
{/* Error Title */}
|
||||
<Text style={styles.title}>Something Went Wrong</Text>
|
||||
<Text style={styles.description}>
|
||||
An unexpected error occurred. We apologize for the inconvenience.
|
||||
</Text>
|
||||
|
||||
{/* Error Details (Development Only) */}
|
||||
{__DEV__ && this.state.error && (
|
||||
<View style={styles.errorDetails}>
|
||||
<Text style={styles.errorDetailsTitle}>
|
||||
Error Details (for developers)
|
||||
</Text>
|
||||
<View style={styles.errorBox}>
|
||||
<Text style={styles.errorLabel}>Error:</Text>
|
||||
<Text style={styles.errorText}>
|
||||
{this.state.error.toString()}
|
||||
</Text>
|
||||
{this.state.errorInfo && (
|
||||
<>
|
||||
<Text style={[styles.errorLabel, styles.stackLabel]}>
|
||||
Component Stack:
|
||||
</Text>
|
||||
<Text style={styles.errorText}>
|
||||
{this.state.errorInfo.componentStack}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Action Buttons */}
|
||||
<View style={styles.buttonContainer}>
|
||||
<TouchableOpacity
|
||||
style={styles.primaryButton}
|
||||
onPress={this.handleReset}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<Text style={styles.buttonText}>Try Again</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Support Contact */}
|
||||
<Text style={styles.supportText}>
|
||||
If this problem persists, please contact support at{' '}
|
||||
<Text style={styles.supportEmail}>info@pezkuwichain.io</Text>
|
||||
</Text>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// No error, render children normally
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#0a0a0a',
|
||||
},
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
},
|
||||
scrollContent: {
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center',
|
||||
padding: 16,
|
||||
},
|
||||
card: {
|
||||
backgroundColor: '#1a1a1a',
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: '#2a2a2a',
|
||||
padding: 24,
|
||||
},
|
||||
iconContainer: {
|
||||
alignItems: 'center',
|
||||
marginBottom: 16,
|
||||
},
|
||||
iconText: {
|
||||
fontSize: 48,
|
||||
},
|
||||
title: {
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
color: '#ffffff',
|
||||
textAlign: 'center',
|
||||
marginBottom: 12,
|
||||
},
|
||||
description: {
|
||||
fontSize: 16,
|
||||
color: '#9ca3af',
|
||||
textAlign: 'center',
|
||||
marginBottom: 24,
|
||||
lineHeight: 24,
|
||||
},
|
||||
errorDetails: {
|
||||
marginBottom: 24,
|
||||
},
|
||||
errorDetailsTitle: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#6b7280',
|
||||
marginBottom: 12,
|
||||
},
|
||||
errorBox: {
|
||||
backgroundColor: '#0a0a0a',
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
borderColor: '#374151',
|
||||
padding: 12,
|
||||
},
|
||||
errorLabel: {
|
||||
fontSize: 12,
|
||||
fontWeight: 'bold',
|
||||
color: '#ef4444',
|
||||
marginBottom: 8,
|
||||
},
|
||||
stackLabel: {
|
||||
marginTop: 12,
|
||||
},
|
||||
errorText: {
|
||||
fontSize: 11,
|
||||
fontFamily: 'monospace',
|
||||
color: '#9ca3af',
|
||||
lineHeight: 16,
|
||||
},
|
||||
buttonContainer: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
primaryButton: {
|
||||
backgroundColor: '#00A94F',
|
||||
borderRadius: 8,
|
||||
padding: 16,
|
||||
alignItems: 'center',
|
||||
},
|
||||
buttonText: {
|
||||
color: '#ffffff',
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
supportText: {
|
||||
fontSize: 14,
|
||||
color: '#6b7280',
|
||||
textAlign: 'center',
|
||||
lineHeight: 20,
|
||||
},
|
||||
supportEmail: {
|
||||
color: '#00A94F',
|
||||
},
|
||||
});
|
||||
@@ -3,6 +3,7 @@
|
||||
* Inspired by Material Design 3, iOS HIG, and Kurdistan aesthetics
|
||||
*/
|
||||
|
||||
export { ErrorBoundary } from './ErrorBoundary';
|
||||
export { Card } from './Card';
|
||||
export { Button } from './Button';
|
||||
export { Input } from './Input';
|
||||
|
||||
Reference in New Issue
Block a user