@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.
- package/LICENSE +21 -0
- package/README.md +451 -0
- package/dist/api.d.ts +43 -0
- package/dist/assets/styles.css +2 -0
- package/dist/chatemi.js +1626 -0
- package/dist/chatemi.umd.cjs +1 -0
- package/dist/components/ChatEmiLauncher.d.ts +3 -0
- package/dist/components/ChatEmiMessenger.d.ts +3 -0
- package/dist/context.d.ts +42 -0
- package/dist/index.d.ts +8 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/mongodb.d.ts +23 -0
- package/dist/server/mongodb.js +58 -0
- package/dist/socket.d.ts +33 -0
- package/dist/types.d.ts +364 -0
- package/docs/BACKEND_CONTRACT.md +305 -0
- package/docs/IMPLEMENTATION_GUIDE.md +306 -0
- package/examples/nextjs-chat-widget/README.md +46 -0
- package/examples/nextjs-chat-widget/app/api/chat/conversations/route.ts +30 -0
- package/examples/nextjs-chat-widget/app/api/chat/socket-events/route.ts +44 -0
- package/examples/nextjs-chat-widget/app/components/ChatWidget.tsx +35 -0
- package/examples/nextjs-chat-widget/app/layout.tsx +20 -0
- package/examples/nextjs-chat-widget/app/lib/chatemi-config.ts +52 -0
- package/examples/nextjs-chat-widget/app/lib/chatemi-mongo.ts +27 -0
- package/examples/nextjs-chat-widget/app/page.tsx +11 -0
- package/package.json +77 -0
|
@@ -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
|
+
}
|