196 lines
5.8 KiB
TypeScript
196 lines
5.8 KiB
TypeScript
|
import React, { useEffect, useState } from 'react';
|
||
|
import { useLocalSearchParams } from 'expo-router';
|
||
|
import { YStack, XStack, Text, Input, Button, ScrollView } from 'tamagui';
|
||
|
import { chacha20Encrypt,chacha20Decrypt } from '../../components/crypto';
|
||
|
|
||
|
const ChatApp = () => {
|
||
|
const {friendUsername } = useLocalSearchParams(); // Friend's username passed via params
|
||
|
const [messages, setMessages] = useState([]);
|
||
|
const [inputText, setInputText] = useState('');
|
||
|
const [loading, setLoading] = useState(true);
|
||
|
const [friendPubKey, setFriendPubKey] = useState(null); // Friend's public key
|
||
|
|
||
|
// Fetch friend's public key
|
||
|
const fetchPublicKey = async () => {
|
||
|
try {
|
||
|
const authToken = localStorage.getItem('jwtToken');
|
||
|
if (!authToken) {
|
||
|
console.error('User is not authenticated');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const response = await fetch(`http://localhost:4000/user/pubkey?username=${friendUsername}`, {
|
||
|
headers: {
|
||
|
Authorization: `Bearer ${authToken}`,
|
||
|
},
|
||
|
});
|
||
|
|
||
|
if (!response.ok) throw new Error('Failed to fetch public key');
|
||
|
const { pubKey } = await response.json();
|
||
|
setFriendPubKey(pubKey);
|
||
|
} catch (err) {
|
||
|
console.error('Error fetching public key:', err);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Fetch messages
|
||
|
// Fetch messages
|
||
|
const fetchMessages = async () => {
|
||
|
try {
|
||
|
const authToken = localStorage.getItem('jwtToken');
|
||
|
if (!authToken) {
|
||
|
console.error('User is not authenticated');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const response = await fetch(`http://localhost:4000/messages/?receiverUsername=${friendUsername}`, {
|
||
|
headers: {
|
||
|
Authorization: `Bearer ${authToken}`,
|
||
|
},
|
||
|
});
|
||
|
|
||
|
if (!response.ok) throw new Error('Failed to fetch messages');
|
||
|
|
||
|
// Fetch encrypted messages as JSON
|
||
|
const encryptedMessages = await response.json();
|
||
|
console.log('Encrypted Messages:', encryptedMessages); // Debug: log encrypted messages
|
||
|
|
||
|
// Decrypt each message and store the decrypted result
|
||
|
const decryptedMessages = await Promise.all(
|
||
|
encryptedMessages.map(async (msg) => {
|
||
|
const decryptedMessage = await chacha20Decrypt(msg);
|
||
|
return { ...msg, content: decryptedMessage }; // Attach decrypted message to original message
|
||
|
})
|
||
|
);
|
||
|
|
||
|
console.log('Decrypted Messages:', decryptedMessages); // Debug: log decrypted messages
|
||
|
|
||
|
// Update the state with the decrypted messages
|
||
|
setMessages(decryptedMessages);
|
||
|
} catch (err) {
|
||
|
console.error('Error fetching messages:', err);
|
||
|
setMessages(["Error fetching messages"]); // Provide a default error message
|
||
|
} finally {
|
||
|
setLoading(false);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
// Fetch initial data
|
||
|
useEffect(() => {
|
||
|
fetchPublicKey();
|
||
|
fetchMessages();
|
||
|
|
||
|
// Poll for new messages every 5 seconds
|
||
|
const intervalId = setInterval(fetchMessages, 1000);
|
||
|
|
||
|
return () => clearInterval(intervalId); // Cleanup on component unmount
|
||
|
}, [friendUsername]);
|
||
|
|
||
|
// Send message
|
||
|
const sendMessage = async () => {
|
||
|
if (!inputText.trim() || !friendPubKey) return;
|
||
|
|
||
|
try {
|
||
|
const authToken = localStorage.getItem('jwtToken');
|
||
|
if (!authToken) {
|
||
|
console.error('User is not authenticated');
|
||
|
return;
|
||
|
}
|
||
|
// Prepare the JSON object for encryption
|
||
|
const messageData = JSON.stringify({
|
||
|
message: inputText,
|
||
|
receiver: friendUsername,
|
||
|
pubkey: friendPubKey,
|
||
|
});
|
||
|
|
||
|
// Encrypt the message using chacha20Encrypt
|
||
|
const encryptedPayload = chacha20Encrypt(messageData);
|
||
|
console.log("payload", JSON.parse(encryptedPayload))
|
||
|
const decrypted = chacha20Decrypt(encryptedPayload)
|
||
|
console.log("decrypted:", decrypted)
|
||
|
|
||
|
// Send the encrypted message to the server
|
||
|
const response = await fetch('http://localhost:4000/messages', {
|
||
|
method: 'POST',
|
||
|
headers: {
|
||
|
'Content-Type': 'application/json',
|
||
|
Authorization: `Bearer ${authToken}`,
|
||
|
},
|
||
|
body: encryptedPayload, // Send the encrypted payload
|
||
|
});
|
||
|
|
||
|
if (!response.ok) throw new Error('Failed to send message');
|
||
|
|
||
|
const newMessage = await response.json();
|
||
|
setMessages((prevMessages) => [...prevMessages, newMessage]);
|
||
|
setInputText('');
|
||
|
} catch (err) {
|
||
|
console.error('Error sending message:', err);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
return (
|
||
|
<YStack flex={1} bg="$background" padding={10}>
|
||
|
{/* Chat Header */}
|
||
|
<Text fontSize={20} fontWeight="bold" mb={10}>
|
||
|
Chat with {friendUsername}
|
||
|
</Text>
|
||
|
|
||
|
{/* Messages List */}
|
||
|
<ScrollView flex={1} mb={10}>
|
||
|
{loading ? (
|
||
|
<Text>Loading...</Text>
|
||
|
) : (
|
||
|
messages.map((msg) => (
|
||
|
<MessageBubble
|
||
|
key={msg.id}
|
||
|
text={msg.content}
|
||
|
sender={msg.senderUsername === friendUsername ? 'friend' : 'user'}
|
||
|
/>
|
||
|
))
|
||
|
)}
|
||
|
</ScrollView>
|
||
|
|
||
|
{/* Input Field and Send Button */}
|
||
|
<XStack space={10} alignItems="center">
|
||
|
<Input
|
||
|
flex={1}
|
||
|
value={inputText}
|
||
|
onChangeText={setInputText}
|
||
|
placeholder="Type a message..."
|
||
|
onSubmitEditing={sendMessage}
|
||
|
/>
|
||
|
<Button size="large" onPress={sendMessage}>
|
||
|
Send
|
||
|
</Button>
|
||
|
</XStack>
|
||
|
</YStack>
|
||
|
);
|
||
|
};
|
||
|
|
||
|
const MessageBubble = ({ text, sender }) => {
|
||
|
const isUser = sender === 'user';
|
||
|
return (
|
||
|
<XStack
|
||
|
justifyContent={isUser ? 'flex-end' : 'flex-start'}
|
||
|
paddingVertical={5}
|
||
|
paddingHorizontal={10}
|
||
|
>
|
||
|
<YStack
|
||
|
maxWidth="70%"
|
||
|
padding={10}
|
||
|
borderRadius={10}
|
||
|
backgroundColor={isUser ? '#007aff' : '#e5e5ea'}
|
||
|
>
|
||
|
<Text color={isUser ? '#fff' : '#000'}>{text}</Text>
|
||
|
</YStack>
|
||
|
</XStack>
|
||
|
);
|
||
|
};
|
||
|
|
||
|
export default ChatApp;
|