@maleeshagimshan98/infinite-messenger-core
v3.0.1
Published
realtime messenging library built with Google Firebase/MongoDB, for Node.js servers
Maintainers
Readme
Messenger Core v2.0
Introduction
Messenger Core is a production-ready, real-time messaging library built with Firebase for Node.js servers. It provides a complete abstraction layer for building chat applications with minimal boilerplate code.
Why Messenger Core?
Building a messaging system from scratch is complex:
- 📊 Database schema design for users, conversations, and messages
- ⚡ Real-time synchronization across multiple clients
- 🔄 Pagination and efficient data retrieval
- 🏗️ Scalable architecture that supports growth
Messenger Core solves these challenges by providing:
- ✅ Clean, layered architecture with separation of concerns
- ✅ Real-time updates out of the box via Firebase listeners
- ✅ Type-safe API with full TypeScript support
- ✅ Database abstraction - swap implementations without code changes
- ✅ Production-tested with comprehensive test coverage
What You Can Build
- 💬 One-on-one chat applications
- 👥 Group messaging platforms
- 📱 Mobile backend APIs
- 🎮 In-game chat systems
- 💼 Customer support portals
Installation
npm install @maleeshagimshan98/infinite-messenger-corePrerequisites
- Node.js 14.x or higher
- Firebase Project with Firestore enabled
- Service Account Key (JSON file) from Firebase Console
Quick Start
1. Set Up Firebase
- Create a Firebase project at Firebase Console
- Enable Firestore Database
- Generate a service account key:
- Go to Project Settings → Service Accounts
- Click "Generate New Private Key"
- Save the JSON file securely
2. Initialize the Library
import { MessengerCore } from '@maleeshagimshan98/infinite-messenger-core';
import { User, Conversation, Message } from '@maleeshagimshan98/infinite-messenger-core';
// Initialize with Firebase configuration
const messenger = new MessengerCore({
dbDriver: 'firebase',
dbConfig: './path/to/firebase-key.json',
});3. Initialize User Session (Required)
⚠️ Important: You MUST call
initialize()before performing any operations with the library. If the user doesn't exist, an error will be thrown - you must handle this appropriately.
try {
// Initialize user session - REQUIRED before any other operations
await messenger.initialize('user123');
// If successful, you can now use the library
const currentUser = messenger.getUser();
console.log('Active user:', currentUser.getName());
} catch (error) {
if (error.message.includes('User not found')) {
console.log('User does not exist. Creating new user...');
// Create a new user
const newUser = await messenger.newUser({
id: 'user123',
name: 'John Doe',
profileImg: 'https://example.com/avatar.jpg',
lastSeen: new Date().toISOString(),
permissions: ['read', 'write'],
conversationsId: 'conv_user123', // Auto-generated if not provided
});
console.log('User created:', newUser.getName());
// Now initialize with the newly created user
await messenger.initialize('user123');
} else {
console.error('Initialization failed:', error);
throw error;
}
}4. Alternative: Create User First (If You Know User Doesn't Exist)
// If you're setting up a new user for the first time
const newUser = await messenger.newUser({
id: 'user456',
name: 'Jane Smith',
profileImg: 'https://example.com/avatar.jpg',
lastSeen: new Date().toISOString(),
permissions: ['read', 'write'],
conversationsId: 'conv_user456',
});
// Then initialize the session
await messenger.initialize('user456');5. Send Your First Message
// Load conversations (after successful initialization)
await messenger.initConversations();
// Start a new conversation
const conversation = new Conversation({
id: 'conv_' + Date.now(),
participants: ['user123', 'user456'],
});
await messenger.conversationService().startConversation(messenger.getUser(), conversation);
// Send a message
const message = new Message({
senderId: 'user123',
content: 'Hello, World! 👋',
});
await messenger.messageService().sendMessage(conversation, message);
console.log('Message sent successfully!');Important: Initialization Workflow
Every session MUST follow this pattern:
- Create
MessengerCoreinstance with database configuration - Call
initialize(userId)- This is MANDATORY before any operations- ✅ If user exists: Session starts successfully
- ❌ If user doesn't exist: Error is thrown - handle by creating the user first
- Perform operations - Only after successful initialization
// Recommended pattern
const messenger = new MessengerCore({ dbDriver: 'firebase', dbConfig: './key.json' });
try {
await messenger.initialize('user123');
// ✅ User exists - proceed with operations
await messenger.initConversations();
// ... rest of your code
} catch (error) {
if (error.message.includes('User not found')) {
// ❌ User doesn't exist - create first, then initialize
await messenger.newUser({ id: 'user123', name: 'New User' /* ... */ });
await messenger.initialize('user123');
}
}Features
🔐 User Management
Create, retrieve, update, and manage users with ease.
Create a User
const user = await messenger.newUser({
id: 'user789',
name: 'Jane Smith',
profileImg: 'https://example.com/jane.jpg',
lastSeen: new Date().toISOString(),
permissions: ['read', 'write', 'admin'],
conversationsId: 'conv_user789',
});Get User Information
const userService = messenger.userService();
const user = await userService.getUser('user789');
console.log('User name:', user.getName());
console.log('Last seen:', user.getLastSeen());
console.log('Is active:', user.getIsActive());Update User Profile
const user = messenger.getUser();
user.setName('Jane Doe');
user.setProfileImg('https://example.com/new-avatar.jpg');
await messenger.updateUser(user);💬 Conversation Management
Manage conversations between users with real-time synchronization.
Start a New Conversation
const conversationService = messenger.conversationService();
const conversation = new Conversation({
id: 'conv_' + Date.now(),
participants: ['user123', 'user456', 'user789'], // Supports multiple participants
});
await conversationService.startConversation(messenger.getUser(), conversation);Retrieve User's Conversations
// Get all conversations
await messenger.initConversations();
const conversations = conversationService.getConversations();
// Iterate through conversations
Object.values(conversations).forEach((conv) => {
console.log('Conversation ID:', conv.getId());
console.log('Participants:', conv.getParticipants());
console.log('Last updated:', conv.getLastUpdatedTime());
});Listen for New Conversations (Real-time)
await conversationService.listenToConversations((conversationsData) => {
if (conversationsData.hasData()) {
const newConversations = conversationsData.data();
newConversations.forEach((conv) => {
console.log('New conversation:', conv.getId());
console.log('With:', conv.getParticipants());
});
}
});Delete a Conversation
conversationService.deleteConversation(conversation);Stop Listening
// Clean up listeners to prevent memory leaks
conversationService.detachListener();📨 Message Management
Send, retrieve, and listen to messages with real-time updates.
Send a Message
const messageService = messenger.messageService();
const message = new Message({
senderId: 'user123',
content: 'Hey! How are you doing?',
timestamp: Date.now(),
});
await messageService.sendMessage(conversation, message);Retrieve Messages
const messagesResult = await messageService.getMessages(conversation);
if (messagesResult.hasData()) {
const messages = messagesResult.data();
messages.forEach((msg) => {
console.log(`${msg.getSenderId()}: ${msg.getContent()}`);
console.log(`Sent at: ${msg.getTime()}`);
});
}Listen for New Messages (Real-time)
messageService.listen(conversation, (messagesData) => {
if (messagesData.hasData()) {
const newMessages = messagesData.data();
newMessages.forEach((msg) => {
console.log(`📩 New message from ${msg.getSenderId()}`);
console.log(`Content: ${msg.getContent()}`);
});
}
});Delete a Message
await messageService.deleteMessage(conversation, 'message-id-123');Stop Listening to Messages
messageService.detachListener(conversation);⚡ Real-time Updates
The library provides built-in real-time synchronization using Firebase's snapshot listeners.
Complete Real-time Example
// Start listening to conversations
await conversationService.listenToConversations((conversations) => {
console.log('Conversations updated!');
});
// Start listening to messages in a specific conversation
messageService.listen(conversation, (messages) => {
console.log('New messages received!');
if (messages.hasData()) {
const messageList = messages.data();
// Update UI with new messages
updateChatUI(messageList);
}
});
// When done, clean up
conversationService.detachListener();
messageService.detachListener(conversation);📄 Pagination Support
Efficiently load large datasets with built-in pagination.
// Get first batch of messages (default limit: 25)
const firstBatch = await messageService.getMessages(conversation);
// Get next batch using the last message ID
const messages = firstBatch.data();
const lastMessageId = messages[messages.length - 1].getId();
const nextBatch = await messageService.getMessages(conversation, lastMessageId);Documentation
API Reference
For detailed API documentation, see:
UserService API
| Method | Parameters | Returns | Description |
| ------------------ | ---------------- | ---------------------------- | ----------------------- |
| getUser(userId) | userId: string | Promise<User \| undefined> | Fetch a user by ID |
| newUser(user) | user: NewUser | Promise<User> | Create a new user |
| updateUser(user) | user: User | Promise<User> | Update user information |
| deleteUser(user) | user: User | Promise<void> | Delete a user |
ConversationService API
| Method | Parameters | Returns | Description |
| --------------------------------------- | --------------------------------------------- | --------------------------------------- | ------------------------------- |
| getConversations() | None | Promise<Record<string, Conversation>> | Get all conversations |
| startConversation(user, conversation) | user: Userconversation: Conversation | Promise<Conversation> | Start a new conversation |
| listenToConversations(callback) | callback: Function | Promise<void> | Listen for conversation updates |
| deleteConversation(conversation) | conversation: Conversation | void | Delete a conversation |
| detachListener() | None | void | Stop listening |
MessageService API
| Method | Parameters | Returns | Description |
| ---------------------------------------- | ----------------------------------------------------- | --------------------------------------- | ---------------------------- |
| getMessages(conversation, start?) | conversation: Conversationstart?: string | Promise<DatabaseResultSet<Message[]>> | Get messages with pagination |
| sendMessage(conversation, message) | conversation: Conversationmessage: Message | Promise<void> | Send a message |
| listen(conversation, callback) | conversation: Conversationcallback: Function | void | Listen for new messages |
| deleteMessage(conversation, messageId) | conversation: ConversationmessageId: string | Promise<void> | Delete a message |
| detachListener(conversation) | conversation: Conversation | void | Stop listening |
Data Models
Data Models
User
Represents a user in the messaging system.
type NewUser = {
id: string; // Unique user identifier
name: string; // Display name
profileImg: string; // Profile image URL
lastSeen: string; // Last activity timestamp
permissions: string[]; // User permissions/roles
conversationsId: string; // Unique conversation collection ID (format: conv_{userId})
};Key Methods:
getId()- Get user IDgetName()- Get user namegetIsActive()- Check if user is onlinegetConversationsId()- Get conversation collection IDsetIsActive(status)- Set online/offline statustoObj()- Convert to plain object for storage
Conversation
Represents a conversation/thread between participants.
interface NewConversation {
id: string; // Unique conversation identifier
participants: string[]; // Array of participant user IDs
startedDate?: string; // When conversation was created
lastUpdatedTime?: string; // Last activity timestamp
lastMessageId?: string; // ID of most recent message
timestamp?: number; // Unix timestamp for sorting
messages?: Record<string, Message>; // Cached messages
}Key Methods:
getId()- Get conversation IDgetParticipants()- Get participant IDsgetMessages()- Get cached messagessetMessage(message)- Add a messagedeleteMessage(messageId)- Remove a messagetoObj()- Convert to plain object for storage
Message
Represents a single message in a conversation.
type NewMessage = {
id?: string; // Unique message identifier (auto-generated if not provided)
senderId: string; // ID of sender
content?: string; // Message text
time?: string; // Human-readable timestamp
timestamp?: number; // Unix timestamp for ordering
};Key Methods:
getId()- Get message IDgetSenderId()- Get sender IDgetContent()- Get message textgetTime()- Get formatted timesetContent(message)- Update contenttoObj()- Convert to plain object for storage
Architecture
Overview
Messenger Core follows a clean, layered architecture with clear separation of concerns:
┌─────────────────────────────────┐
│ MessengerCore (Facade) │ ← Entry point
└────────────┬────────────────────┘
│
┌────────┴────────┐
│ Services │ ← Business logic
│ (User, Conv, │
│ Message) │
└────────┬────────┘
│
┌────────┴────────┐
│ Datastore │ ← Database abstraction
│ (Interface) │
└────────┬────────┘
│
┌────────┴────────┐
│ Firebase Impl │ ← Firebase repositories
└─────────────────┘Key Design Patterns
- Facade Pattern:
MessengerCoreprovides a simplified interface to complex subsystems - Repository Pattern: Data access abstracted through repository interfaces
- Service Layer: Business logic separated into dedicated service classes
- Strategy Pattern: Database implementations are pluggable via interfaces
- Observer Pattern: Real-time listeners notify subscribers of data changes
Data Structure in Firebase
Firestore Database
│
├── users/ # User collection
│ └── {userId}/ # User document
│
├── conv_{userId}/ # Each user's conversations
│ └── {conversationId}/ # Conversation document
│
└── {conversationId}/ # Messages for each conversation
└── {messageId}/ # Message documentKey Design Decisions:
- Conversation Duplication: Each conversation is stored in all participants' collections for efficient querying
- Separate Message Collections: Messages are stored in collections named by conversationId for scalability
- User-specific Collection IDs: Format
conv_{userId}ensures data isolation
For In-Depth Understanding
For a comprehensive architectural overview, see LIBRARY_ARCHITECTURE.md, which includes:
- Detailed component breakdown
- Complete execution paths
- Data flow diagrams
- Implementation details
- Future enhancement roadmap
Examples
Complete Chat Application
import { MessengerCore, User, Conversation, Message } from '@maleeshagimshan98/infinite-messenger-core';
// Initialize
const messenger = new MessengerCore({
dbDriver: 'firebase',
dbConfig: './firebase-key.json',
});
// Initialize user session (REQUIRED - must be called first)
try {
await messenger.initialize('user123');
} catch (error) {
if (error.message.includes('User not found')) {
// Create user if doesn't exist
await messenger.newUser({
id: 'user123',
name: 'John Doe',
profileImg: '',
lastSeen: new Date().toISOString(),
permissions: ['read', 'write'],
conversationsId: 'conv_user123',
});
await messenger.initialize('user123');
} else {
throw error;
}
}
// Get services
const conversationService = messenger.conversationService();
const messageService = messenger.messageService();
// Load conversations
await messenger.initConversations();
// Listen for new conversations
await conversationService.listenToConversations((conversations) => {
console.log('Conversations updated!');
});
// Start a conversation
const conversation = new Conversation({
id: 'conv_' + Date.now(),
participants: ['user123', 'user456'],
});
await conversationService.startConversation(messenger.getUser(), conversation);
// Listen for messages
messageService.listen(conversation, (messages) => {
if (messages.hasData()) {
messages.data().forEach((msg) => {
console.log(`${msg.getSenderId()}: ${msg.getContent()}`);
});
}
});
// Send a message
const message = new Message({
senderId: 'user123',
content: 'Hello!',
});
await messageService.sendMessage(conversation, message);
// Clean up when done
messageService.detachListener(conversation);
conversationService.detachListener();Proper Initialization & Error Handling
Always handle the initialization properly:
const messenger = new MessengerCore({
dbDriver: 'firebase',
dbConfig: './firebase-key.json',
});
try {
// Attempt to initialize with existing user
await messenger.initialize('user123');
console.log('✅ User session initialized successfully');
// Proceed with your application logic
await messenger.initConversations();
// ... rest of your code
} catch (error) {
if (error.message.includes('User not found')) {
console.log('⚠️ User not found. Creating new user...');
// Create user if not exists
const newUser = await messenger.newUser({
id: 'user123',
name: 'New User',
profileImg: '',
lastSeen: new Date().toISOString(),
permissions: ['read', 'write'],
conversationsId: 'conv_user123',
});
console.log('✅ User created:', newUser.getName());
// Initialize with newly created user
await messenger.initialize('user123');
console.log('✅ User session initialized successfully');
} else {
console.error('❌ Initialization failed:', error);
throw error;
}
}Common Initialization Patterns
Pattern 1: Existing User (Most Common)
// For existing users in your system
const messenger = new MessengerCore({ dbDriver: 'firebase', dbConfig: './key.json' });
await messenger.initialize('existingUserId123');
// ✅ Ready to usePattern 2: New User Registration
// When registering a new user
const messenger = new MessengerCore({ dbDriver: 'firebase', dbConfig: './key.json' });
// Create user first
await messenger.newUser({
id: 'newUserId456',
name: 'Alice Johnson',
profileImg: 'https://example.com/alice.jpg',
lastSeen: new Date().toISOString(),
permissions: ['read', 'write'],
conversationsId: 'conv_newUserId456',
});
// Then initialize
await messenger.initialize('newUserId456');
// ✅ Ready to usePattern 3: Safe Initialization (Try existing, create if needed)
const messenger = new MessengerCore({ dbDriver: 'firebase', dbConfig: './key.json' });
async function safeInitialize(userId: string, userData: NewUser) {
try {
await messenger.initialize(userId);
console.log('✅ Existing user initialized');
} catch (error) {
if (error.message.includes('User not found')) {
console.log('⚠️ Creating new user...');
await messenger.newUser(userData);
await messenger.initialize(userId);
console.log('✅ New user created and initialized');
} else {
throw error;
}
}
}
// Use it
await safeInitialize('user789', {
id: 'user789',
name: 'Bob Wilson',
profileImg: '',
lastSeen: new Date().toISOString(),
permissions: ['read', 'write'],
conversationsId: 'conv_user789',
});
// ✅ Ready to use, whether user existed or notTypeScript Support
try { await messenger.initialize('user123'); } catch (error) { if (error.message.includes('User not found')) { // Create user if not exists const newUser = await messenger.newUser({ id: 'user123', name: 'New User', profileImg: '', lastSeen: new Date().toISOString(), permissions: ['read', 'write'], conversationsId: 'conv_user123', });
await messenger.initialize('user123');} else { console.error('Initialization failed:', error); } }
### TypeScript Support
Full TypeScript support with type definitions:
```typescript
import type {
User,
Conversation,
Message,
NewUser,
NewConversation,
NewMessage,
} from '@maleeshagimshan98/infinite-messenger-core';
// Type-safe user creation
const userdata: NewUser = {
id: 'user123',
name: 'John Doe',
profileImg: 'https://example.com/avatar.jpg',
lastSeen: new Date().toISOString(),
permissions: ['read', 'write'],
conversationsId: 'conv_user123',
};
const user: User = await messenger.newUser(userData);Testing
The library includes comprehensive test coverage:
# Run all tests
npm test
# Run tests with coverage
npm run test:coverage
# Run specific test file
npm test -- conversation.service.test.tsTest Structure:
- Unit tests for models (User, Conversation, Message)
- Integration tests for services
- Firebase repository tests
- End-to-end workflow tests
License
This project is licensed under the MIT License - see below for details.
MIT License
Copyright (c) 2025 Maleesha Gimshan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.Contributing
Contributions are welcome and greatly appreciated! 🎉
How to Contribute
Fork the repository
git clone https://github.com/maleeshagimshan98/messenger-core-v2.0.git cd messenger-core-v2.0Create a feature branch
git checkout -b feature/amazing-featureMake your changes
- Write clean, documented code
- Add tests for new features
- Ensure all tests pass:
npm test - Follow the existing code style:
npm run lint
Commit your changes
git commit -m 'Add some amazing feature'Push to your branch
git push origin feature/amazing-featureOpen a Pull Request
- Provide a clear description of changes
- Reference any related issues
- Ensure CI checks pass
Development Setup
# Install dependencies
npm install
# Run tests
npm test
# Run linter
npm run lint
# Fix lint issues
npm run lint:fix
# Format code
npm run format
# Build
npm run buildCode of Conduct
Please be respectful and constructive in all interactions. We aim to maintain a welcoming and inclusive community.
Bug Reports & Feature Requests
Reporting Bugs
If you find a bug, please open an issue with:
- Clear title describing the issue
- Detailed description of the problem
- Steps to reproduce the bug
- Expected behavior vs actual behavior
- Environment details:
- Node.js version
- Library version
- Operating system
- Database (Firebase/MongoDB)
- Code sample or minimal reproduction
- Error messages or stack traces
Example:
## Bug: Messages not appearing in real-time
**Description**: When using `messageService.listen()`, new messages don't appear until page refresh.
**Steps to Reproduce**:
1. Initialize messenger with user123
2. Start listening to conversation: `messageService.listen(conv, callback)`
3. Send message from another client
4. Callback is not invoked
**Expected**: Callback should be invoked with new messages
**Actual**: No callback, messages appear only after refresh
**Environment**:
- Node.js: v16.14.0
- Library: v2.0.0
- OS: Windows 11
- Database: Firebase
**Code**:
\```typescript
messageService.listen(conversation, (messages) => {
console.log('New messages:', messages.data());
});
\```
**Error**: None in consoleFeature Requests
Have an idea? Open an issue with:
- Feature description - What should it do?
- Use case - Why is it needed?
- Example API - How would you use it?
- Alternatives considered - Other approaches you've thought about
Support & Community
- 💬 Discussions: GitHub Discussions
- 🐛 Issues: GitHub Issues
- 📧 Email: [email protected]
- 🔗 Website: maleeshagimshan.com
Author
Maleesha Gimshan
- GitHub: @maleeshagimshan98
- Email: [email protected]
- LinkedIn: Maleesha Gimshan
Acknowledgments
- Firebase team for the amazing real-time database
- All contributors who have helped improve this library
- The Node.js and TypeScript communities
Changelog
See CHANGELOG.md for a detailed history of changes (coming soon).
Roadmap
Upcoming Features
- 🔄 MongoDB Support - Alternative database implementation
- 📎 File Attachments - Support for media messages
- ✅ Message Status - Delivery and read receipts
- 🔔 Push Notifications - Real-time notification support
- 👥 Group Management - Add/remove participants
- 🔍 Message Search - Full-text search capabilities
- 🔐 Enhanced Security - Encryption and permission systems
See GitHub Issues for detailed roadmap and progress.
