@mademi_dev/chatemi 1.0.0 → 1.0.2
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 +157 -11
- package/dist/chatemi.js +3 -5
- package/dist/chatemi.umd.cjs +1 -1
- package/docs/IMPLEMENTATION_GUIDE.md +8 -8
- package/examples/nextjs-chat-widget/README.md +3 -3
- package/examples/nextjs-chat-widget/app/components/ChatWidget.tsx +1 -1
- package/examples/nextjs-chat-widget/app/layout.tsx +1 -1
- package/examples/nextjs-chat-widget/app/lib/chatemi-config.ts +1 -1
- package/examples/nextjs-chat-widget/app/lib/chatemi-mongo.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,12 +13,34 @@ ChatEmi is a publish-ready React messaging package for building in-app messenger
|
|
|
13
13
|
- `useChatEmi` for product code that needs chat actions and state.
|
|
14
14
|
- `ChatEmiMessenger`, a default responsive light/dark Telegram-style UI that can be used immediately or customized.
|
|
15
15
|
|
|
16
|
+
Repository: [github.com/mademi-dev/ChatEmi](https://github.com/mademi-dev/ChatEmi)
|
|
17
|
+
|
|
18
|
+
## Requirements
|
|
19
|
+
|
|
20
|
+
- React `>=18`
|
|
21
|
+
- React DOM `>=18`
|
|
22
|
+
- MongoDB driver `>=7` (optional, server-side only)
|
|
23
|
+
|
|
16
24
|
## Install
|
|
17
25
|
|
|
18
26
|
```bash
|
|
19
|
-
npm install chatemi
|
|
27
|
+
npm install @mademi_dev/chatemi react react-dom
|
|
20
28
|
```
|
|
21
29
|
|
|
30
|
+
For MongoDB server helpers in your API backend:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install @mademi_dev/chatemi mongodb
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Package exports
|
|
37
|
+
|
|
38
|
+
| Import | Purpose |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| `@mademi_dev/chatemi` | Provider, hook, API client, socket client, launcher, messenger UI, and types |
|
|
41
|
+
| `@mademi_dev/chatemi/styles.css` | Default UI, themes, launcher modal, and notification badge styles |
|
|
42
|
+
| `@mademi_dev/chatemi/server` | Node.js MongoDB connection helper and index setup |
|
|
43
|
+
|
|
22
44
|
## Documentation and examples
|
|
23
45
|
|
|
24
46
|
- Full implementation guide: [`docs/IMPLEMENTATION_GUIDE.md`](docs/IMPLEMENTATION_GUIDE.md)
|
|
@@ -26,8 +48,8 @@ npm install chatemi
|
|
|
26
48
|
- Next.js launcher example: [`examples/nextjs-chat-widget`](examples/nextjs-chat-widget)
|
|
27
49
|
|
|
28
50
|
```tsx
|
|
29
|
-
import { ChatEmiLauncher, ChatEmiMessenger, ChatEmiProvider, useChatEmi } from "chatemi";
|
|
30
|
-
import "chatemi/styles.css";
|
|
51
|
+
import { ChatEmiLauncher, ChatEmiMessenger, ChatEmiProvider, useChatEmi } from "@mademi_dev/chatemi";
|
|
52
|
+
import "@mademi_dev/chatemi/styles.css";
|
|
31
53
|
|
|
32
54
|
export function App() {
|
|
33
55
|
return (
|
|
@@ -62,7 +84,7 @@ export function App() {
|
|
|
62
84
|
Import package CSS once from `app/layout.tsx`:
|
|
63
85
|
|
|
64
86
|
```tsx
|
|
65
|
-
import "chatemi/styles.css";
|
|
87
|
+
import "@mademi_dev/chatemi/styles.css";
|
|
66
88
|
import type { Metadata } from "next";
|
|
67
89
|
|
|
68
90
|
export const metadata: Metadata = {
|
|
@@ -83,7 +105,7 @@ Create a client component for the widget:
|
|
|
83
105
|
```tsx
|
|
84
106
|
"use client";
|
|
85
107
|
|
|
86
|
-
import { ChatEmiLauncher, ChatEmiProvider } from "chatemi";
|
|
108
|
+
import { ChatEmiLauncher, ChatEmiProvider } from "@mademi_dev/chatemi";
|
|
87
109
|
|
|
88
110
|
export function ChatWidget() {
|
|
89
111
|
return (
|
|
@@ -114,10 +136,50 @@ export function ChatWidget() {
|
|
|
114
136
|
|
|
115
137
|
Then render `<ChatWidget />` from any client component or include it in a page layout. The provider keeps the socket connected while the launcher modal is closed, so incoming `notification` and `message.created` events continue updating the badge in the background.
|
|
116
138
|
|
|
139
|
+
## Provider configuration
|
|
140
|
+
|
|
141
|
+
`ChatEmiProvider` accepts:
|
|
142
|
+
|
|
143
|
+
| Prop | Default | Purpose |
|
|
144
|
+
| --- | --- | --- |
|
|
145
|
+
| `config` | required | API, socket, auth, theme, and notification settings |
|
|
146
|
+
| `autoConnect` | `true` | Connect the socket on mount when `socketUrl` is set |
|
|
147
|
+
| `initialConversations` | `[]` | Seed conversation list before the first REST fetch |
|
|
148
|
+
| `initialActiveConversationId` | first conversation | Pre-select a conversation |
|
|
149
|
+
| `initialNotifications` | `[]` | Seed notification tray state |
|
|
150
|
+
|
|
151
|
+
`config` fields:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
type ChatEmiConfig = {
|
|
155
|
+
apiBaseUrl: string;
|
|
156
|
+
socketUrl?: string;
|
|
157
|
+
token?: string | (() => string | Promise<string | undefined> | undefined);
|
|
158
|
+
headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
|
159
|
+
currentUser?: ChatEmiUser;
|
|
160
|
+
fetchImpl?: typeof fetch;
|
|
161
|
+
websocketFactory?: (url: string) => WebSocket;
|
|
162
|
+
endpoints?: ChatEmiEndpointOverrides;
|
|
163
|
+
userDirectory?: ChatEmiUserDirectoryConfig;
|
|
164
|
+
theme?: ChatEmiTheme;
|
|
165
|
+
notifications?: ChatEmiNotificationConfig;
|
|
166
|
+
reconnect?: {
|
|
167
|
+
enabled?: boolean;
|
|
168
|
+
maxAttempts?: number;
|
|
169
|
+
initialDelayMs?: number;
|
|
170
|
+
maxDelayMs?: number;
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The socket appends `?token=<token>` to `socketUrl` when a token is configured. Override any REST path with `config.endpoints`.
|
|
176
|
+
|
|
117
177
|
## Hook usage
|
|
118
178
|
|
|
179
|
+
`useChatEmi()` returns provider state, low-level clients, and actions:
|
|
180
|
+
|
|
119
181
|
```tsx
|
|
120
|
-
import { useChatEmi } from "chatemi";
|
|
182
|
+
import { useChatEmi } from "@mademi_dev/chatemi";
|
|
121
183
|
|
|
122
184
|
export function SendWelcomeButton({ conversationId }: { conversationId: string }) {
|
|
123
185
|
const { actions, activeMessages, connectionStatus } = useChatEmi();
|
|
@@ -138,6 +200,51 @@ export function SendWelcomeButton({ conversationId }: { conversationId: string }
|
|
|
138
200
|
}
|
|
139
201
|
```
|
|
140
202
|
|
|
203
|
+
State highlights:
|
|
204
|
+
|
|
205
|
+
- `currentUser`, `conversations`, `activeConversation`, `activeMessages`
|
|
206
|
+
- `notifications`, `unreadNotificationCount`
|
|
207
|
+
- `typingByConversation`, `presenceByUser`
|
|
208
|
+
- `connectionStatus`, `loading`, `error`, `theme`
|
|
209
|
+
- `api`, `socket`
|
|
210
|
+
|
|
211
|
+
Action highlights:
|
|
212
|
+
|
|
213
|
+
- Conversations: `refreshConversations`, `openConversation`, `createConversation`
|
|
214
|
+
- Messages: `sendMessage`, `editMessage`, `deleteMessage`, `forwardMessage`, `searchMessages`
|
|
215
|
+
- Receipts: `markRead`, `markDelivered`
|
|
216
|
+
- Reactions and media: `addReaction`, `removeReaction`, `uploadAttachment`, `updateAvatar`
|
|
217
|
+
- Members: `addMembers`, `updateMember`, `removeMember`
|
|
218
|
+
- Presence: `startTyping`, `stopTyping`, `setPresence`
|
|
219
|
+
- Notifications: `dismissNotification`, `markNotificationsRead`, `clearNotifications`, `requestNotificationPermission`
|
|
220
|
+
- Connection: `connect`, `disconnect`
|
|
221
|
+
|
|
222
|
+
## Low-level clients
|
|
223
|
+
|
|
224
|
+
You can use the typed clients outside React when needed:
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
import { ChatEmiApi, ChatEmiSocket } from "@mademi_dev/chatemi";
|
|
228
|
+
|
|
229
|
+
const api = new ChatEmiApi({
|
|
230
|
+
apiBaseUrl: "https://api.example.com/chat",
|
|
231
|
+
token: () => getAccessToken()
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const socket = new ChatEmiSocket({
|
|
235
|
+
apiBaseUrl: "https://api.example.com/chat",
|
|
236
|
+
socketUrl: "wss://api.example.com/chat/socket",
|
|
237
|
+
token: () => getAccessToken()
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
await socket.connect();
|
|
241
|
+
socket.on("message.created", (message) => {
|
|
242
|
+
console.log(message);
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
`ChatEmiApiError` is exported for typed REST error handling.
|
|
247
|
+
|
|
141
248
|
## Expected REST API contract
|
|
142
249
|
|
|
143
250
|
By default ChatEmi calls these paths under `apiBaseUrl`:
|
|
@@ -159,7 +266,7 @@ By default ChatEmi calls these paths under `apiBaseUrl`:
|
|
|
159
266
|
| Attachment upload | `POST /attachments` multipart form data |
|
|
160
267
|
| Search | `GET /search/messages?q=...` |
|
|
161
268
|
|
|
162
|
-
If your backend uses different paths, pass `config.endpoints` to override any route.
|
|
269
|
+
If your backend uses different paths, pass `config.endpoints` to override any route. See [`docs/BACKEND_CONTRACT.md`](docs/BACKEND_CONTRACT.md) for request/response shapes.
|
|
163
270
|
|
|
164
271
|
## Socket event contract
|
|
165
272
|
|
|
@@ -211,12 +318,27 @@ Use `ChatEmiLauncher` when you want a floating in-app messenger:
|
|
|
211
318
|
theme="midnight"
|
|
212
319
|
title="Messages"
|
|
213
320
|
subtitle="Team chat"
|
|
321
|
+
defaultOpen={false}
|
|
322
|
+
showNotificationList
|
|
323
|
+
markNotificationsReadOnOpen
|
|
214
324
|
initialSize={{ width: 460, height: 720 }}
|
|
215
325
|
minSize={{ width: 360, height: 520 }}
|
|
216
326
|
maxSize={{ width: 960, height: 860 }}
|
|
217
327
|
/>
|
|
218
328
|
```
|
|
219
329
|
|
|
330
|
+
Launcher props:
|
|
331
|
+
|
|
332
|
+
| Prop | Default | Purpose |
|
|
333
|
+
| --- | --- | --- |
|
|
334
|
+
| `placement` | `bottom-right` | `bottom-right`, `bottom-left`, `top-right`, or `top-left` |
|
|
335
|
+
| `defaultOpen` | `false` | Whether the modal starts open |
|
|
336
|
+
| `showNotificationList` | `true` | Show the compact tray above the chat |
|
|
337
|
+
| `markNotificationsReadOnOpen` | `true` | Mark notifications read when opening |
|
|
338
|
+
| `badgeCount` | unread notifications or conversation unread total | Override launcher badge count |
|
|
339
|
+
| `launcherIcon` | built-in icon | Custom toggle button content |
|
|
340
|
+
| `initialSize` / `minSize` / `maxSize` | `420x680` / `340x480` / `920x860` | Desktop modal sizing |
|
|
341
|
+
|
|
220
342
|
The launcher includes:
|
|
221
343
|
|
|
222
344
|
- toggle button with unread notification badge
|
|
@@ -379,11 +501,11 @@ Use `config.userDirectory` when users live outside your chat backend. ChatEmi wi
|
|
|
379
501
|
MongoDB must be connected from your API server, not from browser React code. Install the optional peer dependency in your backend:
|
|
380
502
|
|
|
381
503
|
```bash
|
|
382
|
-
npm install chatemi mongodb
|
|
504
|
+
npm install @mademi_dev/chatemi mongodb
|
|
383
505
|
```
|
|
384
506
|
|
|
385
507
|
```ts
|
|
386
|
-
import { createChatEmiMongoConnection } from "chatemi/server";
|
|
508
|
+
import { createChatEmiMongoConnection, ensureChatEmiIndexes } from "@mademi_dev/chatemi/server";
|
|
387
509
|
|
|
388
510
|
const chatDb = await createChatEmiMongoConnection({
|
|
389
511
|
uri: process.env.MONGODB_URI!,
|
|
@@ -405,6 +527,16 @@ export const receipts = chatDb.collections.receipts;
|
|
|
405
527
|
export const attachments = chatDb.collections.attachments;
|
|
406
528
|
```
|
|
407
529
|
|
|
530
|
+
Default collection names:
|
|
531
|
+
|
|
532
|
+
- `chatemi_conversations`
|
|
533
|
+
- `chatemi_messages`
|
|
534
|
+
- `chatemi_members`
|
|
535
|
+
- `chatemi_receipts`
|
|
536
|
+
- `chatemi_attachments`
|
|
537
|
+
|
|
538
|
+
Override them with `collectionNames` when creating the connection. The helper reuses one MongoDB client per process and URI/options pair.
|
|
539
|
+
|
|
408
540
|
Connection guidance:
|
|
409
541
|
|
|
410
542
|
- Create one MongoDB client per server process and reuse it.
|
|
@@ -422,6 +554,18 @@ Use `theme="light"`, `theme="dark"`, `theme="system"`, `theme="midnight"`, `them
|
|
|
422
554
|
|
|
423
555
|
## Customizing the UI
|
|
424
556
|
|
|
557
|
+
`ChatEmiMessenger` props:
|
|
558
|
+
|
|
559
|
+
| Prop | Default | Purpose |
|
|
560
|
+
| --- | --- | --- |
|
|
561
|
+
| `showSidebar` | `true` | Conversation list sidebar |
|
|
562
|
+
| `enableAdminControls` | `true` | Member management panel for admins |
|
|
563
|
+
| `enableMessageActions` | `true` | Reply, react, and forward actions |
|
|
564
|
+
| `enableMediaPreview` | `true` | Inline image/video/audio previews |
|
|
565
|
+
| `composerPlaceholder` | `Write a message...` | Composer input placeholder |
|
|
566
|
+
| `renderConversation` | built-in row | Custom conversation list item |
|
|
567
|
+
| `renderMessage` | built-in bubble | Custom message renderer |
|
|
568
|
+
|
|
425
569
|
```tsx
|
|
426
570
|
<ChatEmiMessenger
|
|
427
571
|
composerPlaceholder="Message the team"
|
|
@@ -442,10 +586,12 @@ npm run typecheck
|
|
|
442
586
|
npm run build
|
|
443
587
|
```
|
|
444
588
|
|
|
589
|
+
`prepublishOnly` runs `npm run build` automatically before publish.
|
|
590
|
+
|
|
445
591
|
## Publishing
|
|
446
592
|
|
|
447
|
-
Update the version in `package.json`, then run:
|
|
593
|
+
This package is published under the `@mademi_dev` scope. Update the version in `package.json`, then run:
|
|
448
594
|
|
|
449
595
|
```bash
|
|
450
|
-
npm publish
|
|
596
|
+
npm publish --access public
|
|
451
597
|
```
|
package/dist/chatemi.js
CHANGED
|
@@ -28,7 +28,7 @@ var d = {
|
|
|
28
28
|
config;
|
|
29
29
|
fetcher;
|
|
30
30
|
constructor(e) {
|
|
31
|
-
this.config = e, this.fetcher = e.fetchImpl ?? fetch;
|
|
31
|
+
this.config = e, this.fetcher = e.fetchImpl ?? ((e, t) => fetch(e, t));
|
|
32
32
|
}
|
|
33
33
|
async getMe(e) {
|
|
34
34
|
return this.request(this.endpoint("me"), { signal: e });
|
|
@@ -1502,10 +1502,8 @@ function Z({ className: e, title: t = "Messages", subtitle: n = "ChatEmi", place
|
|
|
1502
1502
|
y: 0
|
|
1503
1503
|
}), k = o(void 0), A = i(() => y.reduce((e, t) => e + (t.unreadCount ?? 0), 0), [y]), j = d ?? (w > 0 ? w : A);
|
|
1504
1504
|
function M() {
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
return t && h && v.markNotificationsRead(), t && v.requestNotificationPermission(), t;
|
|
1508
|
-
});
|
|
1505
|
+
let e = !T;
|
|
1506
|
+
E(e), e && (h && v.markNotificationsRead(), v.requestNotificationPermission());
|
|
1509
1507
|
}
|
|
1510
1508
|
function P(e) {
|
|
1511
1509
|
e.target.closest("button") || (e.currentTarget.setPointerCapture(e.pointerId), k.current = {
|
package/dist/chatemi.umd.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("react"),require("react/jsx-runtime")):typeof define==`function`&&define.amd?define([`exports`,`react`,`react/jsx-runtime`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.ChatEmi={},e.React,e.jsxRuntime))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r={me:`/me`,users:`/users`,user:e=>`/users/${encodeURIComponent(e)}`,conversations:`/conversations`,conversation:e=>`/conversations/${encodeURIComponent(e)}`,conversationAvatar:e=>`/conversations/${encodeURIComponent(e)}/avatar`,members:e=>`/conversations/${encodeURIComponent(e)}/members`,member:(e,t)=>`/conversations/${encodeURIComponent(e)}/members/${encodeURIComponent(t)}`,messages:e=>`/conversations/${encodeURIComponent(e)}/messages`,message:(e,t)=>`/conversations/${encodeURIComponent(e)}/messages/${encodeURIComponent(t)}`,markRead:e=>`/conversations/${encodeURIComponent(e)}/read`,markDelivered:e=>`/conversations/${encodeURIComponent(e)}/delivered`,forwardMessage:(e,t)=>`/conversations/${encodeURIComponent(e)}/messages/${encodeURIComponent(t)}/forward`,reactions:(e,t)=>`/conversations/${encodeURIComponent(e)}/messages/${encodeURIComponent(t)}/reactions`,upload:`/attachments`,search:`/search/messages`},i=class extends Error{status;payload;constructor(e,t,n){super(e),this.name=`ChatEmiApiError`,this.status=t,this.payload=n}},a=class{config;fetcher;constructor(e){this.config=e,this.fetcher=e.fetchImpl??fetch}async getMe(e){return this.request(this.endpoint(`me`),{signal:e})}async searchUsers(e,t){if(this.config.userDirectory){let n=this.config.userDirectory.searchPath??`/users`;return o(await this.request(n,{query:{q:e.query,cursor:e.cursor,limit:e.limit},signal:t,baseUrl:this.config.userDirectory.baseUrl,headers:await this.resolveUserDirectoryHeaders(),authorize:!1}),this.config.userDirectory.mapUser)}return this.request(this.endpoint(`users`),{query:{q:e.query,cursor:e.cursor,limit:e.limit},signal:t})}async getUser(e,t){if(this.config.userDirectory){let n=this.config.userDirectory.userPath?.(e)??`/users/${encodeURIComponent(e)}`,r=await this.request(n,{signal:t,baseUrl:this.config.userDirectory.baseUrl,headers:await this.resolveUserDirectoryHeaders(),authorize:!1});return this.config.userDirectory.mapUser?this.config.userDirectory.mapUser(r):r}return this.request(this.endpoint(`user`,e),{signal:t})}async listConversations(e={},t){return this.request(this.endpoint(`conversations`),{query:{...e},signal:t})}async getConversation(e,t){return this.request(this.endpoint(`conversation`,e),{signal:t})}async createConversation(e){return this.request(this.endpoint(`conversations`),{method:`POST`,body:e})}async createGroup(e){return this.createConversation({...e,type:`group`})}async createChannel(e){return this.createConversation({...e,type:`channel`,readOnly:e.readOnly??!0})}async updateConversation(e,t){return this.request(this.endpoint(`conversation`,e),{method:`PATCH`,body:t})}async archiveConversation(e){await this.request(this.endpoint(`conversation`,e),{method:`DELETE`})}async updateConversationAvatar(e){if(e.conversationId)return this.request(this.endpoint(`conversationAvatar`,e.conversationId),{method:`PATCH`,body:{attachment:e.attachment}});if(!e.userId)throw Error(`ChatEmi avatar update requires conversationId or userId`);return this.request(this.endpoint(`user`,e.userId),{method:`PATCH`,body:{avatar:e.attachment}})}async addMembers(e,t){return this.request(this.endpoint(`members`,e),{method:`POST`,body:{userIds:t}})}async updateMember(e){return this.request(this.endpoint(`member`,e.conversationId,e.userId),{method:`PATCH`,body:e})}async removeMember(e){await this.request(this.endpoint(`member`,e.conversationId,e.userId),{method:`DELETE`})}async listMessages(e,t={},n){return this.request(this.endpoint(`messages`,e),{query:{...t},signal:n})}async sendMessage(e){return this.request(this.endpoint(`messages`,e.conversationId),{method:`POST`,body:e})}async forwardMessage(e){return this.request(this.endpoint(`forwardMessage`,e.sourceConversationId,e.messageId),{method:`POST`,body:e})}async editMessage(e){return this.request(this.endpoint(`message`,e.conversationId,e.messageId),{method:`PATCH`,body:e})}async deleteMessage(e,t){await this.request(this.endpoint(`message`,e,t),{method:`DELETE`})}async markConversationRead(e,t){await this.request(this.endpoint(`markRead`,e),{method:`POST`,body:{messageIds:t}})}async markConversationDelivered(e,t){await this.request(this.endpoint(`markDelivered`,e),{method:`POST`,body:{messageIds:t}})}async addReaction(e,t,n){return this.request(this.endpoint(`reactions`,e,t),{method:`POST`,body:{emoji:n}})}async removeReaction(e,t,n){return this.request(this.endpoint(`reactions`,e,t),{method:`DELETE`,body:{emoji:n}})}async uploadAttachment(e){let t=new FormData;return t.append(`file`,e.file,e.name),e.conversationId&&t.append(`conversationId`,e.conversationId),e.type&&t.append(`type`,e.type),e.metadata&&t.append(`metadata`,JSON.stringify(e.metadata)),this.request(this.endpoint(`upload`),{method:`POST`,body:t})}async searchMessages(e,t={},n){return this.request(this.endpoint(`search`),{query:{q:e,...t},signal:n})}endpoint(e,t,n){switch(e){case`user`:case`conversation`:case`conversationAvatar`:case`members`:case`messages`:case`markRead`:case`markDelivered`:return(this.config.endpoints?.[e]??r[e])(t??``);case`member`:case`message`:case`forwardMessage`:case`reactions`:return(this.config.endpoints?.[e]??r[e])(t??``,n??``);default:return this.config.endpoints?.[e]??r[e]}}async request(e,t={}){let n=this.buildUrl(e,t.query,t.baseUrl),r=new Headers(await this.resolveHeaders(t.headers,t.authorize??!0)),a=typeof FormData<`u`&&t.body instanceof FormData;!a&&t.body!==void 0&&!r.has(`Content-Type`)&&r.set(`Content-Type`,`application/json`);let o=await this.fetcher(n,{method:t.method??`GET`,headers:r,body:a?t.body:t.body===void 0?void 0:JSON.stringify(t.body),signal:t.signal});if(o.status===204)return;let s=await this.parseResponse(o);if(!o.ok)throw new i(this.errorMessage(s,o.status),o.status,s);return s}buildUrl(e,t,n=this.config.apiBaseUrl){let r=/^https?:\/\//i.test(e),i=n.replace(/\/+$/,``),a=e.startsWith(`/`)?e:`/${e}`,o=new URL(r?e:`${i}${a}`);return Object.entries(t??{}).forEach(([e,t])=>{t!=null&&t!==``&&o.searchParams.set(e,String(t))}),o.toString()}async resolveHeaders(e,t=!0){let n=new Headers,r=typeof this.config.headers==`function`?await this.config.headers():this.config.headers,i=typeof this.config.token==`function`?await this.config.token():this.config.token;return new Headers(r).forEach((e,t)=>n.set(t,e)),new Headers(e).forEach((e,t)=>n.set(t,e)),t&&i&&!n.has(`Authorization`)&&n.set(`Authorization`,`Bearer ${i}`),n}async resolveUserDirectoryHeaders(){return(typeof this.config.userDirectory?.headers==`function`?await this.config.userDirectory.headers():this.config.userDirectory?.headers)??{}}async parseResponse(e){return(e.headers.get(`Content-Type`)??``).includes(`application/json`)?e.json():e.text()}errorMessage(e,t){return e&&typeof e==`object`&&`message`in e&&typeof e.message==`string`?e.message:`ChatEmi API request failed with status ${t}`}};function o(e,t){let n=e=>t?t(e):e;return Array.isArray(e)?{items:e.map(n)}:{...e,items:e.items.map(n)}}var s=500,c=8e3,l=1/0,u=class{config;socket;reconnectAttempts=0;shouldReconnect=!0;reconnectTimer;queue=[];listeners=new Map;constructor(e){this.config=e}get readyState(){return this.socket?.readyState}async connect(){this.config.socketUrl&&(this.socket?.readyState===WebSocket.OPEN||this.socket?.readyState===WebSocket.CONNECTING||(this.shouldReconnect=this.config.reconnect?.enabled??!0,this.createSocket(await this.buildSocketUrl())))}disconnect(){this.shouldReconnect=!1,this.clearReconnectTimer(),this.socket?.close(),this.socket=void 0}on(e,t){let n=this.listeners.get(e)??new Set;return n.add(t),this.listeners.set(e,n),()=>{n.delete(t),n.size===0&&this.listeners.delete(e)}}send(e,t,n){let r={type:e,payload:t,requestId:n};if(this.socket?.readyState===WebSocket.OPEN){this.socket.send(JSON.stringify(r));return}this.queue.push(r)}subscribeConversation(e){this.send(`conversation.subscribe`,{conversationId:e})}unsubscribeConversation(e){this.send(`conversation.unsubscribe`,{conversationId:e})}sendTyping(e,t){this.send(`typing`,{conversationId:e,isTyping:t})}sendReadReceipt(e,t){this.send(`message.read`,{conversationId:e,messageIds:t})}sendDeliveredReceipt(e,t){this.send(`message.delivered`,{conversationId:e,messageIds:t})}sendForward(e){this.send(`message.forward`,e)}sendMemberUpdate(e){this.send(`conversation.member.update`,e)}sendAvatarUpdate(e){this.send(`conversation.avatar.update`,{conversationId:e})}sendPresence(e){this.send(`presence`,{status:e})}createSocket(e){let t=(this.config.websocketFactory??(e=>new WebSocket(e)))(e);this.socket=t,t.addEventListener(`open`,()=>{this.reconnectAttempts=0,this.dispatch(`connected`,void 0),this.flushQueue()}),t.addEventListener(`close`,e=>{this.dispatch(`disconnected`,e),this.socket=void 0,this.scheduleReconnect()}),t.addEventListener(`error`,e=>{this.dispatch(`error`,e)}),t.addEventListener(`message`,e=>{this.handleMessage(e.data)})}handleMessage(e){try{let t=typeof e==`string`?JSON.parse(e):e;t&&typeof t.type==`string`&&this.dispatchRaw(t.type,t.payload)}catch(e){this.dispatch(`error`,e instanceof Error?e:Error(`Unable to parse ChatEmi socket message`))}}flushQueue(){for(;this.queue.length>0&&this.socket?.readyState===WebSocket.OPEN;){let e=this.queue.shift();e&&this.socket.send(JSON.stringify(e))}}scheduleReconnect(){if(!this.shouldReconnect||!this.config.socketUrl)return;let e=this.config.reconnect?.maxAttempts??l;if(this.reconnectAttempts>=e)return;this.reconnectAttempts+=1;let t=this.config.reconnect?.initialDelayMs??s,n=this.config.reconnect?.maxDelayMs??c,r=Math.min(t*2**(this.reconnectAttempts-1),n);this.dispatch(`reconnecting`,{attempt:this.reconnectAttempts,delay:r}),this.clearReconnectTimer(),this.reconnectTimer=setTimeout(()=>{this.connect()},r)}async buildSocketUrl(){let e=this.config.socketUrl;if(!e)throw Error(`ChatEmi socketUrl is required before connecting the socket`);let t=new URL(e),n=typeof this.config.token==`function`?await this.config.token():this.config.token;return n&&t.searchParams.set(`token`,n),t.toString()}dispatch(e,t){this.listeners.get(e)?.forEach(e=>e(t))}dispatchRaw(e,t){this.listeners.get(e)?.forEach(e=>e(t))}clearReconnectTimer(){this.reconnectTimer&&=(clearTimeout(this.reconnectTimer),void 0)}},d=(0,t.createContext)(void 0);function f(e,t=[],n,r=[]){return{currentUser:e.currentUser,conversations:t,activeConversationId:n??t[0]?.id,messagesByConversation:{},typingByConversation:{},presenceByUser:{},notifications:r,unreadNotificationCount:r.filter(e=>!e.read).length,connectionStatus:`idle`,theme:e.theme??`light`,loading:!1}}function p(e,t){switch(t.type){case`set-loading`:return{...e,loading:t.loading};case`set-error`:return{...e,error:t.error};case`set-connection-status`:return{...e,connectionStatus:t.status};case`set-current-user`:return{...e,currentUser:t.user};case`set-conversations`:return{...e,conversations:b(t.conversations),activeConversationId:e.activeConversationId??t.conversations[0]?.id};case`upsert-conversation`:return{...e,conversations:b(g(e.conversations,t.conversation))};case`remove-conversation`:return{...e,conversations:e.conversations.filter(e=>e.id!==t.conversationId),activeConversationId:e.activeConversationId===t.conversationId?void 0:e.activeConversationId};case`upsert-member`:return{...e,conversations:e.conversations.map(e=>e.id===t.conversationId?{...e,members:_(e.members??[],t.member),participants:g(e.participants,t.member.user)}:e)};case`remove-member`:return{...e,conversations:e.conversations.map(e=>e.id===t.conversationId?{...e,members:(e.members??[]).filter(e=>e.user.id!==t.userId),participants:e.participants.filter(e=>e.id!==t.userId)}:e)};case`set-active-conversation`:return{...e,activeConversationId:t.conversationId};case`set-messages`:return{...e,messagesByConversation:{...e.messagesByConversation,[t.conversationId]:x(t.messages)}};case`upsert-message`:{let n=e.messagesByConversation[t.message.conversationId]??[],r=S(e.conversations,t.message);return{...e,conversations:r,messagesByConversation:{...e.messagesByConversation,[t.message.conversationId]:x(g(n,t.message))}}}case`remove-message`:return{...e,messagesByConversation:{...e.messagesByConversation,[t.conversationId]:(e.messagesByConversation[t.conversationId]??[]).filter(e=>e.id!==t.messageId)}};case`set-message-reactions`:return{...e,messagesByConversation:{...e.messagesByConversation,[t.conversationId]:(e.messagesByConversation[t.conversationId]??[]).map(e=>e.id===t.messageId?{...e,reactions:t.reactions}:e)}};case`apply-receipt`:return{...e,messagesByConversation:{...e.messagesByConversation,[t.receipt.conversationId]:(e.messagesByConversation[t.receipt.conversationId]??[]).map(e=>t.receipt.messageIds.includes(e.id)?v(e,t.receipt):e)}};case`add-notification`:{let n=(e.notifications.some(e=>e.id===t.notification.id)?e.notifications.map(e=>e.id===t.notification.id?t.notification:e):[t.notification,...e.notifications]).slice(0,t.maxStored??50);return{...e,notifications:n,unreadNotificationCount:n.filter(e=>!e.read).length}}case`dismiss-notification`:{let n=e.notifications.filter(e=>e.id!==t.notificationId);return{...e,notifications:n,unreadNotificationCount:n.filter(e=>!e.read).length}}case`mark-notifications-read`:{let n=t.notificationIds?new Set(t.notificationIds):void 0,r=e.notifications.map(e=>!n||n.has(e.id)?{...e,read:!0}:e);return{...e,notifications:r,unreadNotificationCount:r.filter(e=>!e.read).length}}case`clear-notifications`:return{...e,notifications:[],unreadNotificationCount:0};case`set-typing`:{let n=(e.typingByConversation[t.event.conversationId]??[]).filter(e=>e.user.id!==t.event.user.id);return{...e,typingByConversation:{...e.typingByConversation,[t.event.conversationId]:t.event.isTyping?[...n,t.event]:n}}}case`set-presence`:return{...e,presenceByUser:{...e.presenceByUser,[t.userId]:{userId:t.userId,status:t.status,lastSeenAt:t.lastSeenAt}}};default:return e}}function m({children:e,config:r,autoConnect:i=!0,initialConversations:o,initialActiveConversationId:s,initialNotifications:c}){let l=(0,t.useMemo)(()=>new a(r),[r]),m=(0,t.useMemo)(()=>new u(r),[r]),h=(0,t.useRef)({activeConversationId:s,currentUserId:r.currentUser?.id}),g=(0,t.useRef)(new Set),[_,v]=(0,t.useReducer)(p,f(r,o,s,c));(0,t.useEffect)(()=>{h.current={activeConversationId:_.activeConversationId,currentUserId:_.currentUser?.id}},[_.activeConversationId,_.currentUser?.id]),(0,t.useEffect)(()=>{let e=!0,t=new AbortController;v({type:`set-current-user`,user:r.currentUser});async function n(){v({type:`set-loading`,loading:!0}),v({type:`set-error`,error:void 0});try{let[n,i]=await Promise.all([r.currentUser?Promise.resolve(r.currentUser):l.getMe(t.signal).catch(()=>void 0),l.listConversations({},t.signal)]);if(!e)return;v({type:`set-current-user`,user:n}),v({type:`set-conversations`,conversations:i.items})}catch(t){e&&v({type:`set-error`,error:w(t)})}finally{e&&v({type:`set-loading`,loading:!1})}}return n(),()=>{e=!1,t.abort()}},[l,r.currentUser]),(0,t.useEffect)(()=>{let e=[m.on(`connected`,()=>v({type:`set-connection-status`,status:`connected`})),m.on(`disconnected`,()=>v({type:`set-connection-status`,status:`disconnected`})),m.on(`reconnecting`,()=>v({type:`set-connection-status`,status:`reconnecting`})),m.on(`error`,e=>{v({type:`set-connection-status`,status:`error`}),v({type:`set-error`,error:w(e)})}),m.on(`conversation.created`,e=>v({type:`upsert-conversation`,conversation:e})),m.on(`conversation.updated`,e=>v({type:`upsert-conversation`,conversation:e})),m.on(`conversation.deleted`,({conversationId:e})=>v({type:`remove-conversation`,conversationId:e})),m.on(`conversation.member.added`,({conversationId:e,member:t})=>v({type:`upsert-member`,conversationId:e,member:t})),m.on(`conversation.member.updated`,({conversationId:e,member:t})=>v({type:`upsert-member`,conversationId:e,member:t})),m.on(`conversation.member.removed`,({conversationId:e,userId:t})=>v({type:`remove-member`,conversationId:e,userId:t})),m.on(`message.created`,e=>{v({type:`upsert-message`,message:e}),e.sender.id!==h.current.currentUserId&&v({type:`add-notification`,notification:C(e),maxStored:r.notifications?.maxStored})}),m.on(`message.updated`,e=>v({type:`upsert-message`,message:e})),m.on(`message.deleted`,({conversationId:e,messageId:t})=>v({type:`remove-message`,conversationId:e,messageId:t})),m.on(`message.receipt`,e=>v({type:`apply-receipt`,receipt:e})),m.on(`message.reaction`,({conversationId:e,messageId:t,reactions:n})=>v({type:`set-message-reactions`,conversationId:e,messageId:t,reactions:n})),m.on(`typing`,e=>v({type:`set-typing`,event:e})),m.on(`presence`,({userId:e,status:t,lastSeenAt:n})=>v({type:`set-presence`,userId:e,status:t,lastSeenAt:n})),m.on(`notification`,e=>v({type:`add-notification`,notification:e,maxStored:r.notifications?.maxStored}))];return i&&r.socketUrl&&(v({type:`set-connection-status`,status:`connecting`}),m.connect()),()=>{e.forEach(e=>e()),m.disconnect()}},[i,r.notifications?.maxStored,r.socketUrl,m]),(0,t.useEffect)(()=>{!r.notifications?.enabled||!r.notifications.browser||typeof window>`u`||!(`Notification`in window)||window.Notification.permission!==`granted`||(r.notifications.showWhenOpen||typeof document>`u`||document.hidden)&&_.notifications.forEach(e=>{e.read||g.current.has(e.id)||(g.current.add(e.id),new window.Notification(e.title||r.notifications?.title||`New message`,{body:e.body,icon:e.avatarUrl}))})},[r.notifications,_.notifications]);let y=(0,t.useCallback)(async(e={})=>{v({type:`set-loading`,loading:!0});try{let t=await l.listConversations(e);return v({type:`set-conversations`,conversations:t.items}),t.items}finally{v({type:`set-loading`,loading:!1})}},[l]),b=(0,t.useCallback)(async(e,t={})=>{v({type:`set-active-conversation`,conversationId:e}),m.subscribeConversation(e);let n=await l.listMessages(e,t);return v({type:`set-messages`,conversationId:e,messages:n.items}),l.markConversationDelivered(e,n.items.filter(e=>e.sender.id!==_.currentUser?.id).map(e=>e.id)),n.items},[l,m,_.currentUser?.id]),x=(0,t.useCallback)(async e=>{let t=await l.createConversation(e);return v({type:`upsert-conversation`,conversation:t}),v({type:`set-active-conversation`,conversationId:t.id}),t},[l]),S=(0,t.useCallback)(async e=>{let t=`temp-${Date.now()}`,n=_.currentUser?{id:t,conversationId:e.conversationId,sender:_.currentUser,text:e.text,html:e.html,attachments:e.attachments,kind:e.kind,replyToId:e.replyToId,status:`sending`,createdAt:new Date().toISOString(),metadata:e.metadata}:void 0;n&&v({type:`upsert-message`,message:n});try{let r=await l.sendMessage(e);return n&&v({type:`remove-message`,conversationId:e.conversationId,messageId:t}),v({type:`upsert-message`,message:r}),r}catch(e){throw n&&v({type:`upsert-message`,message:{...n,status:`failed`}}),e}},[l,_.currentUser]),T=(0,t.useCallback)(async e=>{let t=await l.editMessage(e);return v({type:`upsert-message`,message:t}),t},[l]),E=(0,t.useCallback)(async(e,t)=>{await l.deleteMessage(e,t),v({type:`remove-message`,conversationId:e,messageId:t})},[l]),D=(0,t.useCallback)(async(e,t)=>{await l.markConversationRead(e,t),m.sendReadReceipt(e,t??[])},[l,m]),O=(0,t.useCallback)(async(e,t)=>{await l.markConversationDelivered(e,t),m.sendDeliveredReceipt(e,t??[])},[l,m]),k=(0,t.useCallback)(async e=>{let t=await l.forwardMessage(e);return m.sendForward(e),v({type:`upsert-message`,message:t}),t},[l,m]),A=(0,t.useCallback)(async(e,t,n)=>{let r=await l.addReaction(e,t,n);return v({type:`upsert-message`,message:r}),r},[l]),j=(0,t.useCallback)(async(e,t,n)=>{let r=await l.removeReaction(e,t,n);return v({type:`upsert-message`,message:r}),r},[l]),M=(0,t.useCallback)(e=>l.uploadAttachment(e),[l]),N=(0,t.useCallback)(async e=>{let t=await l.updateConversationAvatar(e);return`participants`in t?(v({type:`upsert-conversation`,conversation:t}),m.sendAvatarUpdate(t.id)):v({type:`set-current-user`,user:_.currentUser?.id===t.id?t:_.currentUser}),t},[l,m,_.currentUser]),P=(0,t.useCallback)(async(e,t)=>{let n=await l.addMembers(e,t);return n.forEach(t=>v({type:`upsert-member`,conversationId:e,member:t})),n},[l]),F=(0,t.useCallback)(async e=>{let t=await l.updateMember(e);return v({type:`upsert-member`,conversationId:e.conversationId,member:t}),m.sendMemberUpdate(e),t},[l,m]),I=(0,t.useCallback)(async e=>{await l.removeMember(e),v({type:`remove-member`,conversationId:e.conversationId,userId:e.userId})},[l]),L=(0,t.useCallback)(async e=>(await l.searchUsers(e)).items,[l]),R=(0,t.useCallback)(async(e,t={})=>(await l.searchMessages(e,t)).items,[l]),z=(0,t.useCallback)(e=>m.sendTyping(e,!0),[m]),B=(0,t.useCallback)(e=>m.sendTyping(e,!1),[m]),V=(0,t.useCallback)(e=>m.sendPresence(e),[m]),H=(0,t.useCallback)(e=>{v({type:`dismiss-notification`,notificationId:e})},[]),U=(0,t.useCallback)(e=>{v({type:`mark-notifications-read`,notificationIds:e})},[]),W=(0,t.useCallback)(()=>{v({type:`clear-notifications`})},[]),G=(0,t.useCallback)(async()=>typeof window>`u`||!(`Notification`in window)?`unsupported`:window.Notification.permission===`granted`||window.Notification.permission===`denied`?window.Notification.permission:window.Notification.requestPermission(),[]),K=(0,t.useCallback)(()=>m.connect(),[m]),q=(0,t.useCallback)(()=>m.disconnect(),[m]),J=(0,t.useMemo)(()=>({refreshConversations:y,openConversation:b,createConversation:x,sendMessage:S,editMessage:T,deleteMessage:E,markRead:D,markDelivered:O,forwardMessage:k,addReaction:A,removeReaction:j,uploadAttachment:M,updateAvatar:N,addMembers:P,updateMember:F,removeMember:I,searchUsers:L,searchMessages:R,startTyping:z,stopTyping:B,setPresence:V,dismissNotification:H,markNotificationsRead:U,clearNotifications:W,requestNotificationPermission:G,connect:K,disconnect:q}),[y,b,x,S,T,E,D,O,k,A,j,M,N,P,F,I,L,R,z,B,V,H,U,W,G,K,q]),Y=_.conversations.find(e=>e.id===_.activeConversationId),X=_.activeConversationId?_.messagesByConversation[_.activeConversationId]??[]:[],Z=(0,t.useMemo)(()=>({..._,api:l,socket:m,activeConversation:Y,activeMessages:X,actions:J}),[J,Y,X,l,m,_]);return(0,n.jsx)(d.Provider,{value:Z,children:e})}function h(){let e=(0,t.useContext)(d);if(!e)throw Error(`useChatEmi must be used inside a ChatEmiProvider`);return e}function g(e,t){let n=e.findIndex(e=>e.id===t.id);return n===-1?[...e,t]:[...e.slice(0,n),t,...e.slice(n+1)]}function _(e,t){let n=e.findIndex(e=>e.user.id===t.user.id);return n===-1?[...e,t]:[...e.slice(0,n),t,...e.slice(n+1)]}function v(e,t){let n=t.status===`read`?`readBy`:`deliveredTo`,r=y(e[n]??[],t);return{...e,[n]:r,status:t.status===`read`||e.status===`read`?`read`:`delivered`}}function y(e,t){let n=e.findIndex(e=>e.userId===t.userId&&e.status===t.status);return n===-1?[...e,t]:[...e.slice(0,n),t,...e.slice(n+1)]}function b(e){return[...e].sort((e,t)=>{let n=e.lastMessage?.createdAt??e.updatedAt??e.createdAt,r=t.lastMessage?.createdAt??t.updatedAt??t.createdAt;return Date.parse(r)-Date.parse(n)})}function x(e){return[...e].sort((e,t)=>Date.parse(e.createdAt)-Date.parse(t.createdAt))}function S(e,t){return b(e.map(e=>e.id===t.conversationId?{...e,lastMessage:t,updatedAt:t.createdAt}:e))}function C(e){return{id:`message:${e.id}`,kind:`message`,title:e.sender.name,body:e.text??e.attachments?.[0]?.name??`Sent a message`,conversationId:e.conversationId,messageId:e.id,actor:e.sender,avatarUrl:e.sender.avatarUrl,read:!1,createdAt:e.createdAt,metadata:e.metadata}}function w(e){return e instanceof Error?e.message:`Unexpected ChatEmi error`}function T({className:e,emptyState:r,composerPlaceholder:i=`Write a message...`,showSidebar:a=!0,theme:o,enableAdminControls:s=!0,enableMessageActions:c=!0,enableMediaPreview:l=!0,renderConversation:u,renderMessage:d}){let{actions:f,activeConversation:p,activeMessages:m,connectionStatus:g,conversations:_,currentUser:v,error:y,loading:b,theme:x,typingByConversation:S}=h(),[C,w]=(0,t.useState)(``),[T,O]=(0,t.useState)(``),[N,P]=(0,t.useState)(``),[F,z]=(0,t.useState)([]),[B,V]=(0,t.useState)(!1),[H,U]=(0,t.useState)(),[W,G]=(0,t.useState)([]),[K,q]=(0,t.useState)(!1),J=(0,t.useRef)(void 0),Y=o??x,X=(0,t.useMemo)(()=>{let e=T.trim().toLowerCase();return e?_.filter(t=>A(t,v?.id).toLowerCase().includes(e)):_},[_,v?.id,T]),Z=p?S[p.id]??[]:[],Q=(0,t.useMemo)(()=>m.map(e=>e.id).join(`,`),[m]),$=p?.members?.find(e=>e.user.id===v?.id),ee=s&&!!($&&[`owner`,`admin`,`moderator`].includes($.role));(0,t.useEffect)(()=>{if(!p||!Q)return;let e=m.filter(e=>e.sender.id!==v?.id).map(e=>e.id);e.length>0&&f.markRead(p.id,e)},[f,p,m,v?.id,Q]),(0,t.useEffect)(()=>{let e=!0;async function t(){if(N.trim().length<2){z([]);return}let t=await f.searchUsers({query:N.trim(),limit:8});e&&z(t)}return t(),()=>{e=!1}},[f,N]);async function te(e){if(e.preventDefault(),!p||!C.trim()&&W.length===0)return;let t=C.trim(),n=W;w(``),G([]),f.stopTyping(p.id);try{await f.sendMessage({conversationId:p.id,text:t||void 0,attachments:n.length>0?n:void 0,kind:L(n),replyToId:H?.id}),U(void 0)}catch{w(t),G(n)}}async function ne(e){if(e?.length){q(!0);try{let t=await Promise.all(Array.from(e).map(e=>f.uploadAttachment({file:e,name:e.name,type:I(e)})));G(e=>[...e,...t])}finally{q(!1)}}}function re(e){w(e),p&&(f.startTyping(p.id),J.current&&clearTimeout(J.current),J.current=setTimeout(()=>f.stopTyping(p.id),1400))}return(0,n.jsxs)(`section`,{className:[`chatemi`,e].filter(Boolean).join(` `),"data-status":g,"data-theme":Y,children:[a?(0,n.jsxs)(`aside`,{className:`chatemi__sidebar`,"aria-label":`Conversations`,children:[(0,n.jsxs)(`div`,{className:`chatemi__brand`,children:[(0,n.jsxs)(`div`,{children:[(0,n.jsx)(`strong`,{children:`ChatEmi`}),(0,n.jsx)(`span`,{children:M(g)})]}),(0,n.jsx)(`span`,{className:`chatemi__status chatemi__status--${g}`})]}),(0,n.jsxs)(`label`,{className:`chatemi__search`,children:[(0,n.jsx)(`span`,{className:`chatemi__sr-only`,children:`Search conversations`}),(0,n.jsx)(`input`,{value:T,onChange:e=>O(e.target.value),placeholder:`Search chats`})]}),(0,n.jsx)(`div`,{className:`chatemi__conversation-list`,children:X.map(e=>{let t=e.id===p?.id;return(0,n.jsx)(`button`,{className:`chatemi__conversation ${t?`chatemi__conversation--active`:``}`,onClick:()=>void f.openConversation(e.id),type:`button`,children:u?u(e,t):(0,n.jsx)(E,{conversation:e,currentUserId:v?.id})},e.id)})})]}):null,(0,n.jsxs)(`main`,{className:`chatemi__main`,children:[p?(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)(`header`,{className:`chatemi__header`,children:[(0,n.jsx)(k,{conversation:p,currentUserId:v?.id}),(0,n.jsxs)(`div`,{children:[(0,n.jsx)(`strong`,{children:A(p,v?.id)}),(0,n.jsx)(`span`,{children:Z.length>0?`${Z.map(e=>e.user.name).join(`, `)} typing...`:j(p,v?.id)})]}),ee?(0,n.jsx)(`button`,{className:`chatemi__header-action`,onClick:()=>V(e=>!e),type:`button`,children:`Members`}):null]}),B&&p?(0,n.jsxs)(`section`,{className:`chatemi__members`,"aria-label":`Member management`,children:[(0,n.jsx)(`div`,{className:`chatemi__members-list`,children:(p.members??p.participants.map(e=>R(e))).map(e=>(0,n.jsxs)(`div`,{className:`chatemi__member`,children:[e.user.avatarUrl?(0,n.jsx)(`img`,{alt:``,className:`chatemi__member-avatar`,src:e.user.avatarUrl}):(0,n.jsx)(`span`,{className:`chatemi__member-avatar`,children:e.user.name.slice(0,2).toUpperCase()}),(0,n.jsxs)(`span`,{children:[(0,n.jsx)(`strong`,{children:e.user.name}),(0,n.jsx)(`small`,{children:e.role})]}),e.role!==`owner`&&e.user.id!==v?.id?(0,n.jsxs)(`span`,{className:`chatemi__member-actions`,children:[(0,n.jsx)(`button`,{onClick:()=>void f.updateMember({conversationId:p.id,userId:e.user.id,role:`admin`}),type:`button`,children:`Admin`}),(0,n.jsx)(`button`,{onClick:()=>void f.updateMember({conversationId:p.id,userId:e.user.id,role:`member`}),type:`button`,children:`Member`}),(0,n.jsx)(`button`,{onClick:()=>void f.removeMember({conversationId:p.id,userId:e.user.id}),type:`button`,children:`Remove`})]}):null]},e.user.id))}),(0,n.jsxs)(`label`,{className:`chatemi__member-search`,children:[(0,n.jsx)(`span`,{children:`Find users from external directory`}),(0,n.jsx)(`input`,{onChange:e=>P(e.target.value),placeholder:`Search users`,value:N})]}),F.length>0?(0,n.jsx)(`div`,{className:`chatemi__user-results`,children:F.map(e=>(0,n.jsxs)(`button`,{onClick:()=>void f.addMembers(p.id,[e.id]),type:`button`,children:[e.avatarUrl?(0,n.jsx)(`img`,{alt:``,src:e.avatarUrl}):null,`Add `,e.name]},e.id))}):null]}):null,(0,n.jsxs)(`div`,{className:`chatemi__messages`,role:`log`,"aria-live":`polite`,children:[m.map(e=>{let t=e.sender.id===v?.id;return d?(0,n.jsx)(`div`,{className:`chatemi__message-shell`,children:d(e,t)},e.id):(0,n.jsx)(D,{enableActions:c,enableMediaPreview:l,isMine:t,message:e,onForward:e=>{let t=window.prompt(`Forward to conversation id`);t&&f.forwardMessage({sourceConversationId:e.conversationId,targetConversationId:t,messageId:e.id})},onReply:U},e.id)}),m.length===0&&!b?(0,n.jsx)(`div`,{className:`chatemi__empty`,children:`No messages yet. Start the conversation.`}):null]}),W.length>0?(0,n.jsx)(`div`,{className:`chatemi__attachments`,children:W.map(e=>(0,n.jsx)(`button`,{className:`chatemi__attachment-pill`,onClick:()=>G(t=>t.filter(t=>t.id!==e.id)),type:`button`,children:e.name??e.type},e.id))}):null,H?(0,n.jsxs)(`div`,{className:`chatemi__replying`,children:[(0,n.jsxs)(`span`,{children:[`Replying to `,(0,n.jsx)(`strong`,{children:H.sender.name}),`: `,H.text??H.attachments?.[0]?.name??`media`]}),(0,n.jsx)(`button`,{onClick:()=>U(void 0),type:`button`,children:`Cancel`})]}):null,(0,n.jsxs)(`form`,{className:`chatemi__composer`,onSubmit:e=>void te(e),children:[(0,n.jsxs)(`label`,{className:`chatemi__upload`,children:[(0,n.jsx)(`span`,{children:K?`Uploading`:`Attach`}),(0,n.jsx)(`input`,{disabled:K,multiple:!0,onChange:e=>void ne(e.target.files),type:`file`})]}),(0,n.jsx)(`textarea`,{onBlur:()=>f.stopTyping(p.id),onChange:e=>re(e.target.value),placeholder:i,rows:1,value:C}),(0,n.jsx)(`button`,{disabled:K||!C.trim()&&W.length===0,type:`submit`,children:`Send`})]})]}):r??(0,n.jsx)(`div`,{className:`chatemi__empty chatemi__empty--screen`,children:`Select a conversation to start messaging.`}),y?(0,n.jsx)(`div`,{className:`chatemi__error`,children:y}):null]})]})}function E({conversation:e,currentUserId:t}){return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(k,{conversation:e,currentUserId:t}),(0,n.jsxs)(`span`,{className:`chatemi__conversation-body`,children:[(0,n.jsx)(`strong`,{children:A(e,t)}),(0,n.jsx)(`small`,{children:e.lastMessage?.text??e.description??`No messages yet`})]}),e.unreadCount?(0,n.jsx)(`span`,{className:`chatemi__badge`,children:e.unreadCount}):null]})}function D({message:e,isMine:t,enableActions:r,enableMediaPreview:i,onReply:a,onForward:o}){return(0,n.jsxs)(`article`,{className:`chatemi__message ${t?`chatemi__message--mine`:``}`,children:[t?null:(0,n.jsx)(`strong`,{className:`chatemi__message-sender`,children:e.sender.name}),e.forwardedFrom?(0,n.jsxs)(`div`,{className:`chatemi__forwarded`,children:[`Forwarded from `,e.forwardedFrom.name]}):null,e.replyTo?(0,n.jsxs)(`blockquote`,{className:`chatemi__reply-preview`,children:[(0,n.jsx)(`strong`,{children:e.replyTo.sender.name}),(0,n.jsx)(`span`,{children:e.replyTo.text??e.replyTo.attachments?.[0]?.name??`media`})]}):null,e.text?(0,n.jsx)(`p`,{children:e.text}):null,e.html?(0,n.jsx)(`div`,{className:`chatemi__message-html`,dangerouslySetInnerHTML:{__html:e.html}}):null,e.attachments?.length?(0,n.jsx)(`div`,{className:`chatemi__message-attachments`,children:e.attachments.map(e=>O(e,i))}):null,e.reactions?.length?(0,n.jsx)(`div`,{className:`chatemi__reactions`,children:e.reactions.map(e=>(0,n.jsxs)(`span`,{children:[e.emoji,` `,e.count]},e.emoji))}):null,(0,n.jsxs)(`footer`,{children:[(0,n.jsx)(`time`,{dateTime:e.createdAt,children:N(e.createdAt)}),t&&e.status?(0,n.jsx)(`span`,{children:F(e)}):null]}),r?(0,n.jsxs)(`div`,{className:`chatemi__message-actions`,children:[(0,n.jsx)(`button`,{onClick:()=>a(e),type:`button`,children:`Reply`}),(0,n.jsx)(`button`,{onClick:()=>o(e),type:`button`,children:`Forward`})]}):null]})}function O(e,t){return t&&e.type===`image`?(0,n.jsxs)(`a`,{className:`chatemi__media chatemi__media--image`,href:e.url,rel:`noreferrer`,target:`_blank`,children:[(0,n.jsx)(`img`,{alt:e.caption??e.name??`image`,src:e.url}),e.caption?(0,n.jsx)(`span`,{children:e.caption}):null]},e.id):t&&e.type===`video`?(0,n.jsxs)(`figure`,{className:`chatemi__media chatemi__media--video`,children:[(0,n.jsx)(`video`,{controls:!0,poster:e.thumbnailUrl,src:e.url}),(0,n.jsx)(`figcaption`,{children:e.caption??e.name??`Video`})]},e.id):e.type===`voice`||e.type===`audio`?(0,n.jsxs)(`div`,{className:`chatemi__media chatemi__media--audio`,children:[(0,n.jsx)(`span`,{children:e.type===`voice`?`Voice message`:e.name??`Audio`}),(0,n.jsx)(`audio`,{controls:!0,src:e.url})]},e.id):(0,n.jsxs)(`a`,{className:`chatemi__media chatemi__media--file`,href:e.url,rel:`noreferrer`,target:`_blank`,children:[e.thumbnailUrl?(0,n.jsx)(`img`,{alt:``,src:e.thumbnailUrl}):null,(0,n.jsx)(`span`,{children:e.name??e.type}),e.size?(0,n.jsx)(`small`,{children:z(e.size)}):null]},e.id)}function k({conversation:e,currentUserId:t}){let r=A(e,t),i=e.participants.find(e=>e.id!==t)??e.participants[0],a=e.avatarUrl??i?.avatarUrl;return a?(0,n.jsx)(`img`,{alt:``,className:`chatemi__avatar`,src:a}):(0,n.jsx)(`span`,{className:`chatemi__avatar chatemi__avatar--fallback`,children:r.slice(0,2).toUpperCase()})}function A(e,t){return e.title?e.title:e.participants.filter(e=>e.id!==t).map(e=>e.name).join(`, `)||`Untitled chat`}function j(e,t){if(e.type===`channel`)return`${e.participants.length} subscribers`;if(e.type===`group`)return`${e.participants.length} members`;let n=e.participants.find(e=>e.id!==t);return n?n.presence===`online`?`online`:n.lastSeenAt?`last seen ${P(n.lastSeenAt)}`:`last seen recently`:`Saved messages`}function M(e){switch(e){case`connected`:return`Realtime online`;case`connecting`:case`reconnecting`:return`Connecting`;case`error`:return`Connection issue`;default:return`Realtime idle`}}function N(e){return new Intl.DateTimeFormat(void 0,{hour:`2-digit`,minute:`2-digit`}).format(new Date(e))}function P(e){let t=Date.now()-Date.parse(e),n=Math.max(1,Math.round(t/6e4));if(n<60)return`${n}m ago`;let r=Math.round(n/60);return r<24?`${r}h ago`:`${Math.round(r/24)}d ago`}function F(e){return e.status===`read`||e.readBy?.length?`read`:e.status===`delivered`||e.deliveredTo?.length?`delivered`:e.status??`sent`}function I(e){return e.type.startsWith(`image/`)?`image`:e.type.startsWith(`video/`)?`video`:e.type.startsWith(`audio/`)?e.name.toLowerCase().includes(`voice`)?`voice`:`audio`:`file`}function L(e){return e.some(e=>e.type===`voice`)?`voice`:e.length>0?`media`:`text`}function R(e){return{user:e,role:`member`,joinedAt:new Date().toISOString()}}function z(e){return e<1024?`${e} B`:e<1024*1024?`${Math.round(e/1024)} KB`:`${(e/1024/1024).toFixed(1)} MB`}var B={width:420,height:680},V={width:340,height:480},H={width:920,height:860};function U({className:e,title:r=`Messages`,subtitle:i=`ChatEmi`,placement:a=`bottom-right`,defaultOpen:o=!1,showNotificationList:s=!0,badgeCount:c,initialSize:l=B,minSize:u=V,maxSize:d=H,markNotificationsReadOnOpen:f=!0,launcherIcon:p,...m}){let{actions:g,conversations:_,connectionStatus:v,notifications:y,theme:b,unreadNotificationCount:x}=h(),[S,C]=(0,t.useState)(o),[w,E]=(0,t.useState)({x:0,y:0}),D=(0,t.useRef)(void 0),O=(0,t.useMemo)(()=>_.reduce((e,t)=>e+(t.unreadCount??0),0),[_]),k=c??(x>0?x:O);function A(){C(e=>{let t=!e;return t&&f&&g.markNotificationsRead(),t&&g.requestNotificationPermission(),t})}function j(e){e.target.closest(`button`)||(e.currentTarget.setPointerCapture(e.pointerId),D.current={pointerId:e.pointerId,startX:e.clientX,startY:e.clientY,originX:w.x,originY:w.y})}function M(e){let t=D.current;!t||t.pointerId!==e.pointerId||E({x:t.originX+e.clientX-t.startX,y:t.originY+e.clientY-t.startY})}function N(e){D.current?.pointerId===e.pointerId&&(D.current=void 0)}return(0,n.jsxs)(`div`,{className:[`chatemi-launcher`,`chatemi-launcher--${a}`,e].filter(Boolean).join(` `),"data-theme":m.theme??b,children:[S?(0,n.jsxs)(`section`,{"aria-label":r,className:`chatemi-launcher__modal`,style:{width:l.width,height:l.height,minWidth:u.width,minHeight:u.height,maxWidth:d.width,maxHeight:d.height,transform:`translate(${w.x}px, ${w.y}px)`},children:[(0,n.jsxs)(`div`,{className:`chatemi-launcher__modal-header`,onPointerDown:j,onPointerMove:M,onPointerUp:N,onPointerCancel:N,children:[(0,n.jsxs)(`div`,{children:[(0,n.jsx)(`strong`,{children:r}),(0,n.jsxs)(`span`,{children:[i,` · `,v]})]}),(0,n.jsx)(`button`,{"aria-label":`Close messages`,onClick:A,type:`button`,children:`Close`})]}),s&&y.length>0?(0,n.jsxs)(`div`,{className:`chatemi-launcher__notifications`,"aria-label":`Notifications`,children:[y.slice(0,3).map(e=>(0,n.jsxs)(`button`,{className:e.read?`chatemi-launcher__notification`:`chatemi-launcher__notification chatemi-launcher__notification--unread`,onClick:()=>{e.conversationId&&g.openConversation(e.conversationId),g.markNotificationsRead([e.id])},type:`button`,children:[e.avatarUrl?(0,n.jsx)(`img`,{alt:``,src:e.avatarUrl}):(0,n.jsx)(`span`,{children:e.title.slice(0,2).toUpperCase()}),(0,n.jsxs)(`span`,{children:[(0,n.jsx)(`strong`,{children:e.title}),e.body?(0,n.jsx)(`small`,{children:e.body}):null]})]},e.id)),(0,n.jsx)(`button`,{className:`chatemi-launcher__clear`,onClick:g.clearNotifications,type:`button`,children:`Clear`})]}):null,(0,n.jsx)(T,{...m})]}):null,(0,n.jsxs)(`button`,{"aria-expanded":S,"aria-label":S?`Close messages`:`Open messages`,className:`chatemi-launcher__toggle`,onClick:A,type:`button`,children:[(0,n.jsx)(`span`,{className:`chatemi-launcher__icon`,children:p??(0,n.jsx)(W,{})}),k>0?(0,n.jsx)(`span`,{className:`chatemi-launcher__badge`,children:k>99?`99+`:k}):null]})]})}function W(){return(0,n.jsxs)(`svg`,{"aria-hidden":`true`,fill:`none`,height:`28`,viewBox:`0 0 28 28`,width:`28`,children:[(0,n.jsx)(`path`,{d:`M5 13.4C5 8.76 8.98 5 13.9 5h.2C19.02 5 23 8.76 23 13.4c0 4.62-3.98 8.38-8.9 8.38h-.64c-.42 0-.83.12-1.18.34l-3.54 2.2a.75.75 0 0 1-1.14-.64l.18-3.2a1.8 1.8 0 0 0-.5-1.36A8.06 8.06 0 0 1 5 13.4Z`,stroke:`currentColor`,strokeLinecap:`round`,strokeLinejoin:`round`,strokeWidth:`2`}),(0,n.jsx)(`path`,{d:`M10 12.5h8M10 16h5`,stroke:`currentColor`,strokeLinecap:`round`,strokeWidth:`2`})]})}e.ChatEmiApi=a,e.ChatEmiApiError=i,e.ChatEmiLauncher=U,e.ChatEmiMessenger=T,e.ChatEmiProvider=m,e.ChatEmiSocket=u,e.useChatEmi=h});
|
|
1
|
+
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("react"),require("react/jsx-runtime")):typeof define==`function`&&define.amd?define([`exports`,`react`,`react/jsx-runtime`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.ChatEmi={},e.React,e.jsxRuntime))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r={me:`/me`,users:`/users`,user:e=>`/users/${encodeURIComponent(e)}`,conversations:`/conversations`,conversation:e=>`/conversations/${encodeURIComponent(e)}`,conversationAvatar:e=>`/conversations/${encodeURIComponent(e)}/avatar`,members:e=>`/conversations/${encodeURIComponent(e)}/members`,member:(e,t)=>`/conversations/${encodeURIComponent(e)}/members/${encodeURIComponent(t)}`,messages:e=>`/conversations/${encodeURIComponent(e)}/messages`,message:(e,t)=>`/conversations/${encodeURIComponent(e)}/messages/${encodeURIComponent(t)}`,markRead:e=>`/conversations/${encodeURIComponent(e)}/read`,markDelivered:e=>`/conversations/${encodeURIComponent(e)}/delivered`,forwardMessage:(e,t)=>`/conversations/${encodeURIComponent(e)}/messages/${encodeURIComponent(t)}/forward`,reactions:(e,t)=>`/conversations/${encodeURIComponent(e)}/messages/${encodeURIComponent(t)}/reactions`,upload:`/attachments`,search:`/search/messages`},i=class extends Error{status;payload;constructor(e,t,n){super(e),this.name=`ChatEmiApiError`,this.status=t,this.payload=n}},a=class{config;fetcher;constructor(e){this.config=e,this.fetcher=e.fetchImpl??((e,t)=>fetch(e,t))}async getMe(e){return this.request(this.endpoint(`me`),{signal:e})}async searchUsers(e,t){if(this.config.userDirectory){let n=this.config.userDirectory.searchPath??`/users`;return o(await this.request(n,{query:{q:e.query,cursor:e.cursor,limit:e.limit},signal:t,baseUrl:this.config.userDirectory.baseUrl,headers:await this.resolveUserDirectoryHeaders(),authorize:!1}),this.config.userDirectory.mapUser)}return this.request(this.endpoint(`users`),{query:{q:e.query,cursor:e.cursor,limit:e.limit},signal:t})}async getUser(e,t){if(this.config.userDirectory){let n=this.config.userDirectory.userPath?.(e)??`/users/${encodeURIComponent(e)}`,r=await this.request(n,{signal:t,baseUrl:this.config.userDirectory.baseUrl,headers:await this.resolveUserDirectoryHeaders(),authorize:!1});return this.config.userDirectory.mapUser?this.config.userDirectory.mapUser(r):r}return this.request(this.endpoint(`user`,e),{signal:t})}async listConversations(e={},t){return this.request(this.endpoint(`conversations`),{query:{...e},signal:t})}async getConversation(e,t){return this.request(this.endpoint(`conversation`,e),{signal:t})}async createConversation(e){return this.request(this.endpoint(`conversations`),{method:`POST`,body:e})}async createGroup(e){return this.createConversation({...e,type:`group`})}async createChannel(e){return this.createConversation({...e,type:`channel`,readOnly:e.readOnly??!0})}async updateConversation(e,t){return this.request(this.endpoint(`conversation`,e),{method:`PATCH`,body:t})}async archiveConversation(e){await this.request(this.endpoint(`conversation`,e),{method:`DELETE`})}async updateConversationAvatar(e){if(e.conversationId)return this.request(this.endpoint(`conversationAvatar`,e.conversationId),{method:`PATCH`,body:{attachment:e.attachment}});if(!e.userId)throw Error(`ChatEmi avatar update requires conversationId or userId`);return this.request(this.endpoint(`user`,e.userId),{method:`PATCH`,body:{avatar:e.attachment}})}async addMembers(e,t){return this.request(this.endpoint(`members`,e),{method:`POST`,body:{userIds:t}})}async updateMember(e){return this.request(this.endpoint(`member`,e.conversationId,e.userId),{method:`PATCH`,body:e})}async removeMember(e){await this.request(this.endpoint(`member`,e.conversationId,e.userId),{method:`DELETE`})}async listMessages(e,t={},n){return this.request(this.endpoint(`messages`,e),{query:{...t},signal:n})}async sendMessage(e){return this.request(this.endpoint(`messages`,e.conversationId),{method:`POST`,body:e})}async forwardMessage(e){return this.request(this.endpoint(`forwardMessage`,e.sourceConversationId,e.messageId),{method:`POST`,body:e})}async editMessage(e){return this.request(this.endpoint(`message`,e.conversationId,e.messageId),{method:`PATCH`,body:e})}async deleteMessage(e,t){await this.request(this.endpoint(`message`,e,t),{method:`DELETE`})}async markConversationRead(e,t){await this.request(this.endpoint(`markRead`,e),{method:`POST`,body:{messageIds:t}})}async markConversationDelivered(e,t){await this.request(this.endpoint(`markDelivered`,e),{method:`POST`,body:{messageIds:t}})}async addReaction(e,t,n){return this.request(this.endpoint(`reactions`,e,t),{method:`POST`,body:{emoji:n}})}async removeReaction(e,t,n){return this.request(this.endpoint(`reactions`,e,t),{method:`DELETE`,body:{emoji:n}})}async uploadAttachment(e){let t=new FormData;return t.append(`file`,e.file,e.name),e.conversationId&&t.append(`conversationId`,e.conversationId),e.type&&t.append(`type`,e.type),e.metadata&&t.append(`metadata`,JSON.stringify(e.metadata)),this.request(this.endpoint(`upload`),{method:`POST`,body:t})}async searchMessages(e,t={},n){return this.request(this.endpoint(`search`),{query:{q:e,...t},signal:n})}endpoint(e,t,n){switch(e){case`user`:case`conversation`:case`conversationAvatar`:case`members`:case`messages`:case`markRead`:case`markDelivered`:return(this.config.endpoints?.[e]??r[e])(t??``);case`member`:case`message`:case`forwardMessage`:case`reactions`:return(this.config.endpoints?.[e]??r[e])(t??``,n??``);default:return this.config.endpoints?.[e]??r[e]}}async request(e,t={}){let n=this.buildUrl(e,t.query,t.baseUrl),r=new Headers(await this.resolveHeaders(t.headers,t.authorize??!0)),a=typeof FormData<`u`&&t.body instanceof FormData;!a&&t.body!==void 0&&!r.has(`Content-Type`)&&r.set(`Content-Type`,`application/json`);let o=await this.fetcher(n,{method:t.method??`GET`,headers:r,body:a?t.body:t.body===void 0?void 0:JSON.stringify(t.body),signal:t.signal});if(o.status===204)return;let s=await this.parseResponse(o);if(!o.ok)throw new i(this.errorMessage(s,o.status),o.status,s);return s}buildUrl(e,t,n=this.config.apiBaseUrl){let r=/^https?:\/\//i.test(e),i=n.replace(/\/+$/,``),a=e.startsWith(`/`)?e:`/${e}`,o=new URL(r?e:`${i}${a}`);return Object.entries(t??{}).forEach(([e,t])=>{t!=null&&t!==``&&o.searchParams.set(e,String(t))}),o.toString()}async resolveHeaders(e,t=!0){let n=new Headers,r=typeof this.config.headers==`function`?await this.config.headers():this.config.headers,i=typeof this.config.token==`function`?await this.config.token():this.config.token;return new Headers(r).forEach((e,t)=>n.set(t,e)),new Headers(e).forEach((e,t)=>n.set(t,e)),t&&i&&!n.has(`Authorization`)&&n.set(`Authorization`,`Bearer ${i}`),n}async resolveUserDirectoryHeaders(){return(typeof this.config.userDirectory?.headers==`function`?await this.config.userDirectory.headers():this.config.userDirectory?.headers)??{}}async parseResponse(e){return(e.headers.get(`Content-Type`)??``).includes(`application/json`)?e.json():e.text()}errorMessage(e,t){return e&&typeof e==`object`&&`message`in e&&typeof e.message==`string`?e.message:`ChatEmi API request failed with status ${t}`}};function o(e,t){let n=e=>t?t(e):e;return Array.isArray(e)?{items:e.map(n)}:{...e,items:e.items.map(n)}}var s=500,c=8e3,l=1/0,u=class{config;socket;reconnectAttempts=0;shouldReconnect=!0;reconnectTimer;queue=[];listeners=new Map;constructor(e){this.config=e}get readyState(){return this.socket?.readyState}async connect(){this.config.socketUrl&&(this.socket?.readyState===WebSocket.OPEN||this.socket?.readyState===WebSocket.CONNECTING||(this.shouldReconnect=this.config.reconnect?.enabled??!0,this.createSocket(await this.buildSocketUrl())))}disconnect(){this.shouldReconnect=!1,this.clearReconnectTimer(),this.socket?.close(),this.socket=void 0}on(e,t){let n=this.listeners.get(e)??new Set;return n.add(t),this.listeners.set(e,n),()=>{n.delete(t),n.size===0&&this.listeners.delete(e)}}send(e,t,n){let r={type:e,payload:t,requestId:n};if(this.socket?.readyState===WebSocket.OPEN){this.socket.send(JSON.stringify(r));return}this.queue.push(r)}subscribeConversation(e){this.send(`conversation.subscribe`,{conversationId:e})}unsubscribeConversation(e){this.send(`conversation.unsubscribe`,{conversationId:e})}sendTyping(e,t){this.send(`typing`,{conversationId:e,isTyping:t})}sendReadReceipt(e,t){this.send(`message.read`,{conversationId:e,messageIds:t})}sendDeliveredReceipt(e,t){this.send(`message.delivered`,{conversationId:e,messageIds:t})}sendForward(e){this.send(`message.forward`,e)}sendMemberUpdate(e){this.send(`conversation.member.update`,e)}sendAvatarUpdate(e){this.send(`conversation.avatar.update`,{conversationId:e})}sendPresence(e){this.send(`presence`,{status:e})}createSocket(e){let t=(this.config.websocketFactory??(e=>new WebSocket(e)))(e);this.socket=t,t.addEventListener(`open`,()=>{this.reconnectAttempts=0,this.dispatch(`connected`,void 0),this.flushQueue()}),t.addEventListener(`close`,e=>{this.dispatch(`disconnected`,e),this.socket=void 0,this.scheduleReconnect()}),t.addEventListener(`error`,e=>{this.dispatch(`error`,e)}),t.addEventListener(`message`,e=>{this.handleMessage(e.data)})}handleMessage(e){try{let t=typeof e==`string`?JSON.parse(e):e;t&&typeof t.type==`string`&&this.dispatchRaw(t.type,t.payload)}catch(e){this.dispatch(`error`,e instanceof Error?e:Error(`Unable to parse ChatEmi socket message`))}}flushQueue(){for(;this.queue.length>0&&this.socket?.readyState===WebSocket.OPEN;){let e=this.queue.shift();e&&this.socket.send(JSON.stringify(e))}}scheduleReconnect(){if(!this.shouldReconnect||!this.config.socketUrl)return;let e=this.config.reconnect?.maxAttempts??l;if(this.reconnectAttempts>=e)return;this.reconnectAttempts+=1;let t=this.config.reconnect?.initialDelayMs??s,n=this.config.reconnect?.maxDelayMs??c,r=Math.min(t*2**(this.reconnectAttempts-1),n);this.dispatch(`reconnecting`,{attempt:this.reconnectAttempts,delay:r}),this.clearReconnectTimer(),this.reconnectTimer=setTimeout(()=>{this.connect()},r)}async buildSocketUrl(){let e=this.config.socketUrl;if(!e)throw Error(`ChatEmi socketUrl is required before connecting the socket`);let t=new URL(e),n=typeof this.config.token==`function`?await this.config.token():this.config.token;return n&&t.searchParams.set(`token`,n),t.toString()}dispatch(e,t){this.listeners.get(e)?.forEach(e=>e(t))}dispatchRaw(e,t){this.listeners.get(e)?.forEach(e=>e(t))}clearReconnectTimer(){this.reconnectTimer&&=(clearTimeout(this.reconnectTimer),void 0)}},d=(0,t.createContext)(void 0);function f(e,t=[],n,r=[]){return{currentUser:e.currentUser,conversations:t,activeConversationId:n??t[0]?.id,messagesByConversation:{},typingByConversation:{},presenceByUser:{},notifications:r,unreadNotificationCount:r.filter(e=>!e.read).length,connectionStatus:`idle`,theme:e.theme??`light`,loading:!1}}function p(e,t){switch(t.type){case`set-loading`:return{...e,loading:t.loading};case`set-error`:return{...e,error:t.error};case`set-connection-status`:return{...e,connectionStatus:t.status};case`set-current-user`:return{...e,currentUser:t.user};case`set-conversations`:return{...e,conversations:b(t.conversations),activeConversationId:e.activeConversationId??t.conversations[0]?.id};case`upsert-conversation`:return{...e,conversations:b(g(e.conversations,t.conversation))};case`remove-conversation`:return{...e,conversations:e.conversations.filter(e=>e.id!==t.conversationId),activeConversationId:e.activeConversationId===t.conversationId?void 0:e.activeConversationId};case`upsert-member`:return{...e,conversations:e.conversations.map(e=>e.id===t.conversationId?{...e,members:_(e.members??[],t.member),participants:g(e.participants,t.member.user)}:e)};case`remove-member`:return{...e,conversations:e.conversations.map(e=>e.id===t.conversationId?{...e,members:(e.members??[]).filter(e=>e.user.id!==t.userId),participants:e.participants.filter(e=>e.id!==t.userId)}:e)};case`set-active-conversation`:return{...e,activeConversationId:t.conversationId};case`set-messages`:return{...e,messagesByConversation:{...e.messagesByConversation,[t.conversationId]:x(t.messages)}};case`upsert-message`:{let n=e.messagesByConversation[t.message.conversationId]??[],r=S(e.conversations,t.message);return{...e,conversations:r,messagesByConversation:{...e.messagesByConversation,[t.message.conversationId]:x(g(n,t.message))}}}case`remove-message`:return{...e,messagesByConversation:{...e.messagesByConversation,[t.conversationId]:(e.messagesByConversation[t.conversationId]??[]).filter(e=>e.id!==t.messageId)}};case`set-message-reactions`:return{...e,messagesByConversation:{...e.messagesByConversation,[t.conversationId]:(e.messagesByConversation[t.conversationId]??[]).map(e=>e.id===t.messageId?{...e,reactions:t.reactions}:e)}};case`apply-receipt`:return{...e,messagesByConversation:{...e.messagesByConversation,[t.receipt.conversationId]:(e.messagesByConversation[t.receipt.conversationId]??[]).map(e=>t.receipt.messageIds.includes(e.id)?v(e,t.receipt):e)}};case`add-notification`:{let n=(e.notifications.some(e=>e.id===t.notification.id)?e.notifications.map(e=>e.id===t.notification.id?t.notification:e):[t.notification,...e.notifications]).slice(0,t.maxStored??50);return{...e,notifications:n,unreadNotificationCount:n.filter(e=>!e.read).length}}case`dismiss-notification`:{let n=e.notifications.filter(e=>e.id!==t.notificationId);return{...e,notifications:n,unreadNotificationCount:n.filter(e=>!e.read).length}}case`mark-notifications-read`:{let n=t.notificationIds?new Set(t.notificationIds):void 0,r=e.notifications.map(e=>!n||n.has(e.id)?{...e,read:!0}:e);return{...e,notifications:r,unreadNotificationCount:r.filter(e=>!e.read).length}}case`clear-notifications`:return{...e,notifications:[],unreadNotificationCount:0};case`set-typing`:{let n=(e.typingByConversation[t.event.conversationId]??[]).filter(e=>e.user.id!==t.event.user.id);return{...e,typingByConversation:{...e.typingByConversation,[t.event.conversationId]:t.event.isTyping?[...n,t.event]:n}}}case`set-presence`:return{...e,presenceByUser:{...e.presenceByUser,[t.userId]:{userId:t.userId,status:t.status,lastSeenAt:t.lastSeenAt}}};default:return e}}function m({children:e,config:r,autoConnect:i=!0,initialConversations:o,initialActiveConversationId:s,initialNotifications:c}){let l=(0,t.useMemo)(()=>new a(r),[r]),m=(0,t.useMemo)(()=>new u(r),[r]),h=(0,t.useRef)({activeConversationId:s,currentUserId:r.currentUser?.id}),g=(0,t.useRef)(new Set),[_,v]=(0,t.useReducer)(p,f(r,o,s,c));(0,t.useEffect)(()=>{h.current={activeConversationId:_.activeConversationId,currentUserId:_.currentUser?.id}},[_.activeConversationId,_.currentUser?.id]),(0,t.useEffect)(()=>{let e=!0,t=new AbortController;v({type:`set-current-user`,user:r.currentUser});async function n(){v({type:`set-loading`,loading:!0}),v({type:`set-error`,error:void 0});try{let[n,i]=await Promise.all([r.currentUser?Promise.resolve(r.currentUser):l.getMe(t.signal).catch(()=>void 0),l.listConversations({},t.signal)]);if(!e)return;v({type:`set-current-user`,user:n}),v({type:`set-conversations`,conversations:i.items})}catch(t){e&&v({type:`set-error`,error:w(t)})}finally{e&&v({type:`set-loading`,loading:!1})}}return n(),()=>{e=!1,t.abort()}},[l,r.currentUser]),(0,t.useEffect)(()=>{let e=[m.on(`connected`,()=>v({type:`set-connection-status`,status:`connected`})),m.on(`disconnected`,()=>v({type:`set-connection-status`,status:`disconnected`})),m.on(`reconnecting`,()=>v({type:`set-connection-status`,status:`reconnecting`})),m.on(`error`,e=>{v({type:`set-connection-status`,status:`error`}),v({type:`set-error`,error:w(e)})}),m.on(`conversation.created`,e=>v({type:`upsert-conversation`,conversation:e})),m.on(`conversation.updated`,e=>v({type:`upsert-conversation`,conversation:e})),m.on(`conversation.deleted`,({conversationId:e})=>v({type:`remove-conversation`,conversationId:e})),m.on(`conversation.member.added`,({conversationId:e,member:t})=>v({type:`upsert-member`,conversationId:e,member:t})),m.on(`conversation.member.updated`,({conversationId:e,member:t})=>v({type:`upsert-member`,conversationId:e,member:t})),m.on(`conversation.member.removed`,({conversationId:e,userId:t})=>v({type:`remove-member`,conversationId:e,userId:t})),m.on(`message.created`,e=>{v({type:`upsert-message`,message:e}),e.sender.id!==h.current.currentUserId&&v({type:`add-notification`,notification:C(e),maxStored:r.notifications?.maxStored})}),m.on(`message.updated`,e=>v({type:`upsert-message`,message:e})),m.on(`message.deleted`,({conversationId:e,messageId:t})=>v({type:`remove-message`,conversationId:e,messageId:t})),m.on(`message.receipt`,e=>v({type:`apply-receipt`,receipt:e})),m.on(`message.reaction`,({conversationId:e,messageId:t,reactions:n})=>v({type:`set-message-reactions`,conversationId:e,messageId:t,reactions:n})),m.on(`typing`,e=>v({type:`set-typing`,event:e})),m.on(`presence`,({userId:e,status:t,lastSeenAt:n})=>v({type:`set-presence`,userId:e,status:t,lastSeenAt:n})),m.on(`notification`,e=>v({type:`add-notification`,notification:e,maxStored:r.notifications?.maxStored}))];return i&&r.socketUrl&&(v({type:`set-connection-status`,status:`connecting`}),m.connect()),()=>{e.forEach(e=>e()),m.disconnect()}},[i,r.notifications?.maxStored,r.socketUrl,m]),(0,t.useEffect)(()=>{!r.notifications?.enabled||!r.notifications.browser||typeof window>`u`||!(`Notification`in window)||window.Notification.permission!==`granted`||(r.notifications.showWhenOpen||typeof document>`u`||document.hidden)&&_.notifications.forEach(e=>{e.read||g.current.has(e.id)||(g.current.add(e.id),new window.Notification(e.title||r.notifications?.title||`New message`,{body:e.body,icon:e.avatarUrl}))})},[r.notifications,_.notifications]);let y=(0,t.useCallback)(async(e={})=>{v({type:`set-loading`,loading:!0});try{let t=await l.listConversations(e);return v({type:`set-conversations`,conversations:t.items}),t.items}finally{v({type:`set-loading`,loading:!1})}},[l]),b=(0,t.useCallback)(async(e,t={})=>{v({type:`set-active-conversation`,conversationId:e}),m.subscribeConversation(e);let n=await l.listMessages(e,t);return v({type:`set-messages`,conversationId:e,messages:n.items}),l.markConversationDelivered(e,n.items.filter(e=>e.sender.id!==_.currentUser?.id).map(e=>e.id)),n.items},[l,m,_.currentUser?.id]),x=(0,t.useCallback)(async e=>{let t=await l.createConversation(e);return v({type:`upsert-conversation`,conversation:t}),v({type:`set-active-conversation`,conversationId:t.id}),t},[l]),S=(0,t.useCallback)(async e=>{let t=`temp-${Date.now()}`,n=_.currentUser?{id:t,conversationId:e.conversationId,sender:_.currentUser,text:e.text,html:e.html,attachments:e.attachments,kind:e.kind,replyToId:e.replyToId,status:`sending`,createdAt:new Date().toISOString(),metadata:e.metadata}:void 0;n&&v({type:`upsert-message`,message:n});try{let r=await l.sendMessage(e);return n&&v({type:`remove-message`,conversationId:e.conversationId,messageId:t}),v({type:`upsert-message`,message:r}),r}catch(e){throw n&&v({type:`upsert-message`,message:{...n,status:`failed`}}),e}},[l,_.currentUser]),T=(0,t.useCallback)(async e=>{let t=await l.editMessage(e);return v({type:`upsert-message`,message:t}),t},[l]),E=(0,t.useCallback)(async(e,t)=>{await l.deleteMessage(e,t),v({type:`remove-message`,conversationId:e,messageId:t})},[l]),D=(0,t.useCallback)(async(e,t)=>{await l.markConversationRead(e,t),m.sendReadReceipt(e,t??[])},[l,m]),O=(0,t.useCallback)(async(e,t)=>{await l.markConversationDelivered(e,t),m.sendDeliveredReceipt(e,t??[])},[l,m]),k=(0,t.useCallback)(async e=>{let t=await l.forwardMessage(e);return m.sendForward(e),v({type:`upsert-message`,message:t}),t},[l,m]),A=(0,t.useCallback)(async(e,t,n)=>{let r=await l.addReaction(e,t,n);return v({type:`upsert-message`,message:r}),r},[l]),j=(0,t.useCallback)(async(e,t,n)=>{let r=await l.removeReaction(e,t,n);return v({type:`upsert-message`,message:r}),r},[l]),M=(0,t.useCallback)(e=>l.uploadAttachment(e),[l]),N=(0,t.useCallback)(async e=>{let t=await l.updateConversationAvatar(e);return`participants`in t?(v({type:`upsert-conversation`,conversation:t}),m.sendAvatarUpdate(t.id)):v({type:`set-current-user`,user:_.currentUser?.id===t.id?t:_.currentUser}),t},[l,m,_.currentUser]),P=(0,t.useCallback)(async(e,t)=>{let n=await l.addMembers(e,t);return n.forEach(t=>v({type:`upsert-member`,conversationId:e,member:t})),n},[l]),F=(0,t.useCallback)(async e=>{let t=await l.updateMember(e);return v({type:`upsert-member`,conversationId:e.conversationId,member:t}),m.sendMemberUpdate(e),t},[l,m]),I=(0,t.useCallback)(async e=>{await l.removeMember(e),v({type:`remove-member`,conversationId:e.conversationId,userId:e.userId})},[l]),L=(0,t.useCallback)(async e=>(await l.searchUsers(e)).items,[l]),R=(0,t.useCallback)(async(e,t={})=>(await l.searchMessages(e,t)).items,[l]),z=(0,t.useCallback)(e=>m.sendTyping(e,!0),[m]),B=(0,t.useCallback)(e=>m.sendTyping(e,!1),[m]),V=(0,t.useCallback)(e=>m.sendPresence(e),[m]),H=(0,t.useCallback)(e=>{v({type:`dismiss-notification`,notificationId:e})},[]),U=(0,t.useCallback)(e=>{v({type:`mark-notifications-read`,notificationIds:e})},[]),W=(0,t.useCallback)(()=>{v({type:`clear-notifications`})},[]),G=(0,t.useCallback)(async()=>typeof window>`u`||!(`Notification`in window)?`unsupported`:window.Notification.permission===`granted`||window.Notification.permission===`denied`?window.Notification.permission:window.Notification.requestPermission(),[]),K=(0,t.useCallback)(()=>m.connect(),[m]),q=(0,t.useCallback)(()=>m.disconnect(),[m]),J=(0,t.useMemo)(()=>({refreshConversations:y,openConversation:b,createConversation:x,sendMessage:S,editMessage:T,deleteMessage:E,markRead:D,markDelivered:O,forwardMessage:k,addReaction:A,removeReaction:j,uploadAttachment:M,updateAvatar:N,addMembers:P,updateMember:F,removeMember:I,searchUsers:L,searchMessages:R,startTyping:z,stopTyping:B,setPresence:V,dismissNotification:H,markNotificationsRead:U,clearNotifications:W,requestNotificationPermission:G,connect:K,disconnect:q}),[y,b,x,S,T,E,D,O,k,A,j,M,N,P,F,I,L,R,z,B,V,H,U,W,G,K,q]),Y=_.conversations.find(e=>e.id===_.activeConversationId),X=_.activeConversationId?_.messagesByConversation[_.activeConversationId]??[]:[],Z=(0,t.useMemo)(()=>({..._,api:l,socket:m,activeConversation:Y,activeMessages:X,actions:J}),[J,Y,X,l,m,_]);return(0,n.jsx)(d.Provider,{value:Z,children:e})}function h(){let e=(0,t.useContext)(d);if(!e)throw Error(`useChatEmi must be used inside a ChatEmiProvider`);return e}function g(e,t){let n=e.findIndex(e=>e.id===t.id);return n===-1?[...e,t]:[...e.slice(0,n),t,...e.slice(n+1)]}function _(e,t){let n=e.findIndex(e=>e.user.id===t.user.id);return n===-1?[...e,t]:[...e.slice(0,n),t,...e.slice(n+1)]}function v(e,t){let n=t.status===`read`?`readBy`:`deliveredTo`,r=y(e[n]??[],t);return{...e,[n]:r,status:t.status===`read`||e.status===`read`?`read`:`delivered`}}function y(e,t){let n=e.findIndex(e=>e.userId===t.userId&&e.status===t.status);return n===-1?[...e,t]:[...e.slice(0,n),t,...e.slice(n+1)]}function b(e){return[...e].sort((e,t)=>{let n=e.lastMessage?.createdAt??e.updatedAt??e.createdAt,r=t.lastMessage?.createdAt??t.updatedAt??t.createdAt;return Date.parse(r)-Date.parse(n)})}function x(e){return[...e].sort((e,t)=>Date.parse(e.createdAt)-Date.parse(t.createdAt))}function S(e,t){return b(e.map(e=>e.id===t.conversationId?{...e,lastMessage:t,updatedAt:t.createdAt}:e))}function C(e){return{id:`message:${e.id}`,kind:`message`,title:e.sender.name,body:e.text??e.attachments?.[0]?.name??`Sent a message`,conversationId:e.conversationId,messageId:e.id,actor:e.sender,avatarUrl:e.sender.avatarUrl,read:!1,createdAt:e.createdAt,metadata:e.metadata}}function w(e){return e instanceof Error?e.message:`Unexpected ChatEmi error`}function T({className:e,emptyState:r,composerPlaceholder:i=`Write a message...`,showSidebar:a=!0,theme:o,enableAdminControls:s=!0,enableMessageActions:c=!0,enableMediaPreview:l=!0,renderConversation:u,renderMessage:d}){let{actions:f,activeConversation:p,activeMessages:m,connectionStatus:g,conversations:_,currentUser:v,error:y,loading:b,theme:x,typingByConversation:S}=h(),[C,w]=(0,t.useState)(``),[T,O]=(0,t.useState)(``),[N,P]=(0,t.useState)(``),[F,z]=(0,t.useState)([]),[B,V]=(0,t.useState)(!1),[H,U]=(0,t.useState)(),[W,G]=(0,t.useState)([]),[K,q]=(0,t.useState)(!1),J=(0,t.useRef)(void 0),Y=o??x,X=(0,t.useMemo)(()=>{let e=T.trim().toLowerCase();return e?_.filter(t=>A(t,v?.id).toLowerCase().includes(e)):_},[_,v?.id,T]),Z=p?S[p.id]??[]:[],Q=(0,t.useMemo)(()=>m.map(e=>e.id).join(`,`),[m]),$=p?.members?.find(e=>e.user.id===v?.id),ee=s&&!!($&&[`owner`,`admin`,`moderator`].includes($.role));(0,t.useEffect)(()=>{if(!p||!Q)return;let e=m.filter(e=>e.sender.id!==v?.id).map(e=>e.id);e.length>0&&f.markRead(p.id,e)},[f,p,m,v?.id,Q]),(0,t.useEffect)(()=>{let e=!0;async function t(){if(N.trim().length<2){z([]);return}let t=await f.searchUsers({query:N.trim(),limit:8});e&&z(t)}return t(),()=>{e=!1}},[f,N]);async function te(e){if(e.preventDefault(),!p||!C.trim()&&W.length===0)return;let t=C.trim(),n=W;w(``),G([]),f.stopTyping(p.id);try{await f.sendMessage({conversationId:p.id,text:t||void 0,attachments:n.length>0?n:void 0,kind:L(n),replyToId:H?.id}),U(void 0)}catch{w(t),G(n)}}async function ne(e){if(e?.length){q(!0);try{let t=await Promise.all(Array.from(e).map(e=>f.uploadAttachment({file:e,name:e.name,type:I(e)})));G(e=>[...e,...t])}finally{q(!1)}}}function re(e){w(e),p&&(f.startTyping(p.id),J.current&&clearTimeout(J.current),J.current=setTimeout(()=>f.stopTyping(p.id),1400))}return(0,n.jsxs)(`section`,{className:[`chatemi`,e].filter(Boolean).join(` `),"data-status":g,"data-theme":Y,children:[a?(0,n.jsxs)(`aside`,{className:`chatemi__sidebar`,"aria-label":`Conversations`,children:[(0,n.jsxs)(`div`,{className:`chatemi__brand`,children:[(0,n.jsxs)(`div`,{children:[(0,n.jsx)(`strong`,{children:`ChatEmi`}),(0,n.jsx)(`span`,{children:M(g)})]}),(0,n.jsx)(`span`,{className:`chatemi__status chatemi__status--${g}`})]}),(0,n.jsxs)(`label`,{className:`chatemi__search`,children:[(0,n.jsx)(`span`,{className:`chatemi__sr-only`,children:`Search conversations`}),(0,n.jsx)(`input`,{value:T,onChange:e=>O(e.target.value),placeholder:`Search chats`})]}),(0,n.jsx)(`div`,{className:`chatemi__conversation-list`,children:X.map(e=>{let t=e.id===p?.id;return(0,n.jsx)(`button`,{className:`chatemi__conversation ${t?`chatemi__conversation--active`:``}`,onClick:()=>void f.openConversation(e.id),type:`button`,children:u?u(e,t):(0,n.jsx)(E,{conversation:e,currentUserId:v?.id})},e.id)})})]}):null,(0,n.jsxs)(`main`,{className:`chatemi__main`,children:[p?(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)(`header`,{className:`chatemi__header`,children:[(0,n.jsx)(k,{conversation:p,currentUserId:v?.id}),(0,n.jsxs)(`div`,{children:[(0,n.jsx)(`strong`,{children:A(p,v?.id)}),(0,n.jsx)(`span`,{children:Z.length>0?`${Z.map(e=>e.user.name).join(`, `)} typing...`:j(p,v?.id)})]}),ee?(0,n.jsx)(`button`,{className:`chatemi__header-action`,onClick:()=>V(e=>!e),type:`button`,children:`Members`}):null]}),B&&p?(0,n.jsxs)(`section`,{className:`chatemi__members`,"aria-label":`Member management`,children:[(0,n.jsx)(`div`,{className:`chatemi__members-list`,children:(p.members??p.participants.map(e=>R(e))).map(e=>(0,n.jsxs)(`div`,{className:`chatemi__member`,children:[e.user.avatarUrl?(0,n.jsx)(`img`,{alt:``,className:`chatemi__member-avatar`,src:e.user.avatarUrl}):(0,n.jsx)(`span`,{className:`chatemi__member-avatar`,children:e.user.name.slice(0,2).toUpperCase()}),(0,n.jsxs)(`span`,{children:[(0,n.jsx)(`strong`,{children:e.user.name}),(0,n.jsx)(`small`,{children:e.role})]}),e.role!==`owner`&&e.user.id!==v?.id?(0,n.jsxs)(`span`,{className:`chatemi__member-actions`,children:[(0,n.jsx)(`button`,{onClick:()=>void f.updateMember({conversationId:p.id,userId:e.user.id,role:`admin`}),type:`button`,children:`Admin`}),(0,n.jsx)(`button`,{onClick:()=>void f.updateMember({conversationId:p.id,userId:e.user.id,role:`member`}),type:`button`,children:`Member`}),(0,n.jsx)(`button`,{onClick:()=>void f.removeMember({conversationId:p.id,userId:e.user.id}),type:`button`,children:`Remove`})]}):null]},e.user.id))}),(0,n.jsxs)(`label`,{className:`chatemi__member-search`,children:[(0,n.jsx)(`span`,{children:`Find users from external directory`}),(0,n.jsx)(`input`,{onChange:e=>P(e.target.value),placeholder:`Search users`,value:N})]}),F.length>0?(0,n.jsx)(`div`,{className:`chatemi__user-results`,children:F.map(e=>(0,n.jsxs)(`button`,{onClick:()=>void f.addMembers(p.id,[e.id]),type:`button`,children:[e.avatarUrl?(0,n.jsx)(`img`,{alt:``,src:e.avatarUrl}):null,`Add `,e.name]},e.id))}):null]}):null,(0,n.jsxs)(`div`,{className:`chatemi__messages`,role:`log`,"aria-live":`polite`,children:[m.map(e=>{let t=e.sender.id===v?.id;return d?(0,n.jsx)(`div`,{className:`chatemi__message-shell`,children:d(e,t)},e.id):(0,n.jsx)(D,{enableActions:c,enableMediaPreview:l,isMine:t,message:e,onForward:e=>{let t=window.prompt(`Forward to conversation id`);t&&f.forwardMessage({sourceConversationId:e.conversationId,targetConversationId:t,messageId:e.id})},onReply:U},e.id)}),m.length===0&&!b?(0,n.jsx)(`div`,{className:`chatemi__empty`,children:`No messages yet. Start the conversation.`}):null]}),W.length>0?(0,n.jsx)(`div`,{className:`chatemi__attachments`,children:W.map(e=>(0,n.jsx)(`button`,{className:`chatemi__attachment-pill`,onClick:()=>G(t=>t.filter(t=>t.id!==e.id)),type:`button`,children:e.name??e.type},e.id))}):null,H?(0,n.jsxs)(`div`,{className:`chatemi__replying`,children:[(0,n.jsxs)(`span`,{children:[`Replying to `,(0,n.jsx)(`strong`,{children:H.sender.name}),`: `,H.text??H.attachments?.[0]?.name??`media`]}),(0,n.jsx)(`button`,{onClick:()=>U(void 0),type:`button`,children:`Cancel`})]}):null,(0,n.jsxs)(`form`,{className:`chatemi__composer`,onSubmit:e=>void te(e),children:[(0,n.jsxs)(`label`,{className:`chatemi__upload`,children:[(0,n.jsx)(`span`,{children:K?`Uploading`:`Attach`}),(0,n.jsx)(`input`,{disabled:K,multiple:!0,onChange:e=>void ne(e.target.files),type:`file`})]}),(0,n.jsx)(`textarea`,{onBlur:()=>f.stopTyping(p.id),onChange:e=>re(e.target.value),placeholder:i,rows:1,value:C}),(0,n.jsx)(`button`,{disabled:K||!C.trim()&&W.length===0,type:`submit`,children:`Send`})]})]}):r??(0,n.jsx)(`div`,{className:`chatemi__empty chatemi__empty--screen`,children:`Select a conversation to start messaging.`}),y?(0,n.jsx)(`div`,{className:`chatemi__error`,children:y}):null]})]})}function E({conversation:e,currentUserId:t}){return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(k,{conversation:e,currentUserId:t}),(0,n.jsxs)(`span`,{className:`chatemi__conversation-body`,children:[(0,n.jsx)(`strong`,{children:A(e,t)}),(0,n.jsx)(`small`,{children:e.lastMessage?.text??e.description??`No messages yet`})]}),e.unreadCount?(0,n.jsx)(`span`,{className:`chatemi__badge`,children:e.unreadCount}):null]})}function D({message:e,isMine:t,enableActions:r,enableMediaPreview:i,onReply:a,onForward:o}){return(0,n.jsxs)(`article`,{className:`chatemi__message ${t?`chatemi__message--mine`:``}`,children:[t?null:(0,n.jsx)(`strong`,{className:`chatemi__message-sender`,children:e.sender.name}),e.forwardedFrom?(0,n.jsxs)(`div`,{className:`chatemi__forwarded`,children:[`Forwarded from `,e.forwardedFrom.name]}):null,e.replyTo?(0,n.jsxs)(`blockquote`,{className:`chatemi__reply-preview`,children:[(0,n.jsx)(`strong`,{children:e.replyTo.sender.name}),(0,n.jsx)(`span`,{children:e.replyTo.text??e.replyTo.attachments?.[0]?.name??`media`})]}):null,e.text?(0,n.jsx)(`p`,{children:e.text}):null,e.html?(0,n.jsx)(`div`,{className:`chatemi__message-html`,dangerouslySetInnerHTML:{__html:e.html}}):null,e.attachments?.length?(0,n.jsx)(`div`,{className:`chatemi__message-attachments`,children:e.attachments.map(e=>O(e,i))}):null,e.reactions?.length?(0,n.jsx)(`div`,{className:`chatemi__reactions`,children:e.reactions.map(e=>(0,n.jsxs)(`span`,{children:[e.emoji,` `,e.count]},e.emoji))}):null,(0,n.jsxs)(`footer`,{children:[(0,n.jsx)(`time`,{dateTime:e.createdAt,children:N(e.createdAt)}),t&&e.status?(0,n.jsx)(`span`,{children:F(e)}):null]}),r?(0,n.jsxs)(`div`,{className:`chatemi__message-actions`,children:[(0,n.jsx)(`button`,{onClick:()=>a(e),type:`button`,children:`Reply`}),(0,n.jsx)(`button`,{onClick:()=>o(e),type:`button`,children:`Forward`})]}):null]})}function O(e,t){return t&&e.type===`image`?(0,n.jsxs)(`a`,{className:`chatemi__media chatemi__media--image`,href:e.url,rel:`noreferrer`,target:`_blank`,children:[(0,n.jsx)(`img`,{alt:e.caption??e.name??`image`,src:e.url}),e.caption?(0,n.jsx)(`span`,{children:e.caption}):null]},e.id):t&&e.type===`video`?(0,n.jsxs)(`figure`,{className:`chatemi__media chatemi__media--video`,children:[(0,n.jsx)(`video`,{controls:!0,poster:e.thumbnailUrl,src:e.url}),(0,n.jsx)(`figcaption`,{children:e.caption??e.name??`Video`})]},e.id):e.type===`voice`||e.type===`audio`?(0,n.jsxs)(`div`,{className:`chatemi__media chatemi__media--audio`,children:[(0,n.jsx)(`span`,{children:e.type===`voice`?`Voice message`:e.name??`Audio`}),(0,n.jsx)(`audio`,{controls:!0,src:e.url})]},e.id):(0,n.jsxs)(`a`,{className:`chatemi__media chatemi__media--file`,href:e.url,rel:`noreferrer`,target:`_blank`,children:[e.thumbnailUrl?(0,n.jsx)(`img`,{alt:``,src:e.thumbnailUrl}):null,(0,n.jsx)(`span`,{children:e.name??e.type}),e.size?(0,n.jsx)(`small`,{children:z(e.size)}):null]},e.id)}function k({conversation:e,currentUserId:t}){let r=A(e,t),i=e.participants.find(e=>e.id!==t)??e.participants[0],a=e.avatarUrl??i?.avatarUrl;return a?(0,n.jsx)(`img`,{alt:``,className:`chatemi__avatar`,src:a}):(0,n.jsx)(`span`,{className:`chatemi__avatar chatemi__avatar--fallback`,children:r.slice(0,2).toUpperCase()})}function A(e,t){return e.title?e.title:e.participants.filter(e=>e.id!==t).map(e=>e.name).join(`, `)||`Untitled chat`}function j(e,t){if(e.type===`channel`)return`${e.participants.length} subscribers`;if(e.type===`group`)return`${e.participants.length} members`;let n=e.participants.find(e=>e.id!==t);return n?n.presence===`online`?`online`:n.lastSeenAt?`last seen ${P(n.lastSeenAt)}`:`last seen recently`:`Saved messages`}function M(e){switch(e){case`connected`:return`Realtime online`;case`connecting`:case`reconnecting`:return`Connecting`;case`error`:return`Connection issue`;default:return`Realtime idle`}}function N(e){return new Intl.DateTimeFormat(void 0,{hour:`2-digit`,minute:`2-digit`}).format(new Date(e))}function P(e){let t=Date.now()-Date.parse(e),n=Math.max(1,Math.round(t/6e4));if(n<60)return`${n}m ago`;let r=Math.round(n/60);return r<24?`${r}h ago`:`${Math.round(r/24)}d ago`}function F(e){return e.status===`read`||e.readBy?.length?`read`:e.status===`delivered`||e.deliveredTo?.length?`delivered`:e.status??`sent`}function I(e){return e.type.startsWith(`image/`)?`image`:e.type.startsWith(`video/`)?`video`:e.type.startsWith(`audio/`)?e.name.toLowerCase().includes(`voice`)?`voice`:`audio`:`file`}function L(e){return e.some(e=>e.type===`voice`)?`voice`:e.length>0?`media`:`text`}function R(e){return{user:e,role:`member`,joinedAt:new Date().toISOString()}}function z(e){return e<1024?`${e} B`:e<1024*1024?`${Math.round(e/1024)} KB`:`${(e/1024/1024).toFixed(1)} MB`}var B={width:420,height:680},V={width:340,height:480},H={width:920,height:860};function U({className:e,title:r=`Messages`,subtitle:i=`ChatEmi`,placement:a=`bottom-right`,defaultOpen:o=!1,showNotificationList:s=!0,badgeCount:c,initialSize:l=B,minSize:u=V,maxSize:d=H,markNotificationsReadOnOpen:f=!0,launcherIcon:p,...m}){let{actions:g,conversations:_,connectionStatus:v,notifications:y,theme:b,unreadNotificationCount:x}=h(),[S,C]=(0,t.useState)(o),[w,E]=(0,t.useState)({x:0,y:0}),D=(0,t.useRef)(void 0),O=(0,t.useMemo)(()=>_.reduce((e,t)=>e+(t.unreadCount??0),0),[_]),k=c??(x>0?x:O);function A(){let e=!S;C(e),e&&(f&&g.markNotificationsRead(),g.requestNotificationPermission())}function j(e){e.target.closest(`button`)||(e.currentTarget.setPointerCapture(e.pointerId),D.current={pointerId:e.pointerId,startX:e.clientX,startY:e.clientY,originX:w.x,originY:w.y})}function M(e){let t=D.current;!t||t.pointerId!==e.pointerId||E({x:t.originX+e.clientX-t.startX,y:t.originY+e.clientY-t.startY})}function N(e){D.current?.pointerId===e.pointerId&&(D.current=void 0)}return(0,n.jsxs)(`div`,{className:[`chatemi-launcher`,`chatemi-launcher--${a}`,e].filter(Boolean).join(` `),"data-theme":m.theme??b,children:[S?(0,n.jsxs)(`section`,{"aria-label":r,className:`chatemi-launcher__modal`,style:{width:l.width,height:l.height,minWidth:u.width,minHeight:u.height,maxWidth:d.width,maxHeight:d.height,transform:`translate(${w.x}px, ${w.y}px)`},children:[(0,n.jsxs)(`div`,{className:`chatemi-launcher__modal-header`,onPointerDown:j,onPointerMove:M,onPointerUp:N,onPointerCancel:N,children:[(0,n.jsxs)(`div`,{children:[(0,n.jsx)(`strong`,{children:r}),(0,n.jsxs)(`span`,{children:[i,` · `,v]})]}),(0,n.jsx)(`button`,{"aria-label":`Close messages`,onClick:A,type:`button`,children:`Close`})]}),s&&y.length>0?(0,n.jsxs)(`div`,{className:`chatemi-launcher__notifications`,"aria-label":`Notifications`,children:[y.slice(0,3).map(e=>(0,n.jsxs)(`button`,{className:e.read?`chatemi-launcher__notification`:`chatemi-launcher__notification chatemi-launcher__notification--unread`,onClick:()=>{e.conversationId&&g.openConversation(e.conversationId),g.markNotificationsRead([e.id])},type:`button`,children:[e.avatarUrl?(0,n.jsx)(`img`,{alt:``,src:e.avatarUrl}):(0,n.jsx)(`span`,{children:e.title.slice(0,2).toUpperCase()}),(0,n.jsxs)(`span`,{children:[(0,n.jsx)(`strong`,{children:e.title}),e.body?(0,n.jsx)(`small`,{children:e.body}):null]})]},e.id)),(0,n.jsx)(`button`,{className:`chatemi-launcher__clear`,onClick:g.clearNotifications,type:`button`,children:`Clear`})]}):null,(0,n.jsx)(T,{...m})]}):null,(0,n.jsxs)(`button`,{"aria-expanded":S,"aria-label":S?`Close messages`:`Open messages`,className:`chatemi-launcher__toggle`,onClick:A,type:`button`,children:[(0,n.jsx)(`span`,{className:`chatemi-launcher__icon`,children:p??(0,n.jsx)(W,{})}),k>0?(0,n.jsx)(`span`,{className:`chatemi-launcher__badge`,children:k>99?`99+`:k}):null]})]})}function W(){return(0,n.jsxs)(`svg`,{"aria-hidden":`true`,fill:`none`,height:`28`,viewBox:`0 0 28 28`,width:`28`,children:[(0,n.jsx)(`path`,{d:`M5 13.4C5 8.76 8.98 5 13.9 5h.2C19.02 5 23 8.76 23 13.4c0 4.62-3.98 8.38-8.9 8.38h-.64c-.42 0-.83.12-1.18.34l-3.54 2.2a.75.75 0 0 1-1.14-.64l.18-3.2a1.8 1.8 0 0 0-.5-1.36A8.06 8.06 0 0 1 5 13.4Z`,stroke:`currentColor`,strokeLinecap:`round`,strokeLinejoin:`round`,strokeWidth:`2`}),(0,n.jsx)(`path`,{d:`M10 12.5h8M10 16h5`,stroke:`currentColor`,strokeLinecap:`round`,strokeWidth:`2`})]})}e.ChatEmiApi=a,e.ChatEmiApiError=i,e.ChatEmiLauncher=U,e.ChatEmiMessenger=T,e.ChatEmiProvider=m,e.ChatEmiSocket=u,e.useChatEmi=h});
|
|
@@ -8,9 +8,9 @@ ChatEmi is split into browser-safe and server-side pieces:
|
|
|
8
8
|
|
|
9
9
|
| Area | Import | Runs in | Purpose |
|
|
10
10
|
| --- | --- | --- | --- |
|
|
11
|
-
| React package |
|
|
12
|
-
| Styles |
|
|
13
|
-
| Server helpers |
|
|
11
|
+
| React package | `@mademi_dev/chatemi` | Browser / React client | Provider, hook, API client, socket client, launcher, messenger UI |
|
|
12
|
+
| Styles | `@mademi_dev/chatemi/styles.css` | Browser | All default UI, themes, launcher modal, notification badge |
|
|
13
|
+
| Server helpers | `@mademi_dev/chatemi/server` | Node.js only | MongoDB connection helper and collection/index setup |
|
|
14
14
|
|
|
15
15
|
Do not expose database credentials to browser code. Browser code should call your authenticated HTTP and WebSocket APIs.
|
|
16
16
|
|
|
@@ -19,7 +19,7 @@ Do not expose database credentials to browser code. Browser code should call you
|
|
|
19
19
|
### Install
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
npm install chatemi
|
|
22
|
+
npm install @mademi_dev/chatemi
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
If your API server uses ChatEmi MongoDB helpers:
|
|
@@ -33,7 +33,7 @@ npm install mongodb
|
|
|
33
33
|
In App Router, import CSS from `app/layout.tsx`:
|
|
34
34
|
|
|
35
35
|
```tsx
|
|
36
|
-
import "chatemi/styles.css";
|
|
36
|
+
import "@mademi_dev/chatemi/styles.css";
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
### Create a client widget
|
|
@@ -43,7 +43,7 @@ The widget uses browser APIs (`localStorage`, WebSocket, notifications), so it m
|
|
|
43
43
|
```tsx
|
|
44
44
|
"use client";
|
|
45
45
|
|
|
46
|
-
import { ChatEmiLauncher, ChatEmiProvider } from "chatemi";
|
|
46
|
+
import { ChatEmiLauncher, ChatEmiProvider } from "@mademi_dev/chatemi";
|
|
47
47
|
import { useMemo } from "react";
|
|
48
48
|
|
|
49
49
|
export function ChatWidget() {
|
|
@@ -210,10 +210,10 @@ See `docs/BACKEND_CONTRACT.md` for full details.
|
|
|
210
210
|
|
|
211
211
|
## 7. MongoDB integration
|
|
212
212
|
|
|
213
|
-
Use
|
|
213
|
+
Use `@mademi_dev/chatemi/server` only from Node.js:
|
|
214
214
|
|
|
215
215
|
```ts
|
|
216
|
-
import { createChatEmiMongoConnection } from "chatemi/server";
|
|
216
|
+
import { createChatEmiMongoConnection } from "@mademi_dev/chatemi/server";
|
|
217
217
|
|
|
218
218
|
const chatDb = await createChatEmiMongoConnection({
|
|
219
219
|
uri: process.env.MONGODB_URI!,
|
|
@@ -10,12 +10,12 @@ This example shows how to use ChatEmi in a Next.js App Router application with:
|
|
|
10
10
|
- external user-directory configuration
|
|
11
11
|
- server-side MongoDB helper usage
|
|
12
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`,
|
|
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`, `@mademi_dev/chatemi`, and optionally `mongodb` installed.
|
|
14
14
|
|
|
15
15
|
## Install in your app
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npm install chatemi
|
|
18
|
+
npm install @mademi_dev/chatemi
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
If you also use the MongoDB server helpers:
|
|
@@ -38,7 +38,7 @@ Only `NEXT_PUBLIC_*` values are sent to the browser. Keep `MONGODB_URI` server-s
|
|
|
38
38
|
|
|
39
39
|
## Files
|
|
40
40
|
|
|
41
|
-
- `app/layout.tsx` imports
|
|
41
|
+
- `app/layout.tsx` imports `@mademi_dev/chatemi/styles.css` once.
|
|
42
42
|
- `app/components/ChatWidget.tsx` is the client-side launcher widget.
|
|
43
43
|
- `app/lib/chatemi-config.ts` centralizes browser-safe config.
|
|
44
44
|
- `app/lib/chatemi-mongo.ts` shows server-side MongoDB setup.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mademi_dev/chatemi",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "A React messaging kit with API clients, realtime socket support, hooks, provider state, and default Telegram-style UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/chatemi.umd.cjs",
|