@mademi_dev/chatemi 1.0.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.
@@ -0,0 +1,305 @@
1
+ # ChatEmi backend contract
2
+
3
+ This document describes the REST, WebSocket, and data contracts expected by the default ChatEmi API client and UI.
4
+
5
+ ## REST shape
6
+
7
+ All JSON endpoints should return JSON. Paginated list endpoints should use:
8
+
9
+ ```json
10
+ {
11
+ "items": [],
12
+ "nextCursor": null
13
+ }
14
+ ```
15
+
16
+ Errors should use:
17
+
18
+ ```json
19
+ {
20
+ "message": "Human readable error",
21
+ "code": "OPTIONAL_MACHINE_CODE"
22
+ }
23
+ ```
24
+
25
+ ## Authentication
26
+
27
+ The browser client sends:
28
+
29
+ ```http
30
+ Authorization: Bearer <token>
31
+ ```
32
+
33
+ Validate the token on every REST request and socket connection. Do not trust role/member data from the client.
34
+
35
+ ## Users
36
+
37
+ ```ts
38
+ type ChatEmiUser = {
39
+ id: string;
40
+ name: string;
41
+ username?: string;
42
+ avatarUrl?: string;
43
+ statusText?: string;
44
+ presence?: "online" | "offline" | "away" | "busy" | "invisible";
45
+ lastSeenAt?: string;
46
+ };
47
+ ```
48
+
49
+ Endpoints:
50
+
51
+ | Method | Path | Purpose |
52
+ | --- | --- | --- |
53
+ | `GET` | `/me` | Current authenticated user |
54
+ | `GET` | `/users?q=ava&limit=10` | Search users |
55
+ | `GET` | `/users/:userId` | User details |
56
+ | `PATCH` | `/users/:userId` | Update profile/avatar |
57
+
58
+ ## Conversations
59
+
60
+ ```ts
61
+ type ChatEmiConversation = {
62
+ id: string;
63
+ type: "direct" | "group" | "channel" | "bot";
64
+ title?: string;
65
+ description?: string;
66
+ avatarUrl?: string;
67
+ participants: ChatEmiUser[];
68
+ members?: ChatEmiMember[];
69
+ ownerId?: string;
70
+ adminIds?: string[];
71
+ publicUsername?: string;
72
+ lastMessage?: ChatEmiMessage;
73
+ unreadCount?: number;
74
+ readOnly?: boolean;
75
+ createdAt: string;
76
+ updatedAt?: string;
77
+ };
78
+ ```
79
+
80
+ Endpoints:
81
+
82
+ | Method | Path | Purpose |
83
+ | --- | --- | --- |
84
+ | `GET` | `/conversations` | List conversations |
85
+ | `POST` | `/conversations` | Create direct/group/channel/bot conversation |
86
+ | `GET` | `/conversations/:conversationId` | Conversation detail |
87
+ | `PATCH` | `/conversations/:conversationId` | Update title, description, mute, archive, etc. |
88
+ | `DELETE` | `/conversations/:conversationId` | Archive/delete conversation |
89
+ | `PATCH` | `/conversations/:conversationId/avatar` | Update conversation avatar |
90
+
91
+ ## Members and admins
92
+
93
+ ```ts
94
+ type ChatEmiMember = {
95
+ user: ChatEmiUser;
96
+ role: "owner" | "admin" | "moderator" | "member" | "guest";
97
+ permissions?: Array<
98
+ | "send_messages"
99
+ | "send_media"
100
+ | "pin_messages"
101
+ | "manage_messages"
102
+ | "manage_members"
103
+ | "manage_roles"
104
+ | "manage_conversation"
105
+ | "invite_members"
106
+ >;
107
+ joinedAt: string;
108
+ mutedUntil?: string | null;
109
+ bannedUntil?: string | null;
110
+ title?: string;
111
+ };
112
+ ```
113
+
114
+ Endpoints:
115
+
116
+ | Method | Path | Purpose |
117
+ | --- | --- | --- |
118
+ | `POST` | `/conversations/:conversationId/members` | Add members `{ userIds: string[] }` |
119
+ | `PATCH` | `/conversations/:conversationId/members/:userId` | Update role, permissions, mute, ban |
120
+ | `DELETE` | `/conversations/:conversationId/members/:userId` | Remove member |
121
+
122
+ Server rules:
123
+
124
+ - Only owners/admins/moderators should manage members.
125
+ - Only owners/admins should promote admins unless your product says otherwise.
126
+ - Channels with `readOnly: true` should reject normal member messages.
127
+
128
+ ## Messages
129
+
130
+ ```ts
131
+ type ChatEmiMessage = {
132
+ id: string;
133
+ conversationId: string;
134
+ sender: ChatEmiUser;
135
+ kind?: "text" | "media" | "voice" | "system";
136
+ text?: string;
137
+ html?: string;
138
+ attachments?: ChatEmiAttachment[];
139
+ replyTo?: ChatEmiMessage;
140
+ replyToId?: string;
141
+ forwardedFrom?: ChatEmiUser;
142
+ forwardedFromConversationId?: string;
143
+ forwardedFromMessageId?: string;
144
+ reactions?: ChatEmiReaction[];
145
+ status?: "queued" | "sending" | "sent" | "delivered" | "read" | "failed";
146
+ deliveredTo?: ChatEmiReceiptEvent[];
147
+ readBy?: ChatEmiReceiptEvent[];
148
+ createdAt: string;
149
+ };
150
+ ```
151
+
152
+ Endpoints:
153
+
154
+ | Method | Path | Purpose |
155
+ | --- | --- | --- |
156
+ | `GET` | `/conversations/:conversationId/messages` | List messages |
157
+ | `POST` | `/conversations/:conversationId/messages` | Send message |
158
+ | `PATCH` | `/conversations/:conversationId/messages/:messageId` | Edit message |
159
+ | `DELETE` | `/conversations/:conversationId/messages/:messageId` | Delete message |
160
+ | `POST` | `/conversations/:conversationId/messages/:messageId/forward` | Forward message |
161
+
162
+ Send message input:
163
+
164
+ ```json
165
+ {
166
+ "conversationId": "conversation_1",
167
+ "text": "Hello",
168
+ "replyToId": "message_1",
169
+ "kind": "text",
170
+ "attachments": []
171
+ }
172
+ ```
173
+
174
+ ## Attachments
175
+
176
+ ```ts
177
+ type ChatEmiAttachment = {
178
+ id: string;
179
+ type: "image" | "video" | "audio" | "voice" | "file" | "location" | "contact";
180
+ url: string;
181
+ name?: string;
182
+ mimeType?: string;
183
+ size?: number;
184
+ width?: number;
185
+ height?: number;
186
+ duration?: number;
187
+ thumbnailUrl?: string;
188
+ caption?: string;
189
+ };
190
+ ```
191
+
192
+ Endpoint:
193
+
194
+ | Method | Path | Purpose |
195
+ | --- | --- | --- |
196
+ | `POST` | `/attachments` | Multipart upload |
197
+
198
+ Expected response:
199
+
200
+ ```json
201
+ {
202
+ "id": "attachment_1",
203
+ "type": "image",
204
+ "url": "https://cdn.example.com/file.png",
205
+ "name": "file.png",
206
+ "mimeType": "image/png",
207
+ "size": 12345
208
+ }
209
+ ```
210
+
211
+ ## Receipts
212
+
213
+ Endpoints:
214
+
215
+ | Method | Path | Body |
216
+ | --- | --- | --- |
217
+ | `POST` | `/conversations/:conversationId/delivered` | `{ "messageIds": ["message_1"] }` |
218
+ | `POST` | `/conversations/:conversationId/read` | `{ "messageIds": ["message_1"] }` |
219
+
220
+ Socket event:
221
+
222
+ ```json
223
+ {
224
+ "type": "message.receipt",
225
+ "payload": {
226
+ "conversationId": "conversation_1",
227
+ "messageIds": ["message_1"],
228
+ "userId": "user_2",
229
+ "status": "read",
230
+ "at": "2026-06-19T21:05:00.000Z"
231
+ }
232
+ }
233
+ ```
234
+
235
+ ## WebSocket protocol
236
+
237
+ Every event is an envelope:
238
+
239
+ ```json
240
+ {
241
+ "type": "message.created",
242
+ "payload": {}
243
+ }
244
+ ```
245
+
246
+ Incoming events supported by the client:
247
+
248
+ - `conversation.created`
249
+ - `conversation.updated`
250
+ - `conversation.deleted`
251
+ - `conversation.member.added`
252
+ - `conversation.member.updated`
253
+ - `conversation.member.removed`
254
+ - `message.created`
255
+ - `message.updated`
256
+ - `message.deleted`
257
+ - `message.receipt`
258
+ - `message.reaction`
259
+ - `typing`
260
+ - `presence`
261
+ - `notification`
262
+
263
+ Outgoing events sent by client helpers:
264
+
265
+ - `conversation.subscribe`
266
+ - `conversation.unsubscribe`
267
+ - `typing`
268
+ - `message.read`
269
+ - `message.delivered`
270
+ - `message.forward`
271
+ - `conversation.member.update`
272
+ - `conversation.avatar.update`
273
+ - `presence`
274
+
275
+ ## Notification payload
276
+
277
+ ```json
278
+ {
279
+ "type": "notification",
280
+ "payload": {
281
+ "id": "notification_1",
282
+ "kind": "message",
283
+ "title": "Ava",
284
+ "body": "Sent a message",
285
+ "conversationId": "conversation_1",
286
+ "messageId": "message_1",
287
+ "avatarUrl": "https://cdn.example.com/ava.png",
288
+ "read": false,
289
+ "createdAt": "2026-06-19T21:05:00.000Z"
290
+ }
291
+ }
292
+ ```
293
+
294
+ ## Minimal implementation sequence
295
+
296
+ 1. Implement `/me`.
297
+ 2. Implement conversation list.
298
+ 3. Implement message list/send.
299
+ 4. Add WebSocket `message.created`.
300
+ 5. Add receipts.
301
+ 6. Add uploads.
302
+ 7. Add members/admins.
303
+ 8. Add notifications.
304
+ 9. Add external user directory.
305
+ 10. Add production monitoring and rate limits.
@@ -0,0 +1,306 @@
1
+ # ChatEmi implementation guide
2
+
3
+ This guide explains how to ship ChatEmi in a production application. It covers frontend setup, Next.js usage, backend API responsibilities, sockets, notifications, media, MongoDB, external user directories, theming, and release checks.
4
+
5
+ ## 1. Architecture
6
+
7
+ ChatEmi is split into browser-safe and server-side pieces:
8
+
9
+ | Area | Import | Runs in | Purpose |
10
+ | --- | --- | --- | --- |
11
+ | React package | `chatemi` | Browser / React client | Provider, hook, API client, socket client, launcher, messenger UI |
12
+ | Styles | `chatemi/styles.css` | Browser | All default UI, themes, launcher modal, notification badge |
13
+ | Server helpers | `chatemi/server` | Node.js only | MongoDB connection helper and collection/index setup |
14
+
15
+ Do not expose database credentials to browser code. Browser code should call your authenticated HTTP and WebSocket APIs.
16
+
17
+ ## 2. Next.js setup
18
+
19
+ ### Install
20
+
21
+ ```bash
22
+ npm install chatemi
23
+ ```
24
+
25
+ If your API server uses ChatEmi MongoDB helpers:
26
+
27
+ ```bash
28
+ npm install mongodb
29
+ ```
30
+
31
+ ### Import CSS once
32
+
33
+ In App Router, import CSS from `app/layout.tsx`:
34
+
35
+ ```tsx
36
+ import "chatemi/styles.css";
37
+ ```
38
+
39
+ ### Create a client widget
40
+
41
+ The widget uses browser APIs (`localStorage`, WebSocket, notifications), so it must live in a client component:
42
+
43
+ ```tsx
44
+ "use client";
45
+
46
+ import { ChatEmiLauncher, ChatEmiProvider } from "chatemi";
47
+ import { useMemo } from "react";
48
+
49
+ export function ChatWidget() {
50
+ const config = useMemo(
51
+ () => ({
52
+ apiBaseUrl: process.env.NEXT_PUBLIC_CHAT_API_URL!,
53
+ socketUrl: process.env.NEXT_PUBLIC_CHAT_SOCKET_URL,
54
+ token: () => localStorage.getItem("access_token") ?? undefined,
55
+ theme: "glass" as const,
56
+ notifications: {
57
+ enabled: true,
58
+ browser: true,
59
+ showWhenOpen: false,
60
+ maxStored: 100
61
+ }
62
+ }),
63
+ []
64
+ );
65
+
66
+ return (
67
+ <ChatEmiProvider config={config}>
68
+ <ChatEmiLauncher placement="bottom-right" theme="glass" />
69
+ </ChatEmiProvider>
70
+ );
71
+ }
72
+ ```
73
+
74
+ ## 3. Provider configuration
75
+
76
+ ```ts
77
+ type ChatEmiConfig = {
78
+ apiBaseUrl: string;
79
+ socketUrl?: string;
80
+ token?: string | (() => string | Promise<string | undefined> | undefined);
81
+ headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
82
+ currentUser?: ChatEmiUser;
83
+ endpoints?: ChatEmiEndpointOverrides;
84
+ userDirectory?: ChatEmiUserDirectoryConfig;
85
+ theme?: ChatEmiTheme;
86
+ notifications?: ChatEmiNotificationConfig;
87
+ reconnect?: {
88
+ enabled?: boolean;
89
+ maxAttempts?: number;
90
+ initialDelayMs?: number;
91
+ maxDelayMs?: number;
92
+ };
93
+ };
94
+ ```
95
+
96
+ Recommended defaults:
97
+
98
+ ```tsx
99
+ <ChatEmiProvider
100
+ config={{
101
+ apiBaseUrl: "https://api.example.com/chat",
102
+ socketUrl: "wss://api.example.com/chat/socket",
103
+ token: () => auth.getAccessToken(),
104
+ theme: "system",
105
+ notifications: {
106
+ enabled: true,
107
+ browser: true,
108
+ showWhenOpen: false,
109
+ maxStored: 100
110
+ },
111
+ reconnect: {
112
+ enabled: true,
113
+ initialDelayMs: 500,
114
+ maxDelayMs: 8000
115
+ }
116
+ }}
117
+ >
118
+ <ChatEmiLauncher />
119
+ </ChatEmiProvider>
120
+ ```
121
+
122
+ ## 4. Launcher behavior
123
+
124
+ `ChatEmiLauncher` is the recommended app integration:
125
+
126
+ - Floating toggle button.
127
+ - Notification count badge.
128
+ - Draggable desktop modal.
129
+ - Resizable desktop modal.
130
+ - Full-width mobile modal.
131
+ - Notification tray.
132
+ - Keeps provider/socket running while closed.
133
+
134
+ ```tsx
135
+ <ChatEmiLauncher
136
+ title="Support"
137
+ subtitle="Usually replies fast"
138
+ placement="bottom-right"
139
+ theme="midnight"
140
+ defaultOpen={false}
141
+ showNotificationList
142
+ markNotificationsReadOnOpen
143
+ initialSize={{ width: 460, height: 720 }}
144
+ minSize={{ width: 360, height: 520 }}
145
+ maxSize={{ width: 960, height: 860 }}
146
+ />
147
+ ```
148
+
149
+ ## 5. Notifications
150
+
151
+ ChatEmi stores notifications inside provider state:
152
+
153
+ ```tsx
154
+ const {
155
+ notifications,
156
+ unreadNotificationCount,
157
+ actions
158
+ } = useChatEmi();
159
+
160
+ actions.markNotificationsRead();
161
+ actions.dismissNotification("notification_1");
162
+ actions.clearNotifications();
163
+ ```
164
+
165
+ ### Socket notification event
166
+
167
+ ```json
168
+ {
169
+ "type": "notification",
170
+ "payload": {
171
+ "id": "notif_1",
172
+ "kind": "message",
173
+ "title": "Ava",
174
+ "body": "Sent a new message",
175
+ "conversationId": "conversation_1",
176
+ "messageId": "message_1",
177
+ "read": false,
178
+ "createdAt": "2026-06-19T21:05:00.000Z"
179
+ }
180
+ }
181
+ ```
182
+
183
+ If your backend only emits `message.created`, ChatEmi automatically creates a local notification for messages sent by other users.
184
+
185
+ Browser notifications are optional and require user permission. ChatEmi requests permission from a user gesture when the launcher opens.
186
+
187
+ ## 6. Backend responsibilities
188
+
189
+ The package gives you UI and clients. Your backend owns:
190
+
191
+ - Authentication and authorization.
192
+ - Persisting conversations, messages, members, receipts, attachments.
193
+ - Enforcing admin/member permissions.
194
+ - Uploading media to your storage provider.
195
+ - Broadcasting WebSocket events.
196
+ - Mapping external users to `ChatEmiUser`.
197
+
198
+ Minimum production endpoints:
199
+
200
+ - `GET /me`
201
+ - `GET /conversations`
202
+ - `POST /conversations`
203
+ - `GET /conversations/:conversationId/messages`
204
+ - `POST /conversations/:conversationId/messages`
205
+ - `POST /conversations/:conversationId/read`
206
+ - `POST /conversations/:conversationId/delivered`
207
+ - `POST /attachments`
208
+
209
+ See `docs/BACKEND_CONTRACT.md` for full details.
210
+
211
+ ## 7. MongoDB integration
212
+
213
+ Use `chatemi/server` only from Node.js:
214
+
215
+ ```ts
216
+ import { createChatEmiMongoConnection } from "chatemi/server";
217
+
218
+ const chatDb = await createChatEmiMongoConnection({
219
+ uri: process.env.MONGODB_URI!,
220
+ databaseName: "chatemi",
221
+ clientOptions: {
222
+ appName: "chatemi-api"
223
+ }
224
+ });
225
+
226
+ await chatDb.ensureIndexes();
227
+ ```
228
+
229
+ Connection guidance:
230
+
231
+ - Create the client once per process and reuse it.
232
+ - In serverless, initialize outside the handler so warm invocations reuse the client.
233
+ - Do not guess pool sizes. Use `clientOptions` based on deployment type, concurrency, and MongoDB metrics.
234
+ - Monitor `connections.current`, query latency, and wait queue behavior.
235
+
236
+ ## 8. External user directory
237
+
238
+ Use this when users live in another service:
239
+
240
+ ```tsx
241
+ userDirectory: {
242
+ baseUrl: "https://identity.example.com",
243
+ searchPath: "/users/search",
244
+ userPath: (userId) => `/users/${userId}`,
245
+ headers: () => ({
246
+ Authorization: `Bearer ${identityToken()}`
247
+ }),
248
+ mapUser: (raw) => {
249
+ const user = raw as { id: string; displayName: string; photoUrl?: string };
250
+ return {
251
+ id: user.id,
252
+ name: user.displayName,
253
+ avatarUrl: user.photoUrl
254
+ };
255
+ }
256
+ }
257
+ ```
258
+
259
+ ChatEmi does not automatically send the chat API bearer token to the external user API. Add headers explicitly if needed.
260
+
261
+ ## 9. Themes
262
+
263
+ Built-in themes:
264
+
265
+ - `light`
266
+ - `dark`
267
+ - `system`
268
+ - `midnight`
269
+ - `glass`
270
+ - `emerald`
271
+ - `violet`
272
+
273
+ Use the provider default:
274
+
275
+ ```tsx
276
+ <ChatEmiProvider config={{ apiBaseUrl, theme: "violet" }}>
277
+ <ChatEmiLauncher />
278
+ </ChatEmiProvider>
279
+ ```
280
+
281
+ Or override per UI component:
282
+
283
+ ```tsx
284
+ <ChatEmiLauncher theme="glass" />
285
+ ```
286
+
287
+ ## 10. Production checklist
288
+
289
+ Before publishing or deploying:
290
+
291
+ - Confirm package name and npm scope.
292
+ - Confirm `apiBaseUrl` and `socketUrl` point to production services.
293
+ - Verify auth token refresh behavior.
294
+ - Verify CORS for REST and WebSocket APIs.
295
+ - Verify attachment upload limits and file scanning.
296
+ - Verify admin permissions server-side.
297
+ - Verify socket reconnects after network loss.
298
+ - Verify notification permission behavior in Chrome, Safari, Firefox.
299
+ - Verify mobile launcher modal behavior.
300
+ - Run:
301
+
302
+ ```bash
303
+ npm run typecheck
304
+ npm run build
305
+ npm pack --dry-run
306
+ ```
@@ -0,0 +1,46 @@
1
+ # ChatEmi Next.js widget example
2
+
3
+ This example shows how to use ChatEmi in a Next.js App Router application with:
4
+
5
+ - `ChatEmiProvider`
6
+ - `ChatEmiLauncher`
7
+ - floating notification badge
8
+ - draggable/resizable modal
9
+ - browser notifications
10
+ - external user-directory configuration
11
+ - server-side MongoDB helper usage
12
+
13
+ The files are intentionally small and copy-paste friendly. They are not a standalone app inside this repository; copy them into a Next.js project that has `next`, `react`, `react-dom`, `chatemi`, and optionally `mongodb` installed.
14
+
15
+ ## Install in your app
16
+
17
+ ```bash
18
+ npm install chatemi
19
+ ```
20
+
21
+ If you also use the MongoDB server helpers:
22
+
23
+ ```bash
24
+ npm install mongodb
25
+ ```
26
+
27
+ ## Environment variables
28
+
29
+ ```bash
30
+ NEXT_PUBLIC_CHAT_API_URL=https://api.example.com/chat
31
+ NEXT_PUBLIC_CHAT_SOCKET_URL=wss://api.example.com/chat/socket
32
+ NEXT_PUBLIC_IDENTITY_API_URL=https://identity.example.com
33
+ MONGODB_URI=mongodb+srv://...
34
+ MONGODB_DB=chatemi
35
+ ```
36
+
37
+ Only `NEXT_PUBLIC_*` values are sent to the browser. Keep `MONGODB_URI` server-side only.
38
+
39
+ ## Files
40
+
41
+ - `app/layout.tsx` imports `chatemi/styles.css` once.
42
+ - `app/components/ChatWidget.tsx` is the client-side launcher widget.
43
+ - `app/lib/chatemi-config.ts` centralizes browser-safe config.
44
+ - `app/lib/chatemi-mongo.ts` shows server-side MongoDB setup.
45
+ - `app/api/chat/conversations/route.ts` shows how an API route can read MongoDB.
46
+ - `app/api/chat/socket-events/route.ts` documents example socket event payloads.
@@ -0,0 +1,30 @@
1
+ import { NextResponse } from "next/server";
2
+ import { getChatEmiMongo } from "../../../lib/chatemi-mongo";
3
+
4
+ export async function GET() {
5
+ const { collections } = await getChatEmiMongo();
6
+ const conversations = await collections.conversations.find({ archived: { $ne: true } }).sort({ updatedAt: -1 }).limit(50).toArray();
7
+
8
+ return NextResponse.json({
9
+ items: conversations,
10
+ nextCursor: null
11
+ });
12
+ }
13
+
14
+ export async function POST(request: Request) {
15
+ const { collections } = await getChatEmiMongo();
16
+ const input = await request.json();
17
+ const now = new Date().toISOString();
18
+
19
+ const conversation = {
20
+ ...input,
21
+ participants: [],
22
+ createdAt: now,
23
+ updatedAt: now,
24
+ unreadCount: 0
25
+ };
26
+
27
+ await collections.conversations.insertOne(conversation);
28
+
29
+ return NextResponse.json(conversation, { status: 201 });
30
+ }