@dittolive/ditto-chat-core 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +377 -0
- package/dist/index.es.js +1764 -0
- package/dist/index.umd.js +5 -0
- package/dist/src/index.d.ts +9 -0
- package/dist/src/slices/useChatUser.d.ts +22 -0
- package/dist/src/slices/useMessages.d.ts +41 -0
- package/dist/src/slices/useRBAC.d.ts +8 -0
- package/dist/src/slices/useRooms.d.ts +31 -0
- package/dist/src/types/ChatUser.d.ts +11 -0
- package/dist/src/types/Message.d.ts +30 -0
- package/dist/src/types/MessageWithUser.d.ts +9 -0
- package/dist/src/types/RBAC.d.ts +11 -0
- package/dist/src/types/Room.d.ts +13 -0
- package/dist/src/useChat.d.ts +37 -0
- package/dist/tests/setup.d.ts +14 -0
- package/dist/tests/useChat.edge-cases.test.d.ts +1 -0
- package/dist/tests/useChat.error.test.d.ts +1 -0
- package/dist/tests/useChat.test.d.ts +1 -0
- package/dist/tests/useChatUser.test.d.ts +1 -0
- package/dist/tests/useMessages.test.d.ts +1 -0
- package/dist/tests/useRBAC.test.d.ts +1 -0
- package/dist/tests/useRooms.test.d.ts +1 -0
- package/package.json +67 -17
- package/dist/index.d.ts +0 -8
- package/dist/index.js +0 -14
package/README.md
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
# dittochatcore
|
|
2
|
+
|
|
3
|
+
`@dittolive/ditto-chat-core` is a React and TypeScript-based library leveraging Ditto for real-time chat functionalities.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Installation](#installation)
|
|
8
|
+
- [Usage](#usage)
|
|
9
|
+
- [Comment Rooms (Generated Rooms)](#comment-rooms-generated-rooms)
|
|
10
|
+
- [Creating a Comment Room](#creating-a-comment-room)
|
|
11
|
+
- [Subscribing to Messages](#subscribing-to-messages)
|
|
12
|
+
- [Role-Based Access Control (RBAC)](#role-based-access-control-rbac)
|
|
13
|
+
- [Available Permissions](#available-permissions)
|
|
14
|
+
- [Configuring Permissions](#configuring-permissions)
|
|
15
|
+
- [Checking Permissions](#checking-permissions)
|
|
16
|
+
- [Notifications](#notifications)
|
|
17
|
+
- [Notification Handler](#notification-handler)
|
|
18
|
+
- [Default Behavior](#default-behavior)
|
|
19
|
+
- [Custom Notification Handler](#custom-notification-handler)
|
|
20
|
+
- [Common Notification Events](#common-notification-events)
|
|
21
|
+
- [Singleton Store Pattern](#singleton-store-pattern)
|
|
22
|
+
- [Why This Matters](#why-this-matters)
|
|
23
|
+
- [What This Means for You](#what-this-means-for-you)
|
|
24
|
+
- [Example](#example)
|
|
25
|
+
- [Ensuring Single Zustand Installation](#ensuring-single-zustand-installation)
|
|
26
|
+
- [Detailed Documentation](#detailed-documentation)
|
|
27
|
+
- [Architecture & Performance](#architecture--performance)
|
|
28
|
+
- [Optimistic UI Updates](#optimistic-ui-updates)
|
|
29
|
+
- [Available Scripts](#available-scripts)
|
|
30
|
+
- [Keywords](#keywords)
|
|
31
|
+
- [License](#license)
|
|
32
|
+
- [Repository](#repository)
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
You can install `@dittolive/ditto-chat-core` using npm or yarn:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install @dittolive/ditto-chat-core
|
|
40
|
+
# or
|
|
41
|
+
yarn add @dittolive/ditto-chat-core
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
Here's a basic example of how to use `@dittolive/ditto-chat-core` in your React application:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import React, { useEffect, useState } from 'react';
|
|
50
|
+
import { Ditto } from '@dittolive/ditto';
|
|
51
|
+
import { useDittoChat, useDittoChatStore } from '@dittolive/ditto-chat-core';
|
|
52
|
+
|
|
53
|
+
// Assume you have a Ditto instance and user details
|
|
54
|
+
const dittoInstance: Ditto | null = null; // Your Ditto instance
|
|
55
|
+
const currentUserId = 'your-user-id';
|
|
56
|
+
const userCollectionKey = 'your-user-collection-key';
|
|
57
|
+
|
|
58
|
+
function ChatApp() {
|
|
59
|
+
// 1. Initialize the chat store with useDittoChat
|
|
60
|
+
useDittoChat({
|
|
61
|
+
ditto: dittoInstance,
|
|
62
|
+
userId: currentUserId,
|
|
63
|
+
userCollectionKey: userCollectionKey,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// 2. Access parts of the store using useDittoChatStore selectors
|
|
67
|
+
const rooms = useDittoChatStore(state => state.rooms);
|
|
68
|
+
const activeRoomId = useDittoChatStore(state => state.activeRoomId);
|
|
69
|
+
const setActiveRoomId = useDittoChatStore(state => state.setActiveRoomId);
|
|
70
|
+
const messagesByRoom = useDittoChatStore(state => state.messagesByRoom);
|
|
71
|
+
const createMessage = useDittoChatStore(state => state.createMessage);
|
|
72
|
+
|
|
73
|
+
// 3. Derived state
|
|
74
|
+
const activeRoom = rooms.find(r => r._id === activeRoomId);
|
|
75
|
+
const messages = activeRoomId ? (messagesByRoom[activeRoomId] || []) : [];
|
|
76
|
+
|
|
77
|
+
// Example usage:
|
|
78
|
+
const handleSendMessage = (text: string) => {
|
|
79
|
+
if (activeRoom) {
|
|
80
|
+
createMessage(activeRoom, text);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div>
|
|
86
|
+
<h1>Ditto Chat</h1>
|
|
87
|
+
<p>Current User ID: {currentUserId}</p>
|
|
88
|
+
<h2>Rooms:</h2>
|
|
89
|
+
<ul>
|
|
90
|
+
{rooms.map(room => (
|
|
91
|
+
<li
|
|
92
|
+
key={room._id}
|
|
93
|
+
onClick={() => setActiveRoomId(room._id)}
|
|
94
|
+
style={{ fontWeight: room._id === activeRoomId ? 'bold' : 'normal' }}
|
|
95
|
+
>
|
|
96
|
+
{room.name}
|
|
97
|
+
</li>
|
|
98
|
+
))}
|
|
99
|
+
</ul>
|
|
100
|
+
<h2>Messages in Active Room:</h2>
|
|
101
|
+
<ul>
|
|
102
|
+
{messages.map(wrapper => (
|
|
103
|
+
<li key={wrapper.id}>
|
|
104
|
+
<strong>{wrapper.user?.name || wrapper.message.userId}:</strong> {wrapper.message.text}
|
|
105
|
+
</li>
|
|
106
|
+
))}
|
|
107
|
+
</ul>
|
|
108
|
+
<button onClick={() => handleSendMessage('Hello from the app!')}>Send Test Message</button>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export default ChatApp;
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Note:** The `dittoInstance`, `currentUserId`, and `userCollectionKey` should be provided from your application's context or configuration. You will need to replace `null` with a properly initialized Ditto instance.
|
|
117
|
+
|
|
118
|
+
## Comment Rooms (Generated Rooms)
|
|
119
|
+
|
|
120
|
+
DittoChatCore supports "Generated Rooms" which behave differently from standard chat rooms:
|
|
121
|
+
|
|
122
|
+
1. They are **excluded** from the main room list found in `state.rooms`.
|
|
123
|
+
2. They are stored separately in `state.generatedRooms`.
|
|
124
|
+
3. They must be subscribed to explicitly.
|
|
125
|
+
|
|
126
|
+
### Creating a Comment Room
|
|
127
|
+
|
|
128
|
+
Use `createGeneratedRoom` to create a room that won't pollute the main chat list:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
const { createGeneratedRoom } = useDittoChatStore((state) => state)
|
|
132
|
+
|
|
133
|
+
const handleOpenComments = async (entityId: string) => {
|
|
134
|
+
// Creates a room with ID `comments-${entityId}` if it doesn't exist
|
|
135
|
+
// and adds it to state.generatedRooms
|
|
136
|
+
await createGeneratedRoom(`comments-${entityId}`, 'Comments Thread')
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Subscribing to Messages
|
|
141
|
+
|
|
142
|
+
For generated rooms, you often want to subscribe to messages only when viewing that specific thread. Use the `subscribeToRoomMessages` helper:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
const { subscribeToRoomMessages, unsubscribeFromRoomMessages } =
|
|
146
|
+
useDittoChatStore((state) => state)
|
|
147
|
+
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
if (isCommentsOpen) {
|
|
150
|
+
// Subscribe to messages for this specific room
|
|
151
|
+
subscribeToRoomMessages(commentRoomId, 'messages')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return () => {
|
|
155
|
+
// Clean up subscription when closing/unmounting
|
|
156
|
+
unsubscribeFromRoomMessages(commentRoomId)
|
|
157
|
+
}
|
|
158
|
+
}, [isCommentsOpen, commentRoomId])
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Role-Based Access Control (RBAC)
|
|
162
|
+
|
|
163
|
+
DittoChatCore includes a built-in RBAC system that allows you to control user permissions for various chat actions. By default, all permissions are enabled.
|
|
164
|
+
|
|
165
|
+
### Available Permissions
|
|
166
|
+
|
|
167
|
+
| Permission | Description | Default |
|
|
168
|
+
| ---------------------- | ------------------------- | ------- |
|
|
169
|
+
| `canCreateRoom` | Create new chat rooms | `true` |
|
|
170
|
+
| `canEditOwnMessage` | Edit own messages | `true` |
|
|
171
|
+
| `canDeleteOwnMessage` | Delete own messages | `true` |
|
|
172
|
+
| `canAddReaction` | Add reactions to messages | `true` |
|
|
173
|
+
| `canRemoveOwnReaction` | Remove own reactions | `true` |
|
|
174
|
+
| `canMentionUsers` | Mention users in messages | `true` |
|
|
175
|
+
| `canSubscribeToRoom` | Subscribe to chat rooms | `true` |
|
|
176
|
+
|
|
177
|
+
### Configuring Permissions
|
|
178
|
+
|
|
179
|
+
You can configure permissions when initializing the chat or update them dynamically:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
import { useDittoChat, useDittoChatStore } from '@dittolive/ditto-chat-core'
|
|
183
|
+
|
|
184
|
+
// Configure permissions during initialization
|
|
185
|
+
const chat = useDittoChat({
|
|
186
|
+
ditto: dittoInstance,
|
|
187
|
+
userId: currentUserId,
|
|
188
|
+
userCollectionKey: userCollectionKey,
|
|
189
|
+
rbacConfig: {
|
|
190
|
+
canCreateRoom: false, // Disable room creation
|
|
191
|
+
canMentionUsers: false, // Disable user mentions
|
|
192
|
+
canDeleteOwnMessage: true, // Allow deleting own messages
|
|
193
|
+
},
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
// Or update permissions dynamically
|
|
197
|
+
const updateRBACConfig = useDittoChatStore((state) => state.updateRBACConfig)
|
|
198
|
+
|
|
199
|
+
updateRBACConfig({
|
|
200
|
+
canEditOwnMessage: false, // Disable message editing
|
|
201
|
+
})
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Checking Permissions
|
|
205
|
+
|
|
206
|
+
You can check if a user has permission to perform an action:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
const canPerformAction = useDittoChatStore((state) => state.canPerformAction)
|
|
210
|
+
|
|
211
|
+
if (canPerformAction('canCreateRoom')) {
|
|
212
|
+
// Show create room button
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**Note:** When a permission is denied, the action will fail silently with a warning logged to the console. The UI layer should check permissions before displaying action buttons to provide better UX.
|
|
217
|
+
|
|
218
|
+
## Notifications
|
|
219
|
+
|
|
220
|
+
DittoChatCore provides a customizable notification system through the `notificationHandler` prop. This allows you to integrate chat notifications with your preferred toast/notification library.
|
|
221
|
+
|
|
222
|
+
### Notification Handler
|
|
223
|
+
|
|
224
|
+
The `notificationHandler` is an optional callback function that receives notification events from the chat system.
|
|
225
|
+
|
|
226
|
+
**Signature:**
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
notificationHandler?: (title: string, description: string) => void
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**Parameters:**
|
|
233
|
+
|
|
234
|
+
- `title` - The notification title (e.g., "New Message", "Room Created")
|
|
235
|
+
- `description` - Additional details about the notification
|
|
236
|
+
|
|
237
|
+
### Default Behavior
|
|
238
|
+
|
|
239
|
+
If no `notificationHandler` is provided, notifications will not trigger or be logged. You must provide a custom handler to receive and display notifications.
|
|
240
|
+
|
|
241
|
+
### Custom Notification Handler
|
|
242
|
+
|
|
243
|
+
You can provide a custom handler to integrate with any toast/notification library:
|
|
244
|
+
|
|
245
|
+
#### Example with Sonner
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { toast } from 'sonner'
|
|
249
|
+
import { useDittoChat } from '@dittolive/ditto-chat-core'
|
|
250
|
+
|
|
251
|
+
useDittoChat({
|
|
252
|
+
ditto: dittoInstance,
|
|
253
|
+
userId: currentUserId,
|
|
254
|
+
userCollectionKey: userCollectionKey,
|
|
255
|
+
notificationHandler: (title, description) => {
|
|
256
|
+
toast.info(title, {
|
|
257
|
+
description,
|
|
258
|
+
})
|
|
259
|
+
},
|
|
260
|
+
})
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
#### Example with React-Toastify
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
import { toast } from 'react-toastify'
|
|
267
|
+
import { useDittoChat } from '@dittolive/ditto-chat-core'
|
|
268
|
+
|
|
269
|
+
useDittoChat({
|
|
270
|
+
ditto: dittoInstance,
|
|
271
|
+
userId: currentUserId,
|
|
272
|
+
userCollectionKey: userCollectionKey,
|
|
273
|
+
notificationHandler: (title, description) => {
|
|
274
|
+
toast.info(`${title}: ${description}`)
|
|
275
|
+
},
|
|
276
|
+
})
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
#### Example with Custom Notification System
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import { useDittoChat } from '@dittolive/ditto-chat-core'
|
|
283
|
+
|
|
284
|
+
useDittoChat({
|
|
285
|
+
ditto: dittoInstance,
|
|
286
|
+
userId: currentUserId,
|
|
287
|
+
userCollectionKey: userCollectionKey,
|
|
288
|
+
notificationHandler: (title, description) => {
|
|
289
|
+
// Custom notification logic
|
|
290
|
+
showCustomNotification({
|
|
291
|
+
type: 'info',
|
|
292
|
+
title,
|
|
293
|
+
message: description,
|
|
294
|
+
duration: 3000,
|
|
295
|
+
})
|
|
296
|
+
},
|
|
297
|
+
})
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Common Notification Events
|
|
301
|
+
|
|
302
|
+
The chat system triggers notifications for various events:
|
|
303
|
+
|
|
304
|
+
- New messages in subscribed rooms
|
|
305
|
+
- User mentions
|
|
306
|
+
|
|
307
|
+
## Singleton Store Pattern
|
|
308
|
+
|
|
309
|
+
**Important:** DittoChatCore uses a **global singleton pattern** to ensure that all npm packages share the same Zustand store instance.
|
|
310
|
+
|
|
311
|
+
### Why This Matters
|
|
312
|
+
|
|
313
|
+
When `@dittolive/ditto-chat-core` is installed in multiple packages (e.g., in your main app and in `@dittolive/ditto-chat-ui`), each package could potentially get its own copy of the module. To prevent multiple independent store instances, we use `globalThis` to maintain a single shared store.
|
|
314
|
+
|
|
315
|
+
### What This Means for You
|
|
316
|
+
|
|
317
|
+
1. **Initialize once** - Call `useDittoChat()` in your root component
|
|
318
|
+
2. **Use anywhere** - Call `useDittoChatStore()` in any component from any package
|
|
319
|
+
3. **Shared state** - All packages automatically share the same state
|
|
320
|
+
|
|
321
|
+
### Example
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
// In your main app (initialize once)
|
|
325
|
+
function App() {
|
|
326
|
+
useDittoChat({
|
|
327
|
+
ditto: dittoInstance,
|
|
328
|
+
userId: currentUserId,
|
|
329
|
+
userCollectionKey: userCollectionKey,
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
return <YourApp />
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// In any component, from any package (use the shared store)
|
|
336
|
+
function ChatComponent() {
|
|
337
|
+
const messages = useDittoChatStore((state) => state.messagesByRoom)
|
|
338
|
+
// All components see the same state
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## Architecture & Performance
|
|
343
|
+
|
|
344
|
+
### Optimistic UI Updates
|
|
345
|
+
|
|
346
|
+
DittoChatCore uses an **optimistic update pattern** for message reactions to provide instant UI feedback.
|
|
347
|
+
|
|
348
|
+
**How it works:**
|
|
349
|
+
|
|
350
|
+
1. **Immediate UI Update** - Reaction appears instantly in the UI before any database operations
|
|
351
|
+
2. **Async Persistence** - Change persists to Ditto database in the background
|
|
352
|
+
3. **Auto Rollback** - If database update fails, the reaction is automatically removed from the UI
|
|
353
|
+
|
|
354
|
+
**Benefits:** Instant feedback, no perceived latency, automatic data consistency.
|
|
355
|
+
|
|
356
|
+
**Implementation:** See `updateMessageReactions()` function in [`src/slices/useMessages.ts`](src/slices/useMessages.ts) for the complete implementation details.
|
|
357
|
+
|
|
358
|
+
## Available Scripts
|
|
359
|
+
|
|
360
|
+
In the project directory, you can run:
|
|
361
|
+
|
|
362
|
+
- `npm run build`: Builds the project for production.
|
|
363
|
+
- `npm run test`: Runs tests and exits.
|
|
364
|
+
- `npm run test:watch`: Runs tests in watch mode.
|
|
365
|
+
- `npm run clean`: Removes the `dist` directory.
|
|
366
|
+
|
|
367
|
+
## Keywords
|
|
368
|
+
|
|
369
|
+
react, typescript, ditto, ditto-chat-core, ditto-chat
|
|
370
|
+
|
|
371
|
+
## License
|
|
372
|
+
|
|
373
|
+
MIT
|
|
374
|
+
|
|
375
|
+
## Repository
|
|
376
|
+
|
|
377
|
+
https://github.com/getditto/DittoChat.git
|