Skip to main content

📦 @xhub-chat/core

The core SDK provides framework-agnostic chat functionality that can be used with any JavaScript framework or vanilla JavaScript.

Overview

@xhub-chat/core is the foundation of XHub Chat. It handles:

  • ✅ WebSocket connections and real-time communication
  • ✅ Event-driven architecture with EventEmitter
  • ✅ Storage and synchronization (IndexedDB)
  • ✅ Room and timeline management
  • ✅ Message sending and receiving
  • ✅ User presence and typing indicators
  • ✅ Offline support and caching

Installation

pnpm add @xhub-chat/core

Basic Usage

Creating a Client

import { createClient } from '@xhub-chat/core';

const client = createClient({
baseUrl: 'https://api.example.com',
accessToken: 'your-access-token',
userId: '@user:example.com',
});

// Start the client
await client.startClient();

console.log('Client started successfully!');

Listening to Events

// Listen for new messages
client.on('Room.timeline', (event, room, toStartOfTimeline) => {
if (event.getType() === 'm.room.message') {
console.log(`New message in ${room.name}: ${event.getContent().body}`);
}
});

// Listen for sync state changes
client.on('sync', (state, prevState, data) => {
console.log('Sync state:', state);
});

// Listen for room updates
client.on('Room.name', (room) => {
console.log(`Room name changed to: ${room.name}`);
});

Working with Rooms

// Get all rooms
const rooms = client.getRooms();
console.log(`You have ${rooms.length} rooms`);

// Get a specific room
const room = client.getRoom('!roomId:example.com');

// Get room members
const members = room.getMembers();

// Get room timeline
const timeline = room.getLiveTimeline();
const events = timeline.getEvents();

Sending Messages

// Send a text message
const content = {
msgtype: 'm.text',
body: 'Hello, World!',
};

await client.sendMessage('!roomId:example.com', content);

// Send a formatted message
const formattedContent = {
msgtype: 'm.text',
body: 'Hello **World**!',
format: 'org.xhub.custom.html',
formatted_body: '<p>Hello <strong>World</strong>!</p>',
};

await client.sendMessage('!roomId:example.com', formattedContent);

Managing Reactions

// Add a reaction to a message
await client.sendEvent('!roomId:example.com', 'm.reaction', {
'm.relates_to': {
rel_type: 'm.annotation',
event_id: '$messageEventId',
key: '👍',
},
});

// Get reactions for an event
const event = room.findEventById('$eventId');
const relations = room.getUnfilteredTimelineSet().relations;
const reactions = relations.getChildEventsForEvent(
event.getId(),
'm.annotation',
'm.reaction'
);

Core APIs

Client Methods

MethodDescription
startClient()Start syncing with the server
stopClient()Stop syncing
getRooms()Get all joined rooms
getRoom(roomId)Get a specific room
sendMessage(roomId, content)Send a message
sendEvent(roomId, eventType, content)Send a custom event
joinRoom(roomId)Join a room
leave(roomId)Leave a room
createRoom(options)Create a new room

Room Methods

MethodDescription
getMembers()Get room members
getMember(userId)Get a specific member
getLiveTimeline()Get the live timeline
getMyMembership()Get your membership status
getName()Get room name
getTopic()Get room topic
getAvatarUrl()Get room avatar URL

Event Methods

MethodDescription
getType()Get event type
getSender()Get sender user ID
getContent()Get event content
getTs()Get timestamp
getId()Get event ID
getRoomId()Get room ID

Storage Configuration

Using IndexedDB

import { createClient, IndexedDBStore } from '@xhub-chat/core';

const store = new IndexedDBStore({
dbName: 'my-chat-app',
workerScript: '/workers/indexeddb.worker.js',
});

const client = createClient({
baseUrl: 'https://api.example.com',
accessToken: token,
store,
});

Custom Storage

import { IStore } from '@xhub-chat/core';

class CustomStore implements IStore {
async getItem(key: string): Promise<any> {
// Your implementation
}

async setItem(key: string, value: any): Promise<void> {
// Your implementation
}

async removeItem(key: string): Promise<void> {
// Your implementation
}

// ... implement other required methods
}

const client = createClient({
baseUrl: 'https://api.example.com',
accessToken: token,
store: new CustomStore(),
});

Event Types

Common Event Types

TypeDescription
m.room.messageRegular message
m.room.memberMembership change
m.room.nameRoom name change
m.room.topicRoom topic change
m.room.avatarRoom avatar change
m.reactionMessage reaction
m.room.encryptedEncrypted message

Client Events

EventDescription
syncSync state changed
Room.timelineNew event in room
Room.nameRoom name changed
Room.myMembershipYour membership changed
User.presenceUser presence updated
eventAny event received

Advanced Usage

Filtering Events

const filter = {
room: {
timeline: {
limit: 50,
types: ['m.room.message'],
},
},
};

await client.startClient({ filter });

Pagination

const room = client.getRoom(roomId);
const timeline = room.getLiveTimeline();

// Load older messages
const canPaginate = timeline.canPaginateBackwards();
if (canPaginate) {
await client.paginateEventTimeline(timeline, { backwards: true });
}

Custom Event Handlers

// Handle all events
client.on('event', (event) => {
console.log('Event received:', event.getType());
});

// Handle specific event types
client.on('Room.timeline', (event, room) => {
if (event.getType() === 'com.example.custom') {
// Handle custom event
}
});

TypeScript Support

Full TypeScript support with comprehensive type definitions:

import {
XHubChatClient,
Room,
XHubChatEvent,
EventTimeline,
RoomMember,
ICreateClientOpts,
} from '@xhub-chat/core';

const client: XHubChatClient = createClient(config);
const rooms: Room[] = client.getRooms();
const events: XHubChatEvent[] = room.getLiveTimeline().getEvents();

Performance Tips

  1. Use Filters: Limit sync data to what you need
  2. Implement Pagination: Don't load all messages at once
  3. Clean Up Listeners: Remove event listeners when done
  4. Use IndexedDB: Enable caching for better performance
  5. Debounce Events: Batch UI updates for high-frequency events

Best Practices

// ✅ Good: Clean up listeners
const handler = (event) => console.log(event);
client.on('Room.timeline', handler);
// Later...
client.off('Room.timeline', handler);

// ✅ Good: Handle errors
try {
await client.sendMessage(roomId, content);
} catch (error) {
console.error('Failed to send message:', error);
}

// ✅ Good: Check room exists
const room = client.getRoom(roomId);
if (room) {
// Use room
}

// ❌ Bad: Don't forget to start client
const client = createClient(config);
// client.startClient(); // Missing!

Next Steps