@mthahaseen/chatv1
v1.0.0
Published
Private NPM package for chat module with chatroom feature supporting 2-person chat
Maintainers
Readme
@mthahaseen/chatv1
A private NPM package for real-time chat functionality with support for 2-person direct messaging. Built for seamless integration with React Native (mobile) and Next.js (web) applications, powered by Google Firestore and Firebase Storage.
Features
- ✅ Real-time messaging with Firestore snapshot listeners
- ✅ Message types: Text, Image, Video, Location
- ✅ Message status tracking: Sent, Delivered, Read
- ✅ Media handling with Firebase Storage integration
- ✅ Pagination for conversations and messages
- ✅ TypeScript support with comprehensive type definitions
- ✅ Cross-platform compatibility (React Native & Next.js)
- ✅ Security rules for Firestore and Storage
- ✅ Unit tests with Jest
Installation
yarn add @mthahaseen/chatv1 firebaseQuick Start
1. Initialize the ChatService
import { ChatService } from '@mthahaseen/chatv1';
const firebaseConfig = {
apiKey: "your-api-key",
authDomain: "your-project.firebaseapp.com",
projectId: "your-project-id",
storageBucket: "your-project.appspot.com",
messagingSenderId: "123456789",
appId: "your-app-id"
};
const currentUser = {
id: 'user123',
name: 'John Doe',
avatar: 'https://example.com/avatar.jpg' // optional
};
await ChatService.init({
firebaseConfig,
currentUser,
customToken: 'your-firebase-custom-token' // optional
});2. Listen to Conversations
const unsubscribe = ChatService.listenConversations({
pageSize: 20,
onUpdate: (conversations) => {
console.log('Conversations updated:', conversations);
// Update your UI with the conversations list
},
onError: (error) => {
console.error('Error loading conversations:', error);
}
});
// Don't forget to unsubscribe when component unmounts
// unsubscribe();3. Open a Chatroom
const unsubscribe = ChatService.listenMessages({
conversationId: 'conversation-id',
pageSize: 20,
onUpdate: (messages) => {
console.log('Messages updated:', messages);
// Update your UI with the messages
},
onError: (error) => {
console.error('Error loading messages:', error);
}
});4. Send Messages
Text Message
const messageId = await ChatService.sendMessage({
conversationId: 'conversation-id',
content: 'Hello, how are you?'
});Image Message
const messageId = await ChatService.sendMedia({
conversationId: 'conversation-id',
file: imageFile, // File or Blob object
type: 'image'
}, {
onProgress: (progress) => {
console.log(`Upload progress: ${progress.bytesTransferred}/${progress.totalBytes}`);
}
});Video Message
const messageId = await ChatService.sendMedia({
conversationId: 'conversation-id',
file: videoFile, // File or Blob object
type: 'video'
}, {
onProgress: (progress) => {
console.log(`Upload progress: ${progress.bytesTransferred}/${progress.totalBytes}`);
}
});Location Message
const messageId = await ChatService.sendLocation({
conversationId: 'conversation-id',
latitude: 40.7128,
longitude: -74.0060,
address: 'New York, NY' // optional
});5. Mark Messages as Read
await ChatService.markAsRead({
conversationId: 'conversation-id'
});6. Create a New Conversation
const conversationId = await ChatService.createConversation({
participantIds: ['user1', 'user2'],
initialMessage: 'Hello!' // optional
});Data Structures
User
interface User {
id: string;
name: string;
avatar?: string;
}Conversation
interface Conversation {
conversationId: string;
participantIds: [string, string];
lastMessage?: string;
lastMessageAt?: number;
lastMessageStatus?: 'sent' | 'delivered' | 'read';
unreadCount_user1: number;
unreadCount_user2: number;
createdAt: number;
updatedAt: number;
}Message
interface Message {
messageId: string;
conversationId: string;
senderId: string;
receiverId: string;
type: 'text' | 'image' | 'video' | 'location';
content: string;
createdAt: number;
status: 'sent' | 'delivered' | 'read';
// Additional fields for specific message types
fileSize?: number; // for media messages
thumbnailUrl?: string; // for video messages
latitude?: number; // for location messages
longitude?: number; // for location messages
address?: string; // for location messages
}Firestore Structure
The package uses the following Firestore collections:
users/{userId}
conversations/{conversationId}
conversations/{conversationId}/messages/{messageId}Example Documents
Conversation Document
{
"conversationId": "conv_user1_user2",
"participantIds": ["user1", "user2"],
"lastMessage": "Hello!",
"lastMessageAt": 1689273927000,
"lastMessageStatus": "delivered",
"unreadCount_user1": 0,
"unreadCount_user2": 2,
"createdAt": 1689273927000,
"updatedAt": 1689273927000
}Message Document
{
"messageId": "msg_1689273927000_abc123",
"conversationId": "conv_user1_user2",
"senderId": "user1",
"receiverId": "user2",
"type": "text",
"content": "Hello!",
"createdAt": 1689273927000,
"status": "sent"
}Firebase Storage Structure
Media files are stored in the following structure:
chat_media/{conversationId}/images/{userId}/{filename}
chat_media/{conversationId}/videos/{userId}/{filename}
chat_media/{conversationId}/thumbnails/{userId}/{filename}Security Rules
Firestore Rules
Deploy the provided firestore.rules to your Firebase project:
firebase deploy --only firestore:rulesStorage Rules
Deploy the provided storage.rules to your Firebase project:
firebase deploy --only storagePlatform-Specific Integration
React Native
// In your React Native component
import { ChatService } from '@mthahaseen/chatv1';
import { useEffect, useState } from 'react';
export function ChatList() {
const [conversations, setConversations] = useState([]);
useEffect(() => {
const unsubscribe = ChatService.listenConversations({
onUpdate: setConversations,
onError: console.error
});
return unsubscribe;
}, []);
return (
// Your React Native UI
);
}Next.js
// In your Next.js component
import { ChatService } from '@mthahaseen/chatv1';
import { useEffect, useState } from 'react';
export default function ChatPage() {
const [messages, setMessages] = useState([]);
useEffect(() => {
const unsubscribe = ChatService.listenMessages({
conversationId: 'conv-id',
onUpdate: setMessages,
onError: console.error
});
return unsubscribe;
}, []);
return (
// Your Next.js UI
);
}Utility Functions
The package includes helpful utility functions:
import {
formatTimestamp,
formatMessageTime,
getLastMessagePreview,
getMessageStatusIcon,
truncateText,
parseLocationMessage,
formatFileSize,
generateMapsUrl,
calculateDistance
} from '@mthahaseen/chatv1';
// Format timestamp for display
const timeStr = formatTimestamp(message.createdAt);
// Get preview text for last message
const preview = getLastMessagePreview(message);
// Parse location data
const location = parseLocationMessage(locationMessage);
// Generate Google Maps URL
const mapsUrl = generateMapsUrl(latitude, longitude);Error Handling
The package uses a custom ChatServiceError class with specific error codes:
import { ChatServiceError, ErrorCodes } from '@mthahaseen/chatv1';
try {
await ChatService.sendMessage({
conversationId: 'conv-id',
content: 'Hello'
});
} catch (error) {
if (error instanceof ChatServiceError) {
switch (error.code) {
case ErrorCodes.PERMISSION_DENIED:
console.log('Permission denied');
break;
case ErrorCodes.NETWORK_ERROR:
console.log('Network error');
break;
case ErrorCodes.CONVERSATION_NOT_FOUND:
console.log('Conversation not found');
break;
// Handle other error codes...
}
}
}Testing
Run the test suite:
yarn testRun tests in watch mode:
yarn test:watchBuilding
Build the package:
yarn buildBest Practices
- Always unsubscribe from listeners when components unmount
- Handle errors gracefully using the provided error codes
- Implement loading states for better UX during file uploads
- Compress images before uploading for better performance
- Use pagination to avoid loading too many messages at once
- Implement message retry logic for failed sends
Configuration Options
Message Limits
import { MESSAGE_LIMITS } from '@mthahaseen/chatv1';
console.log(MESSAGE_LIMITS.TEXT_MAX_LENGTH); // 4000
console.log(MESSAGE_LIMITS.MEDIA_MAX_SIZE); // 50MB
console.log(MESSAGE_LIMITS.PAGE_SIZE_DEFAULT); // 20Support
For issues and questions, please contact the development team or create an issue in the project repository.
License
This package is proprietary and licensed for internal use only.
