Files
daragt-sms/src/app/index.tsx
2026-06-02 12:54:28 +05:00

423 lines
12 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect, useRef } from 'react';
import { StyleSheet, View, Text, FlatList, TouchableOpacity, PermissionsAndroid, Platform, ScrollView } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { Stack, router } from 'expo-router';
import { useKeepAwake } from 'expo-keep-awake';
import io from 'socket.io-client';
import { SendDirectSms } from 'react-native-send-direct-sms';
const COLORS = {
background: '#121416',
surface: '#1e2022',
surfaceHigh: '#282a2c',
primary: '#fefcff',
onPrimary: '#213148',
text: '#e2e2e5',
textMuted: '#c5c6cd',
success: '#69fc9b',
successDim: '#003919',
error: '#ffb4ab',
border: '#44474d',
};
export default function DashboardScreen() {
useKeepAwake();
const [status, setStatus] = useState('Disconnected');
const [hasPermission, setHasPermission] = useState(false);
const [logs, setLogs] = useState<{id: string, time: string, msg: string, type: 'info' | 'success' | 'error'}[]>([]);
const [latestSms, setLatestSms] = useState<{phone: string, message: string} | null>(null);
const socketRef = useRef<any>(null);
useEffect(() => {
checkAndRequestPermission();
connectSocket();
return () => {
if (socketRef.current) {
socketRef.current.disconnect();
}
};
}, []);
const checkAndRequestPermission = async () => {
if (Platform.OS === 'android') {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.SEND_SMS,
{
title: 'SMS Permission',
message: 'This app needs access to send SMS messages directly.',
buttonNeutral: 'Ask Me Later',
buttonNegative: 'Cancel',
buttonPositive: 'OK',
},
);
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
setHasPermission(true);
addLog('SMS permission granted', 'success');
} else {
setHasPermission(false);
addLog('SMS permission denied', 'error');
}
} catch (err) {
console.warn(err);
addLog(`Permission error: ${err}`, 'error');
}
}
};
const addLog = (msg: string, type: 'info' | 'success' | 'error' = 'info') => {
setLogs(prev => [{
id: Math.random().toString(),
time: new Date().toLocaleTimeString('en-US', { hour12: false }),
msg,
type
}, ...prev].slice(0, 50));
};
const connectSocket = async () => {
try {
const endpoint = await AsyncStorage.getItem('endpoint');
const login = await AsyncStorage.getItem('login');
const password = await AsyncStorage.getItem('password');
if (!endpoint || !login || !password) {
setStatus('Missing Settings');
addLog('Please configure settings first', 'error');
return;
}
if (socketRef.current) {
socketRef.current.disconnect();
}
setStatus('Connecting...');
addLog('Attempting socket handshake', 'info');
const socket = io(endpoint, {
auth: { login, password },
reconnectionAttempts: Infinity,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
});
socket.on('connect', () => {
setStatus('Connected');
addLog('Connected to backend', 'success');
});
socket.on('disconnect', () => {
setStatus('Disconnected');
addLog('Connection lost: Retrying...', 'error');
});
socket.on('connect_error', (err) => {
setStatus('Error');
addLog(`Connection error: ${err.message}`, 'error');
});
socket.on('send_sms', async (data) => {
addLog(`OTP received: ${data.message}`, 'info');
setLatestSms({ phone: data.phone || '+993 61 92 92 48', message: data.message });
try {
const targetPhone = data.phone || '+993 61 92 92 48';
if (Platform.OS === 'android') {
SendDirectSms(targetPhone, data.message)
.then((res: any) => {
addLog(`OTP forwarded successfully`, 'success');
socket.emit('sms_status', { id: data.id, status: 'sent' });
})
.catch((err: any) => {
addLog(`SMS send failed: ${err}`, 'error');
socket.emit('sms_status', { id: data.id, status: 'failed' });
});
} else {
addLog('Direct SMS only supported on Android', 'error');
socket.emit('sms_status', { id: data.id, status: 'failed' });
}
} catch (error) {
addLog(`Error sending SMS: ${error}`, 'error');
socket.emit('sms_status', { id: data.id, status: 'failed' });
}
});
socketRef.current = socket;
} catch (e) {
addLog(`Setup error: ${e}`, 'error');
}
};
return (
<SafeAreaView style={styles.container}>
<Stack.Screen options={{ headerShown: false }} />
<View style={styles.header}>
<View style={styles.headerLeft}>
<Text style={styles.headerIcon}>🛡</Text>
<Text style={styles.headerTitle}>Secure Relay</Text>
</View>
<TouchableOpacity onPress={() => router.push('/settings')}>
<Text style={styles.settingsIcon}></Text>
</TouchableOpacity>
</View>
<ScrollView style={styles.scrollContent} contentContainerStyle={styles.scrollContentContainer}>
{/* Status Card */}
<View style={styles.mainStatusCard}>
<View style={[styles.statusIconContainer, { backgroundColor: status === 'Connected' ? COLORS.successDim : (status === 'Connecting...' ? '#4a3600' : '#4a0005') }]}>
<Text style={styles.statusIcon}>{status === 'Connected' ? '✓' : (status === 'Connecting...' ? '⏳' : '✕')}</Text>
</View>
<Text style={[styles.statusValue, { color: status === 'Connected' ? COLORS.success : (status === 'Connecting...' ? '#ffb4ab' : COLORS.error) }]}>
{status} {status === 'Connected' && '✅'}
</Text>
<Text style={styles.statusDesc}>
{status === 'Connected' ? 'Service is active and monitoring incoming SMS.' : 'Service is currently unavailable.'}
</Text>
</View>
{/* System Status */}
<Text style={styles.sectionTitle}>System Status</Text>
<View style={styles.systemStatusRow}>
<TouchableOpacity style={styles.systemStatusCard} onPress={checkAndRequestPermission}>
<Text style={styles.systemStatusIcon}>💬</Text>
<View>
<Text style={styles.systemStatusLabel}>SMS Perm</Text>
<Text style={[styles.systemStatusValue, { color: hasPermission ? COLORS.success : COLORS.error }]}>
{hasPermission ? 'GRANTED' : 'DENIED'}
</Text>
</View>
</TouchableOpacity>
<View style={styles.systemStatusCard}>
<Text style={styles.systemStatusIcon}>📁</Text>
<View>
<Text style={styles.systemStatusLabel}>Storage</Text>
<Text style={[styles.systemStatusValue, { color: COLORS.success }]}>
GRANTED
</Text>
</View>
</View>
</View>
<TouchableOpacity style={styles.reconnectButton} onPress={connectSocket}>
<Text style={styles.reconnectText}> Reconnect</Text>
</TouchableOpacity>
{/* Latest SMS */}
{latestSms && (
<>
<Text style={styles.sectionTitle}>Latest SMS Request</Text>
<View style={styles.latestSmsCard}>
<Text style={styles.latestSmsText}>📱 Phone: <Text style={styles.latestSmsHighlight}>{latestSms.phone}</Text></Text>
<Text style={styles.latestSmsText}>💬 Message: <Text style={styles.latestSmsHighlight}>'{latestSms.message}'</Text></Text>
</View>
</>
)}
{/* Logs */}
<View style={styles.logHeader}>
<Text style={styles.sectionTitle}>Recent Logs</Text>
<TouchableOpacity onPress={() => setLogs([])} style={styles.clearButton}>
<Text style={styles.clearButtonText}>Clear Logs</Text>
</TouchableOpacity>
</View>
<View style={styles.logsCard}>
{logs.map((item) => (
<View key={item.id} style={styles.logItem}>
<Text style={[styles.logTime, { color: item.type === 'error' ? COLORS.error : (item.type === 'success' ? COLORS.success : COLORS.textMuted) }]}>
[{item.time}]
</Text>
<Text style={[styles.logMsg, { color: item.type === 'error' ? COLORS.error : (item.type === 'success' ? COLORS.success : COLORS.textMuted) }]}>
{item.msg}
</Text>
</View>
))}
{logs.length === 0 && (
<Text style={styles.emptyLogs}>No recent activity.</Text>
)}
</View>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: COLORS.background,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 24,
paddingVertical: 16,
marginTop: 8,
},
headerLeft: {
flexDirection: 'row',
alignItems: 'center',
},
headerIcon: {
fontSize: 24,
marginRight: 8,
},
headerTitle: {
color: COLORS.text,
fontSize: 20,
fontWeight: 'bold',
},
settingsIcon: {
fontSize: 24,
color: COLORS.textMuted,
},
scrollContent: {
flex: 1,
},
scrollContentContainer: {
padding: 24,
paddingTop: 8,
},
mainStatusCard: {
backgroundColor: COLORS.surface,
borderRadius: 24,
padding: 32,
alignItems: 'center',
marginBottom: 24,
},
statusIconContainer: {
width: 48,
height: 48,
borderRadius: 24,
alignItems: 'center',
justifyContent: 'center',
marginBottom: 16,
},
statusIcon: {
fontSize: 24,
color: COLORS.success,
},
statusValue: {
fontSize: 28,
fontWeight: 'bold',
marginBottom: 8,
},
statusDesc: {
color: COLORS.textMuted,
fontSize: 14,
textAlign: 'center',
},
sectionTitle: {
color: COLORS.text,
fontSize: 16,
fontWeight: '600',
marginBottom: 12,
marginTop: 8,
},
systemStatusRow: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 24,
},
systemStatusCard: {
backgroundColor: COLORS.surface,
borderRadius: 16,
padding: 16,
flexDirection: 'row',
alignItems: 'center',
flex: 0.48,
},
systemStatusIcon: {
fontSize: 20,
marginRight: 12,
},
systemStatusLabel: {
color: COLORS.text,
fontSize: 14,
fontWeight: '500',
},
systemStatusValue: {
fontSize: 12,
fontWeight: 'bold',
marginTop: 2,
},
reconnectButton: {
backgroundColor: COLORS.primary,
borderRadius: 9999,
paddingVertical: 16,
alignItems: 'center',
marginBottom: 24,
},
reconnectText: {
color: COLORS.onPrimary,
fontSize: 16,
fontWeight: 'bold',
},
latestSmsCard: {
backgroundColor: COLORS.surface,
borderRadius: 24,
padding: 20,
marginBottom: 24,
},
latestSmsText: {
color: COLORS.textMuted,
fontSize: 14,
marginBottom: 8,
lineHeight: 20,
},
latestSmsHighlight: {
color: COLORS.text,
fontWeight: 'bold',
fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
},
logHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
clearButton: {
backgroundColor: COLORS.surfaceHigh,
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 12,
},
clearButtonText: {
color: COLORS.text,
fontSize: 12,
fontWeight: '500',
},
logsCard: {
backgroundColor: COLORS.surface,
borderRadius: 24,
padding: 20,
minHeight: 150,
},
logItem: {
flexDirection: 'row',
marginBottom: 8,
},
logTime: {
fontSize: 13,
fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
marginRight: 8,
},
logMsg: {
flex: 1,
fontSize: 13,
fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
},
emptyLogs: {
color: COLORS.textMuted,
fontSize: 13,
fontStyle: 'italic',
textAlign: 'center',
marginTop: 20,
}
});