@dittolive/ditto-chat-core 0.0.1 → 0.1.0

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 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