172 lines
4.6 KiB
JavaScript
172 lines
4.6 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import { useSocket } from '../../contexts/SocketContext';
|
|
import MessageList from '../Chat/MessageList';
|
|
import MessageInput from '../Chat/MessageInput';
|
|
import { encryptMessage, decryptMessage } from '../../utils/crypto';
|
|
import { useAuth } from '../../contexts/AuthContext';
|
|
import './ChannelView.css';
|
|
|
|
export default function ChannelView({ channel, projectId }) {
|
|
const { socket } = useSocket();
|
|
const { user } = useAuth();
|
|
const [messages, setMessages] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
if (channel) {
|
|
loadMessages();
|
|
}
|
|
}, [channel]);
|
|
|
|
// Socket listeners
|
|
useEffect(() => {
|
|
if (!socket || !channel) return;
|
|
|
|
const handleNewMessage = async (data) => {
|
|
if (data.conversationId === channel.id) {
|
|
const message = data.message;
|
|
|
|
// Decrypt if not system message
|
|
if (message.contentType !== 'system') {
|
|
try {
|
|
const decrypted = await decryptMessage(
|
|
JSON.parse(message.content),
|
|
user.password
|
|
);
|
|
message.content = decrypted;
|
|
} catch (error) {
|
|
console.error('Failed to decrypt message:', error);
|
|
message.content = '[Failed to decrypt]';
|
|
}
|
|
}
|
|
|
|
setMessages(prev => [message, ...prev]);
|
|
}
|
|
};
|
|
|
|
socket.on('message:new', handleNewMessage);
|
|
|
|
return () => {
|
|
socket.off('message:new', handleNewMessage);
|
|
};
|
|
}, [socket, channel]);
|
|
|
|
const loadMessages = async () => {
|
|
try {
|
|
setLoading(true);
|
|
|
|
const response = await fetch(`/api/conversations/${channel.id}/messages`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
|
}
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
// Decrypt messages
|
|
const decryptedMessages = await Promise.all(
|
|
data.messages.map(async (msg) => {
|
|
// System messages are not encrypted
|
|
if (msg.contentType === 'system') {
|
|
return msg;
|
|
}
|
|
|
|
try {
|
|
const decrypted = await decryptMessage(
|
|
JSON.parse(msg.content),
|
|
user.password
|
|
);
|
|
return {
|
|
...msg,
|
|
content: decrypted
|
|
};
|
|
} catch (error) {
|
|
console.error('Failed to decrypt message:', error);
|
|
return {
|
|
...msg,
|
|
content: '[Failed to decrypt]'
|
|
};
|
|
}
|
|
})
|
|
);
|
|
|
|
setMessages(decryptedMessages);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Failed to load messages:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const sendMessage = async (content) => {
|
|
if (!content.trim()) return;
|
|
|
|
try {
|
|
// Get recipient public keys (all channel participants)
|
|
const participantsResponse = await fetch(`/api/conversations/${channel.id}`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
|
}
|
|
});
|
|
|
|
const participantsData = await participantsResponse.json();
|
|
|
|
if (!participantsData.success) {
|
|
throw new Error('Failed to get participants');
|
|
}
|
|
|
|
const recipientKeys = participantsData.conversation.participants
|
|
.map(p => p.publicKey)
|
|
.filter(Boolean);
|
|
|
|
// Encrypt message
|
|
const encrypted = await encryptMessage(content, recipientKeys);
|
|
|
|
// Send via WebSocket
|
|
socket.emit('message:send', {
|
|
conversationId: channel.id,
|
|
content: JSON.stringify(encrypted),
|
|
contentType: 'text',
|
|
clientId: `temp-${Date.now()}`
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Failed to send message:', error);
|
|
}
|
|
};
|
|
|
|
if (loading) {
|
|
return <div className="channel-view loading">Loading messages...</div>;
|
|
}
|
|
|
|
return (
|
|
<div className="channel-view">
|
|
<div className="channel-header">
|
|
<h3>#{channel.name}</h3>
|
|
<p className="channel-description">{channel.description}</p>
|
|
{channel.permissions && !channel.permissions.includes('all') && (
|
|
<span className="restricted-badge">
|
|
Restricted to: {channel.permissions.join(', ')}
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
<MessageList
|
|
messages={messages}
|
|
currentUserId={user.id}
|
|
typingUsers={new Set()}
|
|
showSystemMessages={true}
|
|
/>
|
|
|
|
<MessageInput
|
|
onSend={sendMessage}
|
|
onTyping={() => {}}
|
|
onStopTyping={() => {}}
|
|
placeholder={`Message #${channel.name}`}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|