@mademi_dev/chatemi 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1626 @@
1
+ import { createContext as e, useCallback as t, useContext as n, useEffect as r, useMemo as i, useReducer as a, useRef as o, useState as s } from "react";
2
+ import { Fragment as c, jsx as l, jsxs as u } from "react/jsx-runtime";
3
+ //#region src/api.ts
4
+ var d = {
5
+ me: "/me",
6
+ users: "/users",
7
+ user: (e) => `/users/${encodeURIComponent(e)}`,
8
+ conversations: "/conversations",
9
+ conversation: (e) => `/conversations/${encodeURIComponent(e)}`,
10
+ conversationAvatar: (e) => `/conversations/${encodeURIComponent(e)}/avatar`,
11
+ members: (e) => `/conversations/${encodeURIComponent(e)}/members`,
12
+ member: (e, t) => `/conversations/${encodeURIComponent(e)}/members/${encodeURIComponent(t)}`,
13
+ messages: (e) => `/conversations/${encodeURIComponent(e)}/messages`,
14
+ message: (e, t) => `/conversations/${encodeURIComponent(e)}/messages/${encodeURIComponent(t)}`,
15
+ markRead: (e) => `/conversations/${encodeURIComponent(e)}/read`,
16
+ markDelivered: (e) => `/conversations/${encodeURIComponent(e)}/delivered`,
17
+ forwardMessage: (e, t) => `/conversations/${encodeURIComponent(e)}/messages/${encodeURIComponent(t)}/forward`,
18
+ reactions: (e, t) => `/conversations/${encodeURIComponent(e)}/messages/${encodeURIComponent(t)}/reactions`,
19
+ upload: "/attachments",
20
+ search: "/search/messages"
21
+ }, f = class extends Error {
22
+ status;
23
+ payload;
24
+ constructor(e, t, n) {
25
+ super(e), this.name = "ChatEmiApiError", this.status = t, this.payload = n;
26
+ }
27
+ }, p = class {
28
+ config;
29
+ fetcher;
30
+ constructor(e) {
31
+ this.config = e, this.fetcher = e.fetchImpl ?? fetch;
32
+ }
33
+ async getMe(e) {
34
+ return this.request(this.endpoint("me"), { signal: e });
35
+ }
36
+ async searchUsers(e, t) {
37
+ if (this.config.userDirectory) {
38
+ let n = this.config.userDirectory.searchPath ?? "/users";
39
+ return m(await this.request(n, {
40
+ query: {
41
+ q: e.query,
42
+ cursor: e.cursor,
43
+ limit: e.limit
44
+ },
45
+ signal: t,
46
+ baseUrl: this.config.userDirectory.baseUrl,
47
+ headers: await this.resolveUserDirectoryHeaders(),
48
+ authorize: !1
49
+ }), this.config.userDirectory.mapUser);
50
+ }
51
+ return this.request(this.endpoint("users"), {
52
+ query: {
53
+ q: e.query,
54
+ cursor: e.cursor,
55
+ limit: e.limit
56
+ },
57
+ signal: t
58
+ });
59
+ }
60
+ async getUser(e, t) {
61
+ if (this.config.userDirectory) {
62
+ let n = this.config.userDirectory.userPath?.(e) ?? `/users/${encodeURIComponent(e)}`, r = await this.request(n, {
63
+ signal: t,
64
+ baseUrl: this.config.userDirectory.baseUrl,
65
+ headers: await this.resolveUserDirectoryHeaders(),
66
+ authorize: !1
67
+ });
68
+ return this.config.userDirectory.mapUser ? this.config.userDirectory.mapUser(r) : r;
69
+ }
70
+ return this.request(this.endpoint("user", e), { signal: t });
71
+ }
72
+ async listConversations(e = {}, t) {
73
+ return this.request(this.endpoint("conversations"), {
74
+ query: { ...e },
75
+ signal: t
76
+ });
77
+ }
78
+ async getConversation(e, t) {
79
+ return this.request(this.endpoint("conversation", e), { signal: t });
80
+ }
81
+ async createConversation(e) {
82
+ return this.request(this.endpoint("conversations"), {
83
+ method: "POST",
84
+ body: e
85
+ });
86
+ }
87
+ async createGroup(e) {
88
+ return this.createConversation({
89
+ ...e,
90
+ type: "group"
91
+ });
92
+ }
93
+ async createChannel(e) {
94
+ return this.createConversation({
95
+ ...e,
96
+ type: "channel",
97
+ readOnly: e.readOnly ?? !0
98
+ });
99
+ }
100
+ async updateConversation(e, t) {
101
+ return this.request(this.endpoint("conversation", e), {
102
+ method: "PATCH",
103
+ body: t
104
+ });
105
+ }
106
+ async archiveConversation(e) {
107
+ await this.request(this.endpoint("conversation", e), { method: "DELETE" });
108
+ }
109
+ async updateConversationAvatar(e) {
110
+ if (e.conversationId) return this.request(this.endpoint("conversationAvatar", e.conversationId), {
111
+ method: "PATCH",
112
+ body: { attachment: e.attachment }
113
+ });
114
+ if (!e.userId) throw Error("ChatEmi avatar update requires conversationId or userId");
115
+ return this.request(this.endpoint("user", e.userId), {
116
+ method: "PATCH",
117
+ body: { avatar: e.attachment }
118
+ });
119
+ }
120
+ async addMembers(e, t) {
121
+ return this.request(this.endpoint("members", e), {
122
+ method: "POST",
123
+ body: { userIds: t }
124
+ });
125
+ }
126
+ async updateMember(e) {
127
+ return this.request(this.endpoint("member", e.conversationId, e.userId), {
128
+ method: "PATCH",
129
+ body: e
130
+ });
131
+ }
132
+ async removeMember(e) {
133
+ await this.request(this.endpoint("member", e.conversationId, e.userId), { method: "DELETE" });
134
+ }
135
+ async listMessages(e, t = {}, n) {
136
+ return this.request(this.endpoint("messages", e), {
137
+ query: { ...t },
138
+ signal: n
139
+ });
140
+ }
141
+ async sendMessage(e) {
142
+ return this.request(this.endpoint("messages", e.conversationId), {
143
+ method: "POST",
144
+ body: e
145
+ });
146
+ }
147
+ async forwardMessage(e) {
148
+ return this.request(this.endpoint("forwardMessage", e.sourceConversationId, e.messageId), {
149
+ method: "POST",
150
+ body: e
151
+ });
152
+ }
153
+ async editMessage(e) {
154
+ return this.request(this.endpoint("message", e.conversationId, e.messageId), {
155
+ method: "PATCH",
156
+ body: e
157
+ });
158
+ }
159
+ async deleteMessage(e, t) {
160
+ await this.request(this.endpoint("message", e, t), { method: "DELETE" });
161
+ }
162
+ async markConversationRead(e, t) {
163
+ await this.request(this.endpoint("markRead", e), {
164
+ method: "POST",
165
+ body: { messageIds: t }
166
+ });
167
+ }
168
+ async markConversationDelivered(e, t) {
169
+ await this.request(this.endpoint("markDelivered", e), {
170
+ method: "POST",
171
+ body: { messageIds: t }
172
+ });
173
+ }
174
+ async addReaction(e, t, n) {
175
+ return this.request(this.endpoint("reactions", e, t), {
176
+ method: "POST",
177
+ body: { emoji: n }
178
+ });
179
+ }
180
+ async removeReaction(e, t, n) {
181
+ return this.request(this.endpoint("reactions", e, t), {
182
+ method: "DELETE",
183
+ body: { emoji: n }
184
+ });
185
+ }
186
+ async uploadAttachment(e) {
187
+ let t = new FormData();
188
+ 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"), {
189
+ method: "POST",
190
+ body: t
191
+ });
192
+ }
193
+ async searchMessages(e, t = {}, n) {
194
+ return this.request(this.endpoint("search"), {
195
+ query: {
196
+ q: e,
197
+ ...t
198
+ },
199
+ signal: n
200
+ });
201
+ }
202
+ endpoint(e, t, n) {
203
+ switch (e) {
204
+ case "user":
205
+ case "conversation":
206
+ case "conversationAvatar":
207
+ case "members":
208
+ case "messages":
209
+ case "markRead":
210
+ case "markDelivered": return (this.config.endpoints?.[e] ?? d[e])(t ?? "");
211
+ case "member":
212
+ case "message":
213
+ case "forwardMessage":
214
+ case "reactions": return (this.config.endpoints?.[e] ?? d[e])(t ?? "", n ?? "");
215
+ default: return this.config.endpoints?.[e] ?? d[e];
216
+ }
217
+ }
218
+ async request(e, t = {}) {
219
+ let n = this.buildUrl(e, t.query, t.baseUrl), r = new Headers(await this.resolveHeaders(t.headers, t.authorize ?? !0)), i = typeof FormData < "u" && t.body instanceof FormData;
220
+ !i && t.body !== void 0 && !r.has("Content-Type") && r.set("Content-Type", "application/json");
221
+ let a = await this.fetcher(n, {
222
+ method: t.method ?? "GET",
223
+ headers: r,
224
+ body: i ? t.body : t.body === void 0 ? void 0 : JSON.stringify(t.body),
225
+ signal: t.signal
226
+ });
227
+ if (a.status === 204) return;
228
+ let o = await this.parseResponse(a);
229
+ if (!a.ok) throw new f(this.errorMessage(o, a.status), a.status, o);
230
+ return o;
231
+ }
232
+ buildUrl(e, t, n = this.config.apiBaseUrl) {
233
+ let r = /^https?:\/\//i.test(e), i = n.replace(/\/+$/, ""), a = e.startsWith("/") ? e : `/${e}`, o = new URL(r ? e : `${i}${a}`);
234
+ return Object.entries(t ?? {}).forEach(([e, t]) => {
235
+ t != null && t !== "" && o.searchParams.set(e, String(t));
236
+ }), o.toString();
237
+ }
238
+ async resolveHeaders(e, t = !0) {
239
+ 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;
240
+ 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;
241
+ }
242
+ async resolveUserDirectoryHeaders() {
243
+ return (typeof this.config.userDirectory?.headers == "function" ? await this.config.userDirectory.headers() : this.config.userDirectory?.headers) ?? {};
244
+ }
245
+ async parseResponse(e) {
246
+ return (e.headers.get("Content-Type") ?? "").includes("application/json") ? e.json() : e.text();
247
+ }
248
+ errorMessage(e, t) {
249
+ return e && typeof e == "object" && "message" in e && typeof e.message == "string" ? e.message : `ChatEmi API request failed with status ${t}`;
250
+ }
251
+ };
252
+ function m(e, t) {
253
+ let n = (e) => t ? t(e) : e;
254
+ return Array.isArray(e) ? { items: e.map(n) } : {
255
+ ...e,
256
+ items: e.items.map(n)
257
+ };
258
+ }
259
+ //#endregion
260
+ //#region src/socket.ts
261
+ var h = 500, g = 8e3, _ = Infinity, v = class {
262
+ config;
263
+ socket;
264
+ reconnectAttempts = 0;
265
+ shouldReconnect = !0;
266
+ reconnectTimer;
267
+ queue = [];
268
+ listeners = /* @__PURE__ */ new Map();
269
+ constructor(e) {
270
+ this.config = e;
271
+ }
272
+ get readyState() {
273
+ return this.socket?.readyState;
274
+ }
275
+ async connect() {
276
+ 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())));
277
+ }
278
+ disconnect() {
279
+ this.shouldReconnect = !1, this.clearReconnectTimer(), this.socket?.close(), this.socket = void 0;
280
+ }
281
+ on(e, t) {
282
+ let n = this.listeners.get(e) ?? /* @__PURE__ */ new Set();
283
+ return n.add(t), this.listeners.set(e, n), () => {
284
+ n.delete(t), n.size === 0 && this.listeners.delete(e);
285
+ };
286
+ }
287
+ send(e, t, n) {
288
+ let r = {
289
+ type: e,
290
+ payload: t,
291
+ requestId: n
292
+ };
293
+ if (this.socket?.readyState === WebSocket.OPEN) {
294
+ this.socket.send(JSON.stringify(r));
295
+ return;
296
+ }
297
+ this.queue.push(r);
298
+ }
299
+ subscribeConversation(e) {
300
+ this.send("conversation.subscribe", { conversationId: e });
301
+ }
302
+ unsubscribeConversation(e) {
303
+ this.send("conversation.unsubscribe", { conversationId: e });
304
+ }
305
+ sendTyping(e, t) {
306
+ this.send("typing", {
307
+ conversationId: e,
308
+ isTyping: t
309
+ });
310
+ }
311
+ sendReadReceipt(e, t) {
312
+ this.send("message.read", {
313
+ conversationId: e,
314
+ messageIds: t
315
+ });
316
+ }
317
+ sendDeliveredReceipt(e, t) {
318
+ this.send("message.delivered", {
319
+ conversationId: e,
320
+ messageIds: t
321
+ });
322
+ }
323
+ sendForward(e) {
324
+ this.send("message.forward", e);
325
+ }
326
+ sendMemberUpdate(e) {
327
+ this.send("conversation.member.update", e);
328
+ }
329
+ sendAvatarUpdate(e) {
330
+ this.send("conversation.avatar.update", { conversationId: e });
331
+ }
332
+ sendPresence(e) {
333
+ this.send("presence", { status: e });
334
+ }
335
+ createSocket(e) {
336
+ let t = (this.config.websocketFactory ?? ((e) => new WebSocket(e)))(e);
337
+ this.socket = t, t.addEventListener("open", () => {
338
+ this.reconnectAttempts = 0, this.dispatch("connected", void 0), this.flushQueue();
339
+ }), t.addEventListener("close", (e) => {
340
+ this.dispatch("disconnected", e), this.socket = void 0, this.scheduleReconnect();
341
+ }), t.addEventListener("error", (e) => {
342
+ this.dispatch("error", e);
343
+ }), t.addEventListener("message", (e) => {
344
+ this.handleMessage(e.data);
345
+ });
346
+ }
347
+ handleMessage(e) {
348
+ try {
349
+ let t = typeof e == "string" ? JSON.parse(e) : e;
350
+ t && typeof t.type == "string" && this.dispatchRaw(t.type, t.payload);
351
+ } catch (e) {
352
+ this.dispatch("error", e instanceof Error ? e : /* @__PURE__ */ Error("Unable to parse ChatEmi socket message"));
353
+ }
354
+ }
355
+ flushQueue() {
356
+ for (; this.queue.length > 0 && this.socket?.readyState === WebSocket.OPEN;) {
357
+ let e = this.queue.shift();
358
+ e && this.socket.send(JSON.stringify(e));
359
+ }
360
+ }
361
+ scheduleReconnect() {
362
+ if (!this.shouldReconnect || !this.config.socketUrl) return;
363
+ let e = this.config.reconnect?.maxAttempts ?? _;
364
+ if (this.reconnectAttempts >= e) return;
365
+ this.reconnectAttempts += 1;
366
+ let t = this.config.reconnect?.initialDelayMs ?? h, n = this.config.reconnect?.maxDelayMs ?? g, r = Math.min(t * 2 ** (this.reconnectAttempts - 1), n);
367
+ this.dispatch("reconnecting", {
368
+ attempt: this.reconnectAttempts,
369
+ delay: r
370
+ }), this.clearReconnectTimer(), this.reconnectTimer = setTimeout(() => {
371
+ this.connect();
372
+ }, r);
373
+ }
374
+ async buildSocketUrl() {
375
+ let e = this.config.socketUrl;
376
+ if (!e) throw Error("ChatEmi socketUrl is required before connecting the socket");
377
+ let t = new URL(e), n = typeof this.config.token == "function" ? await this.config.token() : this.config.token;
378
+ return n && t.searchParams.set("token", n), t.toString();
379
+ }
380
+ dispatch(e, t) {
381
+ this.listeners.get(e)?.forEach((e) => e(t));
382
+ }
383
+ dispatchRaw(e, t) {
384
+ this.listeners.get(e)?.forEach((e) => e(t));
385
+ }
386
+ clearReconnectTimer() {
387
+ this.reconnectTimer &&= (clearTimeout(this.reconnectTimer), void 0);
388
+ }
389
+ }, y = e(void 0);
390
+ function b(e, t = [], n, r = []) {
391
+ return {
392
+ currentUser: e.currentUser,
393
+ conversations: t,
394
+ activeConversationId: n ?? t[0]?.id,
395
+ messagesByConversation: {},
396
+ typingByConversation: {},
397
+ presenceByUser: {},
398
+ notifications: r,
399
+ unreadNotificationCount: r.filter((e) => !e.read).length,
400
+ connectionStatus: "idle",
401
+ theme: e.theme ?? "light",
402
+ loading: !1
403
+ };
404
+ }
405
+ function x(e, t) {
406
+ switch (t.type) {
407
+ case "set-loading": return {
408
+ ...e,
409
+ loading: t.loading
410
+ };
411
+ case "set-error": return {
412
+ ...e,
413
+ error: t.error
414
+ };
415
+ case "set-connection-status": return {
416
+ ...e,
417
+ connectionStatus: t.status
418
+ };
419
+ case "set-current-user": return {
420
+ ...e,
421
+ currentUser: t.user
422
+ };
423
+ case "set-conversations": return {
424
+ ...e,
425
+ conversations: O(t.conversations),
426
+ activeConversationId: e.activeConversationId ?? t.conversations[0]?.id
427
+ };
428
+ case "upsert-conversation": return {
429
+ ...e,
430
+ conversations: O(w(e.conversations, t.conversation))
431
+ };
432
+ case "remove-conversation": return {
433
+ ...e,
434
+ conversations: e.conversations.filter((e) => e.id !== t.conversationId),
435
+ activeConversationId: e.activeConversationId === t.conversationId ? void 0 : e.activeConversationId
436
+ };
437
+ case "upsert-member": return {
438
+ ...e,
439
+ conversations: e.conversations.map((e) => e.id === t.conversationId ? {
440
+ ...e,
441
+ members: T(e.members ?? [], t.member),
442
+ participants: w(e.participants, t.member.user)
443
+ } : e)
444
+ };
445
+ case "remove-member": return {
446
+ ...e,
447
+ conversations: e.conversations.map((e) => e.id === t.conversationId ? {
448
+ ...e,
449
+ members: (e.members ?? []).filter((e) => e.user.id !== t.userId),
450
+ participants: e.participants.filter((e) => e.id !== t.userId)
451
+ } : e)
452
+ };
453
+ case "set-active-conversation": return {
454
+ ...e,
455
+ activeConversationId: t.conversationId
456
+ };
457
+ case "set-messages": return {
458
+ ...e,
459
+ messagesByConversation: {
460
+ ...e.messagesByConversation,
461
+ [t.conversationId]: k(t.messages)
462
+ }
463
+ };
464
+ case "upsert-message": {
465
+ let n = e.messagesByConversation[t.message.conversationId] ?? [], r = A(e.conversations, t.message);
466
+ return {
467
+ ...e,
468
+ conversations: r,
469
+ messagesByConversation: {
470
+ ...e.messagesByConversation,
471
+ [t.message.conversationId]: k(w(n, t.message))
472
+ }
473
+ };
474
+ }
475
+ case "remove-message": return {
476
+ ...e,
477
+ messagesByConversation: {
478
+ ...e.messagesByConversation,
479
+ [t.conversationId]: (e.messagesByConversation[t.conversationId] ?? []).filter((e) => e.id !== t.messageId)
480
+ }
481
+ };
482
+ case "set-message-reactions": return {
483
+ ...e,
484
+ messagesByConversation: {
485
+ ...e.messagesByConversation,
486
+ [t.conversationId]: (e.messagesByConversation[t.conversationId] ?? []).map((e) => e.id === t.messageId ? {
487
+ ...e,
488
+ reactions: t.reactions
489
+ } : e)
490
+ }
491
+ };
492
+ case "apply-receipt": return {
493
+ ...e,
494
+ messagesByConversation: {
495
+ ...e.messagesByConversation,
496
+ [t.receipt.conversationId]: (e.messagesByConversation[t.receipt.conversationId] ?? []).map((e) => t.receipt.messageIds.includes(e.id) ? E(e, t.receipt) : e)
497
+ }
498
+ };
499
+ case "add-notification": {
500
+ 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);
501
+ return {
502
+ ...e,
503
+ notifications: n,
504
+ unreadNotificationCount: n.filter((e) => !e.read).length
505
+ };
506
+ }
507
+ case "dismiss-notification": {
508
+ let n = e.notifications.filter((e) => e.id !== t.notificationId);
509
+ return {
510
+ ...e,
511
+ notifications: n,
512
+ unreadNotificationCount: n.filter((e) => !e.read).length
513
+ };
514
+ }
515
+ case "mark-notifications-read": {
516
+ let n = t.notificationIds ? new Set(t.notificationIds) : void 0, r = e.notifications.map((e) => !n || n.has(e.id) ? {
517
+ ...e,
518
+ read: !0
519
+ } : e);
520
+ return {
521
+ ...e,
522
+ notifications: r,
523
+ unreadNotificationCount: r.filter((e) => !e.read).length
524
+ };
525
+ }
526
+ case "clear-notifications": return {
527
+ ...e,
528
+ notifications: [],
529
+ unreadNotificationCount: 0
530
+ };
531
+ case "set-typing": {
532
+ let n = (e.typingByConversation[t.event.conversationId] ?? []).filter((e) => e.user.id !== t.event.user.id);
533
+ return {
534
+ ...e,
535
+ typingByConversation: {
536
+ ...e.typingByConversation,
537
+ [t.event.conversationId]: t.event.isTyping ? [...n, t.event] : n
538
+ }
539
+ };
540
+ }
541
+ case "set-presence": return {
542
+ ...e,
543
+ presenceByUser: {
544
+ ...e.presenceByUser,
545
+ [t.userId]: {
546
+ userId: t.userId,
547
+ status: t.status,
548
+ lastSeenAt: t.lastSeenAt
549
+ }
550
+ }
551
+ };
552
+ default: return e;
553
+ }
554
+ }
555
+ function S({ children: e, config: n, autoConnect: s = !0, initialConversations: c, initialActiveConversationId: u, initialNotifications: d }) {
556
+ let f = i(() => new p(n), [n]), m = i(() => new v(n), [n]), h = o({
557
+ activeConversationId: u,
558
+ currentUserId: n.currentUser?.id
559
+ }), g = o(/* @__PURE__ */ new Set()), [_, S] = a(x, b(n, c, u, d));
560
+ r(() => {
561
+ h.current = {
562
+ activeConversationId: _.activeConversationId,
563
+ currentUserId: _.currentUser?.id
564
+ };
565
+ }, [_.activeConversationId, _.currentUser?.id]), r(() => {
566
+ let e = !0, t = new AbortController();
567
+ S({
568
+ type: "set-current-user",
569
+ user: n.currentUser
570
+ });
571
+ async function r() {
572
+ S({
573
+ type: "set-loading",
574
+ loading: !0
575
+ }), S({
576
+ type: "set-error",
577
+ error: void 0
578
+ });
579
+ try {
580
+ let [r, i] = await Promise.all([n.currentUser ? Promise.resolve(n.currentUser) : f.getMe(t.signal).catch(() => void 0), f.listConversations({}, t.signal)]);
581
+ if (!e) return;
582
+ S({
583
+ type: "set-current-user",
584
+ user: r
585
+ }), S({
586
+ type: "set-conversations",
587
+ conversations: i.items
588
+ });
589
+ } catch (t) {
590
+ e && S({
591
+ type: "set-error",
592
+ error: M(t)
593
+ });
594
+ } finally {
595
+ e && S({
596
+ type: "set-loading",
597
+ loading: !1
598
+ });
599
+ }
600
+ }
601
+ return r(), () => {
602
+ e = !1, t.abort();
603
+ };
604
+ }, [f, n.currentUser]), r(() => {
605
+ let e = [
606
+ m.on("connected", () => S({
607
+ type: "set-connection-status",
608
+ status: "connected"
609
+ })),
610
+ m.on("disconnected", () => S({
611
+ type: "set-connection-status",
612
+ status: "disconnected"
613
+ })),
614
+ m.on("reconnecting", () => S({
615
+ type: "set-connection-status",
616
+ status: "reconnecting"
617
+ })),
618
+ m.on("error", (e) => {
619
+ S({
620
+ type: "set-connection-status",
621
+ status: "error"
622
+ }), S({
623
+ type: "set-error",
624
+ error: M(e)
625
+ });
626
+ }),
627
+ m.on("conversation.created", (e) => S({
628
+ type: "upsert-conversation",
629
+ conversation: e
630
+ })),
631
+ m.on("conversation.updated", (e) => S({
632
+ type: "upsert-conversation",
633
+ conversation: e
634
+ })),
635
+ m.on("conversation.deleted", ({ conversationId: e }) => S({
636
+ type: "remove-conversation",
637
+ conversationId: e
638
+ })),
639
+ m.on("conversation.member.added", ({ conversationId: e, member: t }) => S({
640
+ type: "upsert-member",
641
+ conversationId: e,
642
+ member: t
643
+ })),
644
+ m.on("conversation.member.updated", ({ conversationId: e, member: t }) => S({
645
+ type: "upsert-member",
646
+ conversationId: e,
647
+ member: t
648
+ })),
649
+ m.on("conversation.member.removed", ({ conversationId: e, userId: t }) => S({
650
+ type: "remove-member",
651
+ conversationId: e,
652
+ userId: t
653
+ })),
654
+ m.on("message.created", (e) => {
655
+ S({
656
+ type: "upsert-message",
657
+ message: e
658
+ }), e.sender.id !== h.current.currentUserId && S({
659
+ type: "add-notification",
660
+ notification: j(e),
661
+ maxStored: n.notifications?.maxStored
662
+ });
663
+ }),
664
+ m.on("message.updated", (e) => S({
665
+ type: "upsert-message",
666
+ message: e
667
+ })),
668
+ m.on("message.deleted", ({ conversationId: e, messageId: t }) => S({
669
+ type: "remove-message",
670
+ conversationId: e,
671
+ messageId: t
672
+ })),
673
+ m.on("message.receipt", (e) => S({
674
+ type: "apply-receipt",
675
+ receipt: e
676
+ })),
677
+ m.on("message.reaction", ({ conversationId: e, messageId: t, reactions: n }) => S({
678
+ type: "set-message-reactions",
679
+ conversationId: e,
680
+ messageId: t,
681
+ reactions: n
682
+ })),
683
+ m.on("typing", (e) => S({
684
+ type: "set-typing",
685
+ event: e
686
+ })),
687
+ m.on("presence", ({ userId: e, status: t, lastSeenAt: n }) => S({
688
+ type: "set-presence",
689
+ userId: e,
690
+ status: t,
691
+ lastSeenAt: n
692
+ })),
693
+ m.on("notification", (e) => S({
694
+ type: "add-notification",
695
+ notification: e,
696
+ maxStored: n.notifications?.maxStored
697
+ }))
698
+ ];
699
+ return s && n.socketUrl && (S({
700
+ type: "set-connection-status",
701
+ status: "connecting"
702
+ }), m.connect()), () => {
703
+ e.forEach((e) => e()), m.disconnect();
704
+ };
705
+ }, [
706
+ s,
707
+ n.notifications?.maxStored,
708
+ n.socketUrl,
709
+ m
710
+ ]), r(() => {
711
+ !n.notifications?.enabled || !n.notifications.browser || typeof window > "u" || !("Notification" in window) || window.Notification.permission !== "granted" || (n.notifications.showWhenOpen || typeof document > "u" || document.hidden) && _.notifications.forEach((e) => {
712
+ e.read || g.current.has(e.id) || (g.current.add(e.id), new window.Notification(e.title || n.notifications?.title || "New message", {
713
+ body: e.body,
714
+ icon: e.avatarUrl
715
+ }));
716
+ });
717
+ }, [n.notifications, _.notifications]);
718
+ let C = t(async (e = {}) => {
719
+ S({
720
+ type: "set-loading",
721
+ loading: !0
722
+ });
723
+ try {
724
+ let t = await f.listConversations(e);
725
+ return S({
726
+ type: "set-conversations",
727
+ conversations: t.items
728
+ }), t.items;
729
+ } finally {
730
+ S({
731
+ type: "set-loading",
732
+ loading: !1
733
+ });
734
+ }
735
+ }, [f]), w = t(async (e, t = {}) => {
736
+ S({
737
+ type: "set-active-conversation",
738
+ conversationId: e
739
+ }), m.subscribeConversation(e);
740
+ let n = await f.listMessages(e, t);
741
+ return S({
742
+ type: "set-messages",
743
+ conversationId: e,
744
+ messages: n.items
745
+ }), f.markConversationDelivered(e, n.items.filter((e) => e.sender.id !== _.currentUser?.id).map((e) => e.id)), n.items;
746
+ }, [
747
+ f,
748
+ m,
749
+ _.currentUser?.id
750
+ ]), T = t(async (e) => {
751
+ let t = await f.createConversation(e);
752
+ return S({
753
+ type: "upsert-conversation",
754
+ conversation: t
755
+ }), S({
756
+ type: "set-active-conversation",
757
+ conversationId: t.id
758
+ }), t;
759
+ }, [f]), E = t(async (e) => {
760
+ let t = `temp-${Date.now()}`, n = _.currentUser ? {
761
+ id: t,
762
+ conversationId: e.conversationId,
763
+ sender: _.currentUser,
764
+ text: e.text,
765
+ html: e.html,
766
+ attachments: e.attachments,
767
+ kind: e.kind,
768
+ replyToId: e.replyToId,
769
+ status: "sending",
770
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
771
+ metadata: e.metadata
772
+ } : void 0;
773
+ n && S({
774
+ type: "upsert-message",
775
+ message: n
776
+ });
777
+ try {
778
+ let r = await f.sendMessage(e);
779
+ return n && S({
780
+ type: "remove-message",
781
+ conversationId: e.conversationId,
782
+ messageId: t
783
+ }), S({
784
+ type: "upsert-message",
785
+ message: r
786
+ }), r;
787
+ } catch (e) {
788
+ throw n && S({
789
+ type: "upsert-message",
790
+ message: {
791
+ ...n,
792
+ status: "failed"
793
+ }
794
+ }), e;
795
+ }
796
+ }, [f, _.currentUser]), D = t(async (e) => {
797
+ let t = await f.editMessage(e);
798
+ return S({
799
+ type: "upsert-message",
800
+ message: t
801
+ }), t;
802
+ }, [f]), O = t(async (e, t) => {
803
+ await f.deleteMessage(e, t), S({
804
+ type: "remove-message",
805
+ conversationId: e,
806
+ messageId: t
807
+ });
808
+ }, [f]), k = t(async (e, t) => {
809
+ await f.markConversationRead(e, t), m.sendReadReceipt(e, t ?? []);
810
+ }, [f, m]), A = t(async (e, t) => {
811
+ await f.markConversationDelivered(e, t), m.sendDeliveredReceipt(e, t ?? []);
812
+ }, [f, m]), N = t(async (e) => {
813
+ let t = await f.forwardMessage(e);
814
+ return m.sendForward(e), S({
815
+ type: "upsert-message",
816
+ message: t
817
+ }), t;
818
+ }, [f, m]), P = t(async (e, t, n) => {
819
+ let r = await f.addReaction(e, t, n);
820
+ return S({
821
+ type: "upsert-message",
822
+ message: r
823
+ }), r;
824
+ }, [f]), F = t(async (e, t, n) => {
825
+ let r = await f.removeReaction(e, t, n);
826
+ return S({
827
+ type: "upsert-message",
828
+ message: r
829
+ }), r;
830
+ }, [f]), I = t((e) => f.uploadAttachment(e), [f]), L = t(async (e) => {
831
+ let t = await f.updateConversationAvatar(e);
832
+ return "participants" in t ? (S({
833
+ type: "upsert-conversation",
834
+ conversation: t
835
+ }), m.sendAvatarUpdate(t.id)) : S({
836
+ type: "set-current-user",
837
+ user: _.currentUser?.id === t.id ? t : _.currentUser
838
+ }), t;
839
+ }, [
840
+ f,
841
+ m,
842
+ _.currentUser
843
+ ]), R = t(async (e, t) => {
844
+ let n = await f.addMembers(e, t);
845
+ return n.forEach((t) => S({
846
+ type: "upsert-member",
847
+ conversationId: e,
848
+ member: t
849
+ })), n;
850
+ }, [f]), z = t(async (e) => {
851
+ let t = await f.updateMember(e);
852
+ return S({
853
+ type: "upsert-member",
854
+ conversationId: e.conversationId,
855
+ member: t
856
+ }), m.sendMemberUpdate(e), t;
857
+ }, [f, m]), B = t(async (e) => {
858
+ await f.removeMember(e), S({
859
+ type: "remove-member",
860
+ conversationId: e.conversationId,
861
+ userId: e.userId
862
+ });
863
+ }, [f]), V = t(async (e) => (await f.searchUsers(e)).items, [f]), H = t(async (e, t = {}) => (await f.searchMessages(e, t)).items, [f]), U = t((e) => m.sendTyping(e, !0), [m]), W = t((e) => m.sendTyping(e, !1), [m]), G = t((e) => m.sendPresence(e), [m]), K = t((e) => {
864
+ S({
865
+ type: "dismiss-notification",
866
+ notificationId: e
867
+ });
868
+ }, []), q = t((e) => {
869
+ S({
870
+ type: "mark-notifications-read",
871
+ notificationIds: e
872
+ });
873
+ }, []), J = t(() => {
874
+ S({ type: "clear-notifications" });
875
+ }, []), Y = t(async () => typeof window > "u" || !("Notification" in window) ? "unsupported" : window.Notification.permission === "granted" || window.Notification.permission === "denied" ? window.Notification.permission : window.Notification.requestPermission(), []), X = t(() => m.connect(), [m]), Z = t(() => m.disconnect(), [m]), Q = i(() => ({
876
+ refreshConversations: C,
877
+ openConversation: w,
878
+ createConversation: T,
879
+ sendMessage: E,
880
+ editMessage: D,
881
+ deleteMessage: O,
882
+ markRead: k,
883
+ markDelivered: A,
884
+ forwardMessage: N,
885
+ addReaction: P,
886
+ removeReaction: F,
887
+ uploadAttachment: I,
888
+ updateAvatar: L,
889
+ addMembers: R,
890
+ updateMember: z,
891
+ removeMember: B,
892
+ searchUsers: V,
893
+ searchMessages: H,
894
+ startTyping: U,
895
+ stopTyping: W,
896
+ setPresence: G,
897
+ dismissNotification: K,
898
+ markNotificationsRead: q,
899
+ clearNotifications: J,
900
+ requestNotificationPermission: Y,
901
+ connect: X,
902
+ disconnect: Z
903
+ }), [
904
+ C,
905
+ w,
906
+ T,
907
+ E,
908
+ D,
909
+ O,
910
+ k,
911
+ A,
912
+ N,
913
+ P,
914
+ F,
915
+ I,
916
+ L,
917
+ R,
918
+ z,
919
+ B,
920
+ V,
921
+ H,
922
+ U,
923
+ W,
924
+ G,
925
+ K,
926
+ q,
927
+ J,
928
+ Y,
929
+ X,
930
+ Z
931
+ ]), $ = _.conversations.find((e) => e.id === _.activeConversationId), ee = _.activeConversationId ? _.messagesByConversation[_.activeConversationId] ?? [] : [], te = i(() => ({
932
+ ..._,
933
+ api: f,
934
+ socket: m,
935
+ activeConversation: $,
936
+ activeMessages: ee,
937
+ actions: Q
938
+ }), [
939
+ Q,
940
+ $,
941
+ ee,
942
+ f,
943
+ m,
944
+ _
945
+ ]);
946
+ return /* @__PURE__ */ l(y.Provider, {
947
+ value: te,
948
+ children: e
949
+ });
950
+ }
951
+ function C() {
952
+ let e = n(y);
953
+ if (!e) throw Error("useChatEmi must be used inside a ChatEmiProvider");
954
+ return e;
955
+ }
956
+ function w(e, t) {
957
+ let n = e.findIndex((e) => e.id === t.id);
958
+ return n === -1 ? [...e, t] : [
959
+ ...e.slice(0, n),
960
+ t,
961
+ ...e.slice(n + 1)
962
+ ];
963
+ }
964
+ function T(e, t) {
965
+ let n = e.findIndex((e) => e.user.id === t.user.id);
966
+ return n === -1 ? [...e, t] : [
967
+ ...e.slice(0, n),
968
+ t,
969
+ ...e.slice(n + 1)
970
+ ];
971
+ }
972
+ function E(e, t) {
973
+ let n = t.status === "read" ? "readBy" : "deliveredTo", r = D(e[n] ?? [], t);
974
+ return {
975
+ ...e,
976
+ [n]: r,
977
+ status: t.status === "read" || e.status === "read" ? "read" : "delivered"
978
+ };
979
+ }
980
+ function D(e, t) {
981
+ let n = e.findIndex((e) => e.userId === t.userId && e.status === t.status);
982
+ return n === -1 ? [...e, t] : [
983
+ ...e.slice(0, n),
984
+ t,
985
+ ...e.slice(n + 1)
986
+ ];
987
+ }
988
+ function O(e) {
989
+ return [...e].sort((e, t) => {
990
+ let n = e.lastMessage?.createdAt ?? e.updatedAt ?? e.createdAt, r = t.lastMessage?.createdAt ?? t.updatedAt ?? t.createdAt;
991
+ return Date.parse(r) - Date.parse(n);
992
+ });
993
+ }
994
+ function k(e) {
995
+ return [...e].sort((e, t) => Date.parse(e.createdAt) - Date.parse(t.createdAt));
996
+ }
997
+ function A(e, t) {
998
+ return O(e.map((e) => e.id === t.conversationId ? {
999
+ ...e,
1000
+ lastMessage: t,
1001
+ updatedAt: t.createdAt
1002
+ } : e));
1003
+ }
1004
+ function j(e) {
1005
+ return {
1006
+ id: `message:${e.id}`,
1007
+ kind: "message",
1008
+ title: e.sender.name,
1009
+ body: e.text ?? e.attachments?.[0]?.name ?? "Sent a message",
1010
+ conversationId: e.conversationId,
1011
+ messageId: e.id,
1012
+ actor: e.sender,
1013
+ avatarUrl: e.sender.avatarUrl,
1014
+ read: !1,
1015
+ createdAt: e.createdAt,
1016
+ metadata: e.metadata
1017
+ };
1018
+ }
1019
+ function M(e) {
1020
+ return e instanceof Error ? e.message : "Unexpected ChatEmi error";
1021
+ }
1022
+ //#endregion
1023
+ //#region src/components/ChatEmiMessenger.tsx
1024
+ function N({ className: e, emptyState: t, composerPlaceholder: n = "Write a message...", showSidebar: a = !0, theme: d, enableAdminControls: f = !0, enableMessageActions: p = !0, enableMediaPreview: m = !0, renderConversation: h, renderMessage: g }) {
1025
+ let { actions: _, activeConversation: v, activeMessages: y, connectionStatus: b, conversations: x, currentUser: S, error: w, loading: T, theme: E, typingByConversation: D } = C(), [O, k] = s(""), [A, j] = s(""), [M, N] = s(""), [I, V] = s([]), [H, U] = s(!1), [q, J] = s(), [Y, X] = s([]), [Z, Q] = s(!1), $ = o(void 0), ee = d ?? E, te = i(() => {
1026
+ let e = A.trim().toLowerCase();
1027
+ return e ? x.filter((t) => R(t, S?.id).toLowerCase().includes(e)) : x;
1028
+ }, [
1029
+ x,
1030
+ S?.id,
1031
+ A
1032
+ ]), ne = v ? D[v.id] ?? [] : [], re = i(() => y.map((e) => e.id).join(","), [y]), ie = v?.members?.find((e) => e.user.id === S?.id), ae = f && !!(ie && [
1033
+ "owner",
1034
+ "admin",
1035
+ "moderator"
1036
+ ].includes(ie.role));
1037
+ r(() => {
1038
+ if (!v || !re) return;
1039
+ let e = y.filter((e) => e.sender.id !== S?.id).map((e) => e.id);
1040
+ e.length > 0 && _.markRead(v.id, e);
1041
+ }, [
1042
+ _,
1043
+ v,
1044
+ y,
1045
+ S?.id,
1046
+ re
1047
+ ]), r(() => {
1048
+ let e = !0;
1049
+ async function t() {
1050
+ if (M.trim().length < 2) {
1051
+ V([]);
1052
+ return;
1053
+ }
1054
+ let t = await _.searchUsers({
1055
+ query: M.trim(),
1056
+ limit: 8
1057
+ });
1058
+ e && V(t);
1059
+ }
1060
+ return t(), () => {
1061
+ e = !1;
1062
+ };
1063
+ }, [_, M]);
1064
+ async function oe(e) {
1065
+ if (e.preventDefault(), !v || !O.trim() && Y.length === 0) return;
1066
+ let t = O.trim(), n = Y;
1067
+ k(""), X([]), _.stopTyping(v.id);
1068
+ try {
1069
+ await _.sendMessage({
1070
+ conversationId: v.id,
1071
+ text: t || void 0,
1072
+ attachments: n.length > 0 ? n : void 0,
1073
+ kind: G(n),
1074
+ replyToId: q?.id
1075
+ }), J(void 0);
1076
+ } catch {
1077
+ k(t), X(n);
1078
+ }
1079
+ }
1080
+ async function se(e) {
1081
+ if (e?.length) {
1082
+ Q(!0);
1083
+ try {
1084
+ let t = await Promise.all(Array.from(e).map((e) => _.uploadAttachment({
1085
+ file: e,
1086
+ name: e.name,
1087
+ type: W(e)
1088
+ })));
1089
+ X((e) => [...e, ...t]);
1090
+ } finally {
1091
+ Q(!1);
1092
+ }
1093
+ }
1094
+ }
1095
+ function ce(e) {
1096
+ k(e), v && (_.startTyping(v.id), $.current && clearTimeout($.current), $.current = setTimeout(() => _.stopTyping(v.id), 1400));
1097
+ }
1098
+ return /* @__PURE__ */ u("section", {
1099
+ className: ["chatemi", e].filter(Boolean).join(" "),
1100
+ "data-status": b,
1101
+ "data-theme": ee,
1102
+ children: [a ? /* @__PURE__ */ u("aside", {
1103
+ className: "chatemi__sidebar",
1104
+ "aria-label": "Conversations",
1105
+ children: [
1106
+ /* @__PURE__ */ u("div", {
1107
+ className: "chatemi__brand",
1108
+ children: [/* @__PURE__ */ u("div", { children: [/* @__PURE__ */ l("strong", { children: "ChatEmi" }), /* @__PURE__ */ l("span", { children: B(b) })] }), /* @__PURE__ */ l("span", { className: `chatemi__status chatemi__status--${b}` })]
1109
+ }),
1110
+ /* @__PURE__ */ u("label", {
1111
+ className: "chatemi__search",
1112
+ children: [/* @__PURE__ */ l("span", {
1113
+ className: "chatemi__sr-only",
1114
+ children: "Search conversations"
1115
+ }), /* @__PURE__ */ l("input", {
1116
+ value: A,
1117
+ onChange: (e) => j(e.target.value),
1118
+ placeholder: "Search chats"
1119
+ })]
1120
+ }),
1121
+ /* @__PURE__ */ l("div", {
1122
+ className: "chatemi__conversation-list",
1123
+ children: te.map((e) => {
1124
+ let t = e.id === v?.id;
1125
+ return /* @__PURE__ */ l("button", {
1126
+ className: `chatemi__conversation ${t ? "chatemi__conversation--active" : ""}`,
1127
+ onClick: () => void _.openConversation(e.id),
1128
+ type: "button",
1129
+ children: h ? h(e, t) : /* @__PURE__ */ l(P, {
1130
+ conversation: e,
1131
+ currentUserId: S?.id
1132
+ })
1133
+ }, e.id);
1134
+ })
1135
+ })
1136
+ ]
1137
+ }) : null, /* @__PURE__ */ u("main", {
1138
+ className: "chatemi__main",
1139
+ children: [v ? /* @__PURE__ */ u(c, { children: [
1140
+ /* @__PURE__ */ u("header", {
1141
+ className: "chatemi__header",
1142
+ children: [
1143
+ /* @__PURE__ */ l(L, {
1144
+ conversation: v,
1145
+ currentUserId: S?.id
1146
+ }),
1147
+ /* @__PURE__ */ u("div", { children: [/* @__PURE__ */ l("strong", { children: R(v, S?.id) }), /* @__PURE__ */ l("span", { children: ne.length > 0 ? `${ne.map((e) => e.user.name).join(", ")} typing...` : z(v, S?.id) })] }),
1148
+ ae ? /* @__PURE__ */ l("button", {
1149
+ className: "chatemi__header-action",
1150
+ onClick: () => U((e) => !e),
1151
+ type: "button",
1152
+ children: "Members"
1153
+ }) : null
1154
+ ]
1155
+ }),
1156
+ H && v ? /* @__PURE__ */ u("section", {
1157
+ className: "chatemi__members",
1158
+ "aria-label": "Member management",
1159
+ children: [
1160
+ /* @__PURE__ */ l("div", {
1161
+ className: "chatemi__members-list",
1162
+ children: (v.members ?? v.participants.map((e) => K(e))).map((e) => /* @__PURE__ */ u("div", {
1163
+ className: "chatemi__member",
1164
+ children: [
1165
+ e.user.avatarUrl ? /* @__PURE__ */ l("img", {
1166
+ alt: "",
1167
+ className: "chatemi__member-avatar",
1168
+ src: e.user.avatarUrl
1169
+ }) : /* @__PURE__ */ l("span", {
1170
+ className: "chatemi__member-avatar",
1171
+ children: e.user.name.slice(0, 2).toUpperCase()
1172
+ }),
1173
+ /* @__PURE__ */ u("span", { children: [/* @__PURE__ */ l("strong", { children: e.user.name }), /* @__PURE__ */ l("small", { children: e.role })] }),
1174
+ e.role !== "owner" && e.user.id !== S?.id ? /* @__PURE__ */ u("span", {
1175
+ className: "chatemi__member-actions",
1176
+ children: [
1177
+ /* @__PURE__ */ l("button", {
1178
+ onClick: () => void _.updateMember({
1179
+ conversationId: v.id,
1180
+ userId: e.user.id,
1181
+ role: "admin"
1182
+ }),
1183
+ type: "button",
1184
+ children: "Admin"
1185
+ }),
1186
+ /* @__PURE__ */ l("button", {
1187
+ onClick: () => void _.updateMember({
1188
+ conversationId: v.id,
1189
+ userId: e.user.id,
1190
+ role: "member"
1191
+ }),
1192
+ type: "button",
1193
+ children: "Member"
1194
+ }),
1195
+ /* @__PURE__ */ l("button", {
1196
+ onClick: () => void _.removeMember({
1197
+ conversationId: v.id,
1198
+ userId: e.user.id
1199
+ }),
1200
+ type: "button",
1201
+ children: "Remove"
1202
+ })
1203
+ ]
1204
+ }) : null
1205
+ ]
1206
+ }, e.user.id))
1207
+ }),
1208
+ /* @__PURE__ */ u("label", {
1209
+ className: "chatemi__member-search",
1210
+ children: [/* @__PURE__ */ l("span", { children: "Find users from external directory" }), /* @__PURE__ */ l("input", {
1211
+ onChange: (e) => N(e.target.value),
1212
+ placeholder: "Search users",
1213
+ value: M
1214
+ })]
1215
+ }),
1216
+ I.length > 0 ? /* @__PURE__ */ l("div", {
1217
+ className: "chatemi__user-results",
1218
+ children: I.map((e) => /* @__PURE__ */ u("button", {
1219
+ onClick: () => void _.addMembers(v.id, [e.id]),
1220
+ type: "button",
1221
+ children: [
1222
+ e.avatarUrl ? /* @__PURE__ */ l("img", {
1223
+ alt: "",
1224
+ src: e.avatarUrl
1225
+ }) : null,
1226
+ "Add ",
1227
+ e.name
1228
+ ]
1229
+ }, e.id))
1230
+ }) : null
1231
+ ]
1232
+ }) : null,
1233
+ /* @__PURE__ */ u("div", {
1234
+ className: "chatemi__messages",
1235
+ role: "log",
1236
+ "aria-live": "polite",
1237
+ children: [y.map((e) => {
1238
+ let t = e.sender.id === S?.id;
1239
+ return g ? /* @__PURE__ */ l("div", {
1240
+ className: "chatemi__message-shell",
1241
+ children: g(e, t)
1242
+ }, e.id) : /* @__PURE__ */ l(F, {
1243
+ enableActions: p,
1244
+ enableMediaPreview: m,
1245
+ isMine: t,
1246
+ message: e,
1247
+ onForward: (e) => {
1248
+ let t = window.prompt("Forward to conversation id");
1249
+ t && _.forwardMessage({
1250
+ sourceConversationId: e.conversationId,
1251
+ targetConversationId: t,
1252
+ messageId: e.id
1253
+ });
1254
+ },
1255
+ onReply: J
1256
+ }, e.id);
1257
+ }), y.length === 0 && !T ? /* @__PURE__ */ l("div", {
1258
+ className: "chatemi__empty",
1259
+ children: "No messages yet. Start the conversation."
1260
+ }) : null]
1261
+ }),
1262
+ Y.length > 0 ? /* @__PURE__ */ l("div", {
1263
+ className: "chatemi__attachments",
1264
+ children: Y.map((e) => /* @__PURE__ */ l("button", {
1265
+ className: "chatemi__attachment-pill",
1266
+ onClick: () => X((t) => t.filter((t) => t.id !== e.id)),
1267
+ type: "button",
1268
+ children: e.name ?? e.type
1269
+ }, e.id))
1270
+ }) : null,
1271
+ q ? /* @__PURE__ */ u("div", {
1272
+ className: "chatemi__replying",
1273
+ children: [/* @__PURE__ */ u("span", { children: [
1274
+ "Replying to ",
1275
+ /* @__PURE__ */ l("strong", { children: q.sender.name }),
1276
+ ": ",
1277
+ q.text ?? q.attachments?.[0]?.name ?? "media"
1278
+ ] }), /* @__PURE__ */ l("button", {
1279
+ onClick: () => J(void 0),
1280
+ type: "button",
1281
+ children: "Cancel"
1282
+ })]
1283
+ }) : null,
1284
+ /* @__PURE__ */ u("form", {
1285
+ className: "chatemi__composer",
1286
+ onSubmit: (e) => void oe(e),
1287
+ children: [
1288
+ /* @__PURE__ */ u("label", {
1289
+ className: "chatemi__upload",
1290
+ children: [/* @__PURE__ */ l("span", { children: Z ? "Uploading" : "Attach" }), /* @__PURE__ */ l("input", {
1291
+ disabled: Z,
1292
+ multiple: !0,
1293
+ onChange: (e) => void se(e.target.files),
1294
+ type: "file"
1295
+ })]
1296
+ }),
1297
+ /* @__PURE__ */ l("textarea", {
1298
+ onBlur: () => _.stopTyping(v.id),
1299
+ onChange: (e) => ce(e.target.value),
1300
+ placeholder: n,
1301
+ rows: 1,
1302
+ value: O
1303
+ }),
1304
+ /* @__PURE__ */ l("button", {
1305
+ disabled: Z || !O.trim() && Y.length === 0,
1306
+ type: "submit",
1307
+ children: "Send"
1308
+ })
1309
+ ]
1310
+ })
1311
+ ] }) : t ?? /* @__PURE__ */ l("div", {
1312
+ className: "chatemi__empty chatemi__empty--screen",
1313
+ children: "Select a conversation to start messaging."
1314
+ }), w ? /* @__PURE__ */ l("div", {
1315
+ className: "chatemi__error",
1316
+ children: w
1317
+ }) : null]
1318
+ })]
1319
+ });
1320
+ }
1321
+ function P({ conversation: e, currentUserId: t }) {
1322
+ return /* @__PURE__ */ u(c, { children: [
1323
+ /* @__PURE__ */ l(L, {
1324
+ conversation: e,
1325
+ currentUserId: t
1326
+ }),
1327
+ /* @__PURE__ */ u("span", {
1328
+ className: "chatemi__conversation-body",
1329
+ children: [/* @__PURE__ */ l("strong", { children: R(e, t) }), /* @__PURE__ */ l("small", { children: e.lastMessage?.text ?? e.description ?? "No messages yet" })]
1330
+ }),
1331
+ e.unreadCount ? /* @__PURE__ */ l("span", {
1332
+ className: "chatemi__badge",
1333
+ children: e.unreadCount
1334
+ }) : null
1335
+ ] });
1336
+ }
1337
+ function F({ message: e, isMine: t, enableActions: n, enableMediaPreview: r, onReply: i, onForward: a }) {
1338
+ return /* @__PURE__ */ u("article", {
1339
+ className: `chatemi__message ${t ? "chatemi__message--mine" : ""}`,
1340
+ children: [
1341
+ t ? null : /* @__PURE__ */ l("strong", {
1342
+ className: "chatemi__message-sender",
1343
+ children: e.sender.name
1344
+ }),
1345
+ e.forwardedFrom ? /* @__PURE__ */ u("div", {
1346
+ className: "chatemi__forwarded",
1347
+ children: ["Forwarded from ", e.forwardedFrom.name]
1348
+ }) : null,
1349
+ e.replyTo ? /* @__PURE__ */ u("blockquote", {
1350
+ className: "chatemi__reply-preview",
1351
+ children: [/* @__PURE__ */ l("strong", { children: e.replyTo.sender.name }), /* @__PURE__ */ l("span", { children: e.replyTo.text ?? e.replyTo.attachments?.[0]?.name ?? "media" })]
1352
+ }) : null,
1353
+ e.text ? /* @__PURE__ */ l("p", { children: e.text }) : null,
1354
+ e.html ? /* @__PURE__ */ l("div", {
1355
+ className: "chatemi__message-html",
1356
+ dangerouslySetInnerHTML: { __html: e.html }
1357
+ }) : null,
1358
+ e.attachments?.length ? /* @__PURE__ */ l("div", {
1359
+ className: "chatemi__message-attachments",
1360
+ children: e.attachments.map((e) => I(e, r))
1361
+ }) : null,
1362
+ e.reactions?.length ? /* @__PURE__ */ l("div", {
1363
+ className: "chatemi__reactions",
1364
+ children: e.reactions.map((e) => /* @__PURE__ */ u("span", { children: [
1365
+ e.emoji,
1366
+ " ",
1367
+ e.count
1368
+ ] }, e.emoji))
1369
+ }) : null,
1370
+ /* @__PURE__ */ u("footer", { children: [/* @__PURE__ */ l("time", {
1371
+ dateTime: e.createdAt,
1372
+ children: V(e.createdAt)
1373
+ }), t && e.status ? /* @__PURE__ */ l("span", { children: U(e) }) : null] }),
1374
+ n ? /* @__PURE__ */ u("div", {
1375
+ className: "chatemi__message-actions",
1376
+ children: [/* @__PURE__ */ l("button", {
1377
+ onClick: () => i(e),
1378
+ type: "button",
1379
+ children: "Reply"
1380
+ }), /* @__PURE__ */ l("button", {
1381
+ onClick: () => a(e),
1382
+ type: "button",
1383
+ children: "Forward"
1384
+ })]
1385
+ }) : null
1386
+ ]
1387
+ });
1388
+ }
1389
+ function I(e, t) {
1390
+ return t && e.type === "image" ? /* @__PURE__ */ u("a", {
1391
+ className: "chatemi__media chatemi__media--image",
1392
+ href: e.url,
1393
+ rel: "noreferrer",
1394
+ target: "_blank",
1395
+ children: [/* @__PURE__ */ l("img", {
1396
+ alt: e.caption ?? e.name ?? "image",
1397
+ src: e.url
1398
+ }), e.caption ? /* @__PURE__ */ l("span", { children: e.caption }) : null]
1399
+ }, e.id) : t && e.type === "video" ? /* @__PURE__ */ u("figure", {
1400
+ className: "chatemi__media chatemi__media--video",
1401
+ children: [/* @__PURE__ */ l("video", {
1402
+ controls: !0,
1403
+ poster: e.thumbnailUrl,
1404
+ src: e.url
1405
+ }), /* @__PURE__ */ l("figcaption", { children: e.caption ?? e.name ?? "Video" })]
1406
+ }, e.id) : e.type === "voice" || e.type === "audio" ? /* @__PURE__ */ u("div", {
1407
+ className: "chatemi__media chatemi__media--audio",
1408
+ children: [/* @__PURE__ */ l("span", { children: e.type === "voice" ? "Voice message" : e.name ?? "Audio" }), /* @__PURE__ */ l("audio", {
1409
+ controls: !0,
1410
+ src: e.url
1411
+ })]
1412
+ }, e.id) : /* @__PURE__ */ u("a", {
1413
+ className: "chatemi__media chatemi__media--file",
1414
+ href: e.url,
1415
+ rel: "noreferrer",
1416
+ target: "_blank",
1417
+ children: [
1418
+ e.thumbnailUrl ? /* @__PURE__ */ l("img", {
1419
+ alt: "",
1420
+ src: e.thumbnailUrl
1421
+ }) : null,
1422
+ /* @__PURE__ */ l("span", { children: e.name ?? e.type }),
1423
+ e.size ? /* @__PURE__ */ l("small", { children: q(e.size) }) : null
1424
+ ]
1425
+ }, e.id);
1426
+ }
1427
+ function L({ conversation: e, currentUserId: t }) {
1428
+ let n = R(e, t), r = e.participants.find((e) => e.id !== t) ?? e.participants[0], i = e.avatarUrl ?? r?.avatarUrl;
1429
+ return i ? /* @__PURE__ */ l("img", {
1430
+ alt: "",
1431
+ className: "chatemi__avatar",
1432
+ src: i
1433
+ }) : /* @__PURE__ */ l("span", {
1434
+ className: "chatemi__avatar chatemi__avatar--fallback",
1435
+ children: n.slice(0, 2).toUpperCase()
1436
+ });
1437
+ }
1438
+ function R(e, t) {
1439
+ return e.title ? e.title : e.participants.filter((e) => e.id !== t).map((e) => e.name).join(", ") || "Untitled chat";
1440
+ }
1441
+ function z(e, t) {
1442
+ if (e.type === "channel") return `${e.participants.length} subscribers`;
1443
+ if (e.type === "group") return `${e.participants.length} members`;
1444
+ let n = e.participants.find((e) => e.id !== t);
1445
+ return n ? n.presence === "online" ? "online" : n.lastSeenAt ? `last seen ${H(n.lastSeenAt)}` : "last seen recently" : "Saved messages";
1446
+ }
1447
+ function B(e) {
1448
+ switch (e) {
1449
+ case "connected": return "Realtime online";
1450
+ case "connecting":
1451
+ case "reconnecting": return "Connecting";
1452
+ case "error": return "Connection issue";
1453
+ default: return "Realtime idle";
1454
+ }
1455
+ }
1456
+ function V(e) {
1457
+ return new Intl.DateTimeFormat(void 0, {
1458
+ hour: "2-digit",
1459
+ minute: "2-digit"
1460
+ }).format(new Date(e));
1461
+ }
1462
+ function H(e) {
1463
+ let t = Date.now() - Date.parse(e), n = Math.max(1, Math.round(t / 6e4));
1464
+ if (n < 60) return `${n}m ago`;
1465
+ let r = Math.round(n / 60);
1466
+ return r < 24 ? `${r}h ago` : `${Math.round(r / 24)}d ago`;
1467
+ }
1468
+ function U(e) {
1469
+ return e.status === "read" || e.readBy?.length ? "read" : e.status === "delivered" || e.deliveredTo?.length ? "delivered" : e.status ?? "sent";
1470
+ }
1471
+ function W(e) {
1472
+ return e.type.startsWith("image/") ? "image" : e.type.startsWith("video/") ? "video" : e.type.startsWith("audio/") ? e.name.toLowerCase().includes("voice") ? "voice" : "audio" : "file";
1473
+ }
1474
+ function G(e) {
1475
+ return e.some((e) => e.type === "voice") ? "voice" : e.length > 0 ? "media" : "text";
1476
+ }
1477
+ function K(e) {
1478
+ return {
1479
+ user: e,
1480
+ role: "member",
1481
+ joinedAt: (/* @__PURE__ */ new Date()).toISOString()
1482
+ };
1483
+ }
1484
+ function q(e) {
1485
+ return e < 1024 ? `${e} B` : e < 1024 * 1024 ? `${Math.round(e / 1024)} KB` : `${(e / 1024 / 1024).toFixed(1)} MB`;
1486
+ }
1487
+ //#endregion
1488
+ //#region src/components/ChatEmiLauncher.tsx
1489
+ var J = {
1490
+ width: 420,
1491
+ height: 680
1492
+ }, Y = {
1493
+ width: 340,
1494
+ height: 480
1495
+ }, X = {
1496
+ width: 920,
1497
+ height: 860
1498
+ };
1499
+ function Z({ className: e, title: t = "Messages", subtitle: n = "ChatEmi", placement: r = "bottom-right", defaultOpen: a = !1, showNotificationList: c = !0, badgeCount: d, initialSize: f = J, minSize: p = Y, maxSize: m = X, markNotificationsReadOnOpen: h = !0, launcherIcon: g, ..._ }) {
1500
+ let { actions: v, conversations: y, connectionStatus: b, notifications: x, theme: S, unreadNotificationCount: w } = C(), [T, E] = s(a), [D, O] = s({
1501
+ x: 0,
1502
+ y: 0
1503
+ }), k = o(void 0), A = i(() => y.reduce((e, t) => e + (t.unreadCount ?? 0), 0), [y]), j = d ?? (w > 0 ? w : A);
1504
+ function M() {
1505
+ E((e) => {
1506
+ let t = !e;
1507
+ return t && h && v.markNotificationsRead(), t && v.requestNotificationPermission(), t;
1508
+ });
1509
+ }
1510
+ function P(e) {
1511
+ e.target.closest("button") || (e.currentTarget.setPointerCapture(e.pointerId), k.current = {
1512
+ pointerId: e.pointerId,
1513
+ startX: e.clientX,
1514
+ startY: e.clientY,
1515
+ originX: D.x,
1516
+ originY: D.y
1517
+ });
1518
+ }
1519
+ function F(e) {
1520
+ let t = k.current;
1521
+ !t || t.pointerId !== e.pointerId || O({
1522
+ x: t.originX + e.clientX - t.startX,
1523
+ y: t.originY + e.clientY - t.startY
1524
+ });
1525
+ }
1526
+ function I(e) {
1527
+ k.current?.pointerId === e.pointerId && (k.current = void 0);
1528
+ }
1529
+ return /* @__PURE__ */ u("div", {
1530
+ className: [
1531
+ "chatemi-launcher",
1532
+ `chatemi-launcher--${r}`,
1533
+ e
1534
+ ].filter(Boolean).join(" "),
1535
+ "data-theme": _.theme ?? S,
1536
+ children: [T ? /* @__PURE__ */ u("section", {
1537
+ "aria-label": t,
1538
+ className: "chatemi-launcher__modal",
1539
+ style: {
1540
+ width: f.width,
1541
+ height: f.height,
1542
+ minWidth: p.width,
1543
+ minHeight: p.height,
1544
+ maxWidth: m.width,
1545
+ maxHeight: m.height,
1546
+ transform: `translate(${D.x}px, ${D.y}px)`
1547
+ },
1548
+ children: [
1549
+ /* @__PURE__ */ u("div", {
1550
+ className: "chatemi-launcher__modal-header",
1551
+ onPointerDown: P,
1552
+ onPointerMove: F,
1553
+ onPointerUp: I,
1554
+ onPointerCancel: I,
1555
+ children: [/* @__PURE__ */ u("div", { children: [/* @__PURE__ */ l("strong", { children: t }), /* @__PURE__ */ u("span", { children: [
1556
+ n,
1557
+ " · ",
1558
+ b
1559
+ ] })] }), /* @__PURE__ */ l("button", {
1560
+ "aria-label": "Close messages",
1561
+ onClick: M,
1562
+ type: "button",
1563
+ children: "Close"
1564
+ })]
1565
+ }),
1566
+ c && x.length > 0 ? /* @__PURE__ */ u("div", {
1567
+ className: "chatemi-launcher__notifications",
1568
+ "aria-label": "Notifications",
1569
+ children: [x.slice(0, 3).map((e) => /* @__PURE__ */ u("button", {
1570
+ className: e.read ? "chatemi-launcher__notification" : "chatemi-launcher__notification chatemi-launcher__notification--unread",
1571
+ onClick: () => {
1572
+ e.conversationId && v.openConversation(e.conversationId), v.markNotificationsRead([e.id]);
1573
+ },
1574
+ type: "button",
1575
+ children: [e.avatarUrl ? /* @__PURE__ */ l("img", {
1576
+ alt: "",
1577
+ src: e.avatarUrl
1578
+ }) : /* @__PURE__ */ l("span", { children: e.title.slice(0, 2).toUpperCase() }), /* @__PURE__ */ u("span", { children: [/* @__PURE__ */ l("strong", { children: e.title }), e.body ? /* @__PURE__ */ l("small", { children: e.body }) : null] })]
1579
+ }, e.id)), /* @__PURE__ */ l("button", {
1580
+ className: "chatemi-launcher__clear",
1581
+ onClick: v.clearNotifications,
1582
+ type: "button",
1583
+ children: "Clear"
1584
+ })]
1585
+ }) : null,
1586
+ /* @__PURE__ */ l(N, { ..._ })
1587
+ ]
1588
+ }) : null, /* @__PURE__ */ u("button", {
1589
+ "aria-expanded": T,
1590
+ "aria-label": T ? "Close messages" : "Open messages",
1591
+ className: "chatemi-launcher__toggle",
1592
+ onClick: M,
1593
+ type: "button",
1594
+ children: [/* @__PURE__ */ l("span", {
1595
+ className: "chatemi-launcher__icon",
1596
+ children: g ?? /* @__PURE__ */ l(Q, {})
1597
+ }), j > 0 ? /* @__PURE__ */ l("span", {
1598
+ className: "chatemi-launcher__badge",
1599
+ children: j > 99 ? "99+" : j
1600
+ }) : null]
1601
+ })]
1602
+ });
1603
+ }
1604
+ function Q() {
1605
+ return /* @__PURE__ */ u("svg", {
1606
+ "aria-hidden": "true",
1607
+ fill: "none",
1608
+ height: "28",
1609
+ viewBox: "0 0 28 28",
1610
+ width: "28",
1611
+ children: [/* @__PURE__ */ l("path", {
1612
+ 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",
1613
+ stroke: "currentColor",
1614
+ strokeLinecap: "round",
1615
+ strokeLinejoin: "round",
1616
+ strokeWidth: "2"
1617
+ }), /* @__PURE__ */ l("path", {
1618
+ d: "M10 12.5h8M10 16h5",
1619
+ stroke: "currentColor",
1620
+ strokeLinecap: "round",
1621
+ strokeWidth: "2"
1622
+ })]
1623
+ });
1624
+ }
1625
+ //#endregion
1626
+ export { p as ChatEmiApi, f as ChatEmiApiError, Z as ChatEmiLauncher, N as ChatEmiMessenger, S as ChatEmiProvider, v as ChatEmiSocket, C as useChatEmi };