UNPKG

chatstorm-client

Version:

A real-time chat client built using Socket.IO for seamless communication. Compatible with both React and React Native. This client handles connection, message exchange, event-driven updates, and user session management in a modular and extensible structur

872 lines (720 loc) 22.8 kB
# ChatStorm Client A powerful React hook for real-time chat functionality using Socket.IO. ChatStorm Client provides an easy-to-use interface for integrating socket-based messaging into your **React** and **React Native** applications with comprehensive event handling and callback systems. ## 🚀 Features - **Real-time Communication**: Instant message delivery and updates - **Socket.IO Integration**: Robust WebSocket connection management - **Event-driven Architecture**: Comprehensive callback system for all events - **Typing Indicators**: Real-time typing status notifications - **Message History**: Retrieve and search past conversations - **Chat Management**: Join chats, get chat lists, and manage conversations - **Message Operations**: Send, delete, and update messages (supports text, links, and media) - **Online Status**: Check if users are online in real-time - **Error Handling**: Built-in error notification system - **TypeScript Support**: Full TypeScript definitions included - **Auto-reconnection**: Automatic connection handling and cleanup ## 📦 Installation Install the package via npm: ```bash npm install chatstorm-client ``` ### Peer Dependencies Make sure you have the following dependencies installed: **For React:** ```bash npm install react react-dom socket.io-client ``` **For React Native:** ```bash npm install react socket.io-client # react-dom is not needed for React Native ``` ## 🏃‍♂️ Quick Start ### React Native Setup For React Native projects, you may need to install additional polyfills for Socket.IO: ```bash npm install react-native-get-random-values ``` Then import it at the top of your entry file (usually `index.js` or `App.js`): ```js import 'react-native-get-random-values'; ``` ### Basic Setup ```tsx import React from 'react'; import useChatSocket from 'chatstorm-client'; const ChatComponent = () => { const BACKEND_SOCKET_URL = 'ws://localhost:3001'; const MONGO_USER_ID = 'your-user-id-here'; const { messages, sendMessage, joinChat, getChatList, retrieveMessages, updateTypingAlert, deleteMessage, leaveChat, checkOnlineStatus, disconnectUser, setHandshakeSuccessCallback, setMessageReceivedCallback, setChatListCallback, setRetrieveMessagesCallback, setMessageSentCallback, setMessageUpdateCallback, setReceiverMessageUpdateCallback, setTypingAlertCallback, setOnLeaveCallback, setChatStatusCallback, setOnCheckOnlineStatus, setOnErrorNotify, setOnDisconnect, socketInstance, } = useChatSocket(BACKEND_SOCKET_URL, MONGO_USER_ID); // Set up event callbacks React.useEffect(() => { setHandshakeSuccessCallback((data) => { console.log('Connected successfully:', data); }); setMessageReceivedCallback((message) => { console.log('New message received:', message); }); setChatListCallback((chatList) => { console.log('Chat list updated:', chatList); }); setRetrieveMessagesCallback((messages) => { console.log('Messages retrieved:', messages); }); setOnCheckOnlineStatus((data) => { console.log('Online status:', data); }); setOnErrorNotify((error) => { console.error('Error notification:', error); }); setOnDisconnect((data) => { console.log('Disconnected:', data); }); setChatStatusCallback((data) => { if (data.isJoined) { console.log('User joined chat:', data.data); } else if (data.isLeft) { console.log('User left chat'); } }); }, []); return ( <div> <h1>Chat Application</h1> {/* Your chat UI components here */} </div> ); }; ``` ## 📚 API Reference ### Hook Parameters ```tsx useChatSocket(serverUrl: string, userId: string) ``` - `serverUrl`: WebSocket server URL (e.g., 'ws://localhost:3001') - `userId`: Unique identifier for the current user ### Returned Functions #### Core Functions | Function | Parameters | Description | |----------|------------|-------------| | `sendMessage` | `{ receiverId: string, message: { text: string, link: string, media: string } }` | Send a message to a specific user (supports text, link, and media) | | `joinChat` | `{ receiverId: string }` | Join a chat with another user | | `getChatList` | `{ keyword?: string }` | Retrieve list of available chats (keyword is optional) | | `retrieveMessages` | `{ receiverId: string, keyword?: string }` | Get message history with a user (keyword is optional, automatically marks messages as seen) | | `updateTypingAlert` | `{ receiverId: string, isTyping: boolean }` | Send typing status | | `deleteMessage` | `{ messageId: string }` | Delete a specific message | | `leaveChat` | `{ receiverId: string }` | Leave a chat conversation | | `checkOnlineStatus` | `{ receiverId: string }` | Check if a user is online | | `disconnectUser` | `()` | Manually disconnect the user from the socket | #### Callback Setters | Function | Description | |----------|-------------| | `setHandshakeSuccessCallback` | Called when connection is established | | `setMessageReceivedCallback` | Called when a new message is received | | `setMessageSentCallback` | Called when a message is sent successfully | | `setChatListCallback` | Called when chat list is updated | | `setRetrieveMessagesCallback` | Called when messages are retrieved | | `setMessageUpdateCallback` | Called when a message is updated | | `setReceiverMessageUpdateCallback` | Called when receiver updates a message | | `setTypingAlertCallback` | Called when typing status is received | | `setOnLeaveCallback` | Called when a user leaves the chat | | `setChatStatusCallback` | Called when chat status changes (user joined/left) | | `setOnCheckOnlineStatus` | Called when online status is received | | `setOnErrorNotify` | Called when an error notification is received | | `setOnDisconnect` | Called when the socket connection is disconnected | ### State - `messages`: Array of current messages in the chat ## 💡 Use Cases ### 1. Private Messaging App ```tsx import React, { useState } from 'react'; import useChatSocket from 'chatstorm-client'; const PrivateChat = () => { const [currentChat, setCurrentChat] = useState(null); const [messageInput, setMessageInput] = useState(''); const [chatList, setChatList] = useState([]); const { messages, sendMessage, joinChat, getChatList, retrieveMessages, setMessageReceivedCallback, setChatListCallback, setRetrieveMessagesCallback, } = useChatSocket('ws://localhost:3001', 'user123'); React.useEffect(() => { // Load chat list on component mount getChatList({ keyword: '' }); setChatListCallback((data) => { setChatList(data.chats || []); }); setMessageReceivedCallback((message) => { console.log('New message:', message); }); setRetrieveMessagesCallback((data) => { console.log('Messages loaded:', data); }); }, []); const handleSendMessage = () => { if (messageInput.trim() && currentChat) { sendMessage({ receiverId: currentChat.id, message: { text: messageInput, link: '', media: '', }, }); setMessageInput(''); } }; const handleSelectChat = (chat) => { setCurrentChat(chat); joinChat({ receiverId: chat.id }); retrieveMessages({ receiverId: chat.id, keyword: '' }); }; return ( <div style={{ display: 'flex', height: '100vh' }}> {/* Chat List Sidebar */} <div style={{ width: '300px', borderRight: '1px solid #ccc' }}> <h3>Chats</h3> {chatList.map((chat) => ( <div key={chat.id} onClick={() => handleSelectChat(chat)} style={{ padding: '10px', cursor: 'pointer', backgroundColor: currentChat?.id === chat.id ? '#f0f0f0' : 'white', }} > {chat.name} </div> ))} </div> {/* Chat Area */} <div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}> {currentChat ? ( <> <div style={{ padding: '10px', borderBottom: '1px solid #ccc' }}> <h3>{currentChat.name}</h3> </div> {/* Messages */} <div style={{ flex: 1, padding: '10px', overflowY: 'auto' }}> {messages.map((message, index) => ( <div key={index} style={{ marginBottom: '10px' }}> <strong>{message.sender}:</strong> {message.content} </div> ))} </div> {/* Message Input */} <div style={{ padding: '10px', borderTop: '1px solid #ccc' }}> <input type="text" value={messageInput} onChange={(e) => setMessageInput(e.target.value)} onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()} placeholder="Type a message..." style={{ width: '100%', padding: '8px' }} /> <button onClick={handleSendMessage}>Send</button> </div> </> ) : ( <div style={{ padding: '20px', textAlign: 'center' }}> Select a chat to start messaging </div> )} </div> </div> ); }; ``` ### 2. Customer Support Chat ```tsx import React, { useState, useEffect } from 'react'; import useChatSocket from 'chatstorm-client'; const CustomerSupport = () => { const [isTyping, setIsTyping] = useState(false); const [supportAgent, setSupportAgent] = useState(null); const { messages, sendMessage, joinChat, updateTypingAlert, setMessageReceivedCallback, setHandshakeSuccessCallback, setTypingAlertCallback, } = useChatSocket('ws://support.example.com', 'customer123'); useEffect(() => { // Connect to support agent joinChat({ receiverId: 'support-agent-001' }); setHandshakeSuccessCallback((data) => { console.log('Connected to support:', data); setSupportAgent(data.agent); }); setMessageReceivedCallback((message) => { console.log('Support message:', message); }); setTypingAlertCallback((data) => { setIsTyping(data.isTyping); }); }, []); const handleTyping = (isTyping) => { updateTypingAlert({ receiverId: 'support-agent-001', isTyping, }); }; return ( <div> <h2>Customer Support</h2> {supportAgent && ( <p>Connected to: {supportAgent.name}</p> )} <div> {messages.map((message, index) => ( <div key={index}> <strong>{message.sender}:</strong> {message.content} </div> ))} </div> {isTyping && ( <p>Support agent is typing...</p> )} <input type="text" onFocus={() => handleTyping(true)} onBlur={() => handleTyping(false)} onKeyPress={(e) => { if (e.key === 'Enter') { sendMessage({ receiverId: 'support-agent-001', message: { text: e.target.value, link: '', media: '', }, }); e.target.value = ''; } }} placeholder="Type your message..." /> </div> ); }; ``` ### 3. Group Chat Application ```tsx import React, { useState } from 'react'; import useChatSocket from 'chatstorm-client'; const GroupChat = () => { const [groupMembers, setGroupMembers] = useState([]); const [messageInput, setMessageInput] = useState(''); const { messages, sendMessage, joinChat, getChatList, setMessageReceivedCallback, setChatListCallback, } = useChatSocket('ws://localhost:3001', 'user123'); React.useEffect(() => { // Get group chats getChatList({ keyword: 'group' }); setChatListCallback((data) => { setGroupMembers(data.chats || []); }); setMessageReceivedCallback((message) => { console.log('Group message:', message); }); }, []); const handleSendGroupMessage = () => { if (messageInput.trim()) { // Send to all group members groupMembers.forEach(member => { sendMessage({ receiverId: member.id, message: { text: messageInput, link: '', media: '', }, }); }); setMessageInput(''); } }; return ( <div> <h2>Group Chat</h2> <div> <h3>Members ({groupMembers.length})</h3> {groupMembers.map(member => ( <span key={member.id}>{member.name}, </span> ))} </div> <div> {messages.map((message, index) => ( <div key={index}> <strong>{message.sender}:</strong> {message.content} </div> ))} </div> <input type="text" value={messageInput} onChange={(e) => setMessageInput(e.target.value)} onKeyPress={(e) => e.key === 'Enter' && handleSendGroupMessage()} placeholder="Type a group message..." /> <button onClick={handleSendGroupMessage}>Send</button> </div> ); }; ``` ## 📱 React Native Example Here's a complete example for React Native: ```tsx import React, { useState, useEffect } from 'react'; import { View, Text, TextInput, TouchableOpacity, FlatList, StyleSheet } from 'react-native'; import useChatSocket from 'chatstorm-client'; const ChatScreen = () => { const [messageInput, setMessageInput] = useState(''); const [chatMessages, setChatMessages] = useState([]); const BACKEND_SOCKET_URL = 'ws://your-server.com'; const USER_ID = 'your-user-id'; const { messages, sendMessage, joinChat, setMessageReceivedCallback, setHandshakeSuccessCallback, } = useChatSocket(BACKEND_SOCKET_URL, USER_ID); useEffect(() => { setHandshakeSuccessCallback((data) => { console.log('Connected:', data); // Join a chat after connection joinChat({ receiverId: 'target-user-id' }); }); setMessageReceivedCallback((message) => { setChatMessages((prev) => [...prev, message]); }); }, []); const handleSend = () => { if (messageInput.trim()) { sendMessage({ receiverId: 'target-user-id', message: { text: messageInput, link: '', media: '', }, }); setMessageInput(''); } }; return ( <View style={styles.container}> <FlatList data={chatMessages} keyExtractor={(item, index) => index.toString()} renderItem={({ item }) => ( <View style={styles.messageContainer}> <Text style={styles.messageText}>{item.message}</Text> </View> )} /> <View style={styles.inputContainer}> <TextInput style={styles.input} value={messageInput} onChangeText={setMessageInput} placeholder="Type a message..." /> <TouchableOpacity onPress={handleSend} style={styles.button}> <Text style={styles.buttonText}>Send</Text> </TouchableOpacity> </View> </View> ); }; const styles = StyleSheet.create({ container: { flex: 1, padding: 10, }, messageContainer: { padding: 10, marginVertical: 5, backgroundColor: '#f0f0f0', borderRadius: 5, }, messageText: { fontSize: 16, }, inputContainer: { flexDirection: 'row', padding: 10, }, input: { flex: 1, borderWidth: 1, borderColor: '#ccc', borderRadius: 5, padding: 10, marginRight: 10, }, button: { backgroundColor: '#007bff', padding: 10, borderRadius: 5, justifyContent: 'center', }, buttonText: { color: 'white', fontWeight: 'bold', }, }); export default ChatScreen; ``` ## 🔧 Advanced Configuration ### Custom Event Handling ```tsx const AdvancedChat = () => { const { setMessageUpdateCallback, setReceiverMessageUpdateCallback, setOnLeaveCallback, setOnCheckOnlineStatus, setOnErrorNotify, setOnDisconnect, deleteMessage, checkOnlineStatus, disconnectUser, } = useChatSocket('ws://localhost:3001', 'user123'); React.useEffect(() => { // Handle message updates setMessageUpdateCallback((data) => { console.log('Message updated:', data); // Update UI to show edited message }); setReceiverMessageUpdateCallback((data) => { console.log('Receiver updated message:', data); // Handle when other user edits their message }); setOnLeaveCallback((data) => { console.log('User left:', data); // Handle user leaving the chat }); setOnCheckOnlineStatus((data) => { console.log('User online status:', data); // Handle online status updates }); setOnErrorNotify((error) => { console.error('Error occurred:', error); // Handle error notifications }); setOnDisconnect((data) => { console.log('Socket disconnected:', data); // Handle disconnection (e.g., show reconnection UI) }); }, []); const handleDeleteMessage = (messageId) => { deleteMessage({ messageId }); }; const handleCheckOnline = (receiverId) => { checkOnlineStatus({ receiverId }); }; const handleDisconnect = () => { disconnectUser(); }; return ( <div> {/* Your chat UI with delete functionality */} </div> ); }; ``` ## 🐛 Troubleshooting ### Common Issues 1. **Connection Failed** ```tsx // Ensure your server URL is correct and accessible const serverUrl = 'ws://localhost:3001'; // or 'wss://' for secure connections ``` ### React Native Specific Issues 1. **Socket.IO Connection Issues in React Native** - Make sure you've installed `react-native-get-random-values` and imported it at the top of your entry file - For Android, ensure you have internet permissions in `AndroidManifest.xml`: ```xml <uses-permission android:name="android.permission.INTERNET" /> ``` - For iOS, ensure your server URL uses `http://` or `https://` instead of `ws://` or `wss://` in some cases - The package automatically configures transports for React Native compatibility 2. **Metro Bundler Issues** - If you encounter module resolution errors, try clearing the Metro cache: ```bash npx react-native start --reset-cache ``` 3. **Network Requests Blocked** - For Android 9+, you may need to configure network security: - Add `android:usesCleartextTraffic="true"` to `AndroidManifest.xml` for HTTP connections - Or use HTTPS/WSS for production 2. **Messages Not Received** ```tsx // Make sure to set up callbacks before sending messages useEffect(() => { setMessageReceivedCallback((message) => { console.log('Message received:', message); }); }, []); ``` 3. **Typing Indicators Not Working** ```tsx // Ensure you're calling updateTypingAlert with correct parameters updateTypingAlert({ receiverId: 'target-user-id', isTyping: true, // or false }); ``` ### Debug Mode Enable debug logging by checking the browser console for socket events: ```tsx useEffect(() => { setHandshakeSuccessCallback((data) => { console.log('🔗 Connection established:', data); }); setMessageReceivedCallback((message) => { console.log('📨 Message received:', message); }); setChatListCallback((chatList) => { console.log('💬 Chat list updated:', chatList); }); setOnCheckOnlineStatus((data) => { console.log('🟢 Online status:', data); }); setOnErrorNotify((error) => { console.error('❌ Error notification:', error); }); setOnDisconnect((data) => { console.log('🔌 Disconnected:', data); }); }, []); ``` ### Chat Status Example Handle chat join/leave notifications: ```tsx const ChatWithStatus = () => { const { joinChat, leaveChat, setChatStatusCallback } = useChatSocket('ws://localhost:3001', 'user123'); useEffect(() => { setChatStatusCallback((data) => { if (data.isJoined) { console.log('User joined:', data.data); // Show notification: "User has joined the chat" } else if (data.isLeft) { console.log('User left the chat'); // Show notification: "User has left the chat" } }); }, []); const handleJoinChat = (receiverId) => { joinChat({ receiverId }); }; const handleLeaveChat = (receiverId) => { leaveChat({ receiverId }); }; return ( <div> <button onClick={() => handleJoinChat('target-user-id')}>Join Chat</button> <button onClick={() => handleLeaveChat('target-user-id')}>Leave Chat</button> </div> ); }; ``` ### Online Status Example Check if a user is online: ```tsx const ChatWithOnlineStatus = () => { const [isOnline, setIsOnline] = useState(false); const { checkOnlineStatus, setOnCheckOnlineStatus } = useChatSocket('ws://localhost:3001', 'user123'); useEffect(() => { setOnCheckOnlineStatus((data) => { setIsOnline(data.isOnline || false); }); }, []); const handleCheckStatus = (receiverId) => { checkOnlineStatus({ receiverId }); }; return ( <div> <button onClick={() => handleCheckStatus('target-user-id')}> Check Online Status </button> {isOnline ? <span>🟢 Online</span> : <span>🔴 Offline</span>} </div> ); }; ``` ### Disconnect Example Manually disconnect from the socket: ```tsx const ChatWithDisconnect = () => { const { disconnectUser, setOnDisconnect } = useChatSocket('ws://localhost:3001', 'user123'); useEffect(() => { setOnDisconnect((data) => { console.log('Disconnected from server:', data); // Handle disconnection (e.g., show offline message, clear UI) }); }, []); const handleLogout = () => { // Disconnect before logging out disconnectUser(); // Additional logout logic here }; return ( <div> <button onClick={handleLogout}>Logout</button> </div> ); }; ``` ## 📄 License ISC License - see package.json for details. ## 🤝 Contributing Contributions are welcome! Please feel free to submit a Pull Request. ## 📞 Support For support and questions, please open an issue on the GitHub repository. --- ## 🔽 Download Examples (ZIP) - **React Web Example ZIP** 👉 https://github.com/Vikas-Rajput-JS/Chatstorm-client/raw/refs/heads/master/test/react-web.zip - **React Native Example ZIP** 👉 https://github.com/Vikas-Rajput-JS/Chatstorm-client/raw/refs/heads/master/test/native.zip **Made with ❤️ by Vikas Rajput**