UNPKG

e2ee-chat

Version:

A simple realtime chat SDK for web and mobile apps using socket.io with support for end-to-end encryption and multi-tenant backend integration.

238 lines (211 loc) 6.76 kB
const { useEffect, useState, useRef } = require("react"); const { connectSocket, sendMessage, requestTakeover, disconnectSocket, markMessagesAsRead, getUnreadCounts, } = require("./socket"); const { encryptMessage, decryptMessage } = require("./encryption"); const { fetchHistory } = require("./api"); function useChat({ serverUrl, roomId, userId, userType, secretKey, apiKey, receiverId, onMessage, }) { const [messages, setMessages] = useState([]); const [joined, setJoined] = useState(false); const [activeHandler, setActiveHandler] = useState(null); const [unreadCount, setUnreadCount] = useState(0); const prevSessionRef = useRef({ roomId: null, userId: null }); // // Function to fetch unread counts // const fetchUnreadCounts = async () => { // try { // const counts = await getUnreadCounts(serverUrl, userId, apiKey); // setUnreadCount(counts[roomId] || 0); // } catch (error) { // console.error("Failed to fetch unread counts:", error); // } // }; // // In the SDK's useChat.js // const fetchUnreadCounts = async () => { // try { // const counts = await getUnreadCounts(serverUrl, userId, apiKey); // // Set the current room's unread count // setUnreadCount(counts[roomId] || 0); // // Return the entire counts object for the component to use // return counts || {}; // } catch (error) { // console.error("Failed to fetch unread counts:", error); // return {}; // } // }; // In useChat.js const fetchUnreadCounts = async () => { try { // Add validation for required parameters if (!serverUrl || !userId || !apiKey) { console.error("SDK: Missing required parameters in useChat:", { serverUrl, userId, apiKey, }); return {}; } console.log("SDK: Fetching unread counts with params:", { serverUrl, userId, apiKey, }); const counts = await getUnreadCounts(serverUrl, userId, apiKey); // Set the current room's unread count if (roomId) { setUnreadCount(counts[roomId] || 0); } return counts || {}; } catch (error) { console.error("SDK: Failed to fetch unread counts:", error); return {}; } }; useEffect(() => { const isFirstLoad = prevSessionRef.current.roomId === null; const hasChanged = prevSessionRef.current.roomId !== roomId || prevSessionRef.current.userId !== userId; const shouldConnect = roomId && userId && secretKey && (isFirstLoad || hasChanged); if (!shouldConnect) return; console.log("Connecting to chat with:", { serverUrl, roomId, userId, userType, hasChanged, isFirstLoad, }); prevSessionRef.current = { roomId, userId }; // Reset state before connecting setMessages([]); setJoined(false); setActiveHandler(null); setUnreadCount(0); // Disconnect any existing socket disconnectSocket(); // Small delay to ensure clean state setTimeout(() => { connectSocket({ serverUrl, roomId, userId, userType, apiKey, onJoin: async ({ activeHandler }) => { console.log("Socket joined successfully:", { activeHandler }); setJoined(true); setActiveHandler(activeHandler); try { const cleanUrl = serverUrl.split("?")[0]; console.log("Fetching history from:", cleanUrl); const history = await fetchHistory(cleanUrl, roomId, apiKey); const messageArray = Array.isArray(history) ? history : []; const decrypted = messageArray.map((msg) => { try { return { ...msg, decryptedText: decryptMessage(msg.encryptedText, secretKey), }; } catch (decryptError) { console.error( "Failed to decrypt message in history:", decryptError ); console.error("Message:", msg); return { ...msg, decryptedText: "[Decryption failed]", decryptionError: true, }; } }); setMessages(decrypted); // Mark messages as read when joining markMessagesAsRead({ roomId, userId, apiKey }); // Fetch initial unread count fetchUnreadCounts(); } catch (error) { console.error("Failed to fetch or decrypt chat history:", error); setMessages([]); } }, onMessage: (msg) => { try { const decryptedText = decryptMessage(msg.encryptedText, secretKey); const messageWithDecryption = { ...msg, decryptedText }; setMessages((prev) => [...prev, messageWithDecryption]); // Call the external onMessage callback if provided if (onMessage) { onMessage(messageWithDecryption); } // Increment unread count if message is from someone else if (msg.senderId !== userId) { setUnreadCount((prev) => prev + 1); } } catch (error) { console.error("Failed to decrypt incoming message:", error); console.error("Message data:", msg); // Still add the message but with an error indicator const messageWithError = { ...msg, decryptedText: "[Decryption failed]", decryptionError: true, }; setMessages((prev) => [...prev, messageWithError]); if (onMessage) { onMessage(messageWithError); } } }, onHandlerChanged: ({ newHandlerId }) => { setActiveHandler(newHandlerId); }, onMessagesRead: () => { setUnreadCount(0); }, }); }, 100); return () => { disconnectSocket(); }; }, [serverUrl, roomId, userId, userType, secretKey, apiKey]); const send = (text, receiverId) => { const encrypted = encryptMessage(text, secretKey); sendMessage({ roomId, senderId: userId, receiverId, encryptedText: encrypted, apiKey, }); }; const takeover = () => { requestTakeover({ roomId, newHandlerId: userId, apiKey }); }; return { messages, sendMessage: send, takeover, activeHandler, joined, unreadCount, markMessagesAsRead: () => markMessagesAsRead({ roomId, userId, apiKey }), fetchUnreadCounts, }; } module.exports = useChat;