@ethora/chat-component 26.10.1 → 26.10.4

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.
Files changed (70) hide show
  1. package/README.md +1 -0
  2. package/dist/{ChatProfileModal-Bf5jGPBZ.js → ChatProfileModal-Cke_6sXC.js} +3 -3
  3. package/dist/{FilePreviewModal-Bwx0RZZw.js → FilePreviewModal-CHEI7FWU.js} +1 -1
  4. package/dist/{ManageDataModal-Dhkp0LWH.js → ManageDataModal-9W1ZSLli.js} +2 -2
  5. package/dist/{MarkdownBody-_sUhW2O8.js → MarkdownBody-DFuD11jv.js} +1 -1
  6. package/dist/{MessageSearchModal-rwwWrILl.js → MessageSearchModal-FlVO7fz6.js} +2 -2
  7. package/dist/{PublicChatsModal-Byso6_vt.js → PublicChatsModal-BWDXSdBx.js} +2 -2
  8. package/dist/{SideDrawer-CQPOf0ek.js → SideDrawer-BQ413Bso.js} +3 -3
  9. package/dist/{UserProfileModal-Vj2kICvc.js → UserProfileModal-9aSW48jh.js} +3 -3
  10. package/dist/{UserSettingsModal-CioX4ZIV.js → UserSettingsModal--sdi-ypb.js} +2 -2
  11. package/dist/{VideoCallOverlay-DodAkiam.js → VideoCallOverlay-DXJ_3E0D.js} +1 -1
  12. package/dist/{VisibilityModal-CIl7rkBM.js → VisibilityModal-DLi1r_OQ.js} +2 -2
  13. package/dist/api.config.d.ts +0 -23
  14. package/dist/components/ContextMenu/ContextMenuComponents.d.ts +24 -8
  15. package/dist/components/ExampleComponents/CustomMessage.d.ts +27 -9
  16. package/dist/components/MainComponents/ChooseChatMessage.d.ts +12 -4
  17. package/dist/components/MainComponents/EditWrapper.d.ts +12 -4
  18. package/dist/components/MainComponents/MessageList.d.ts +2 -1
  19. package/dist/components/MainComponents/SystemMessage.d.ts +3 -1
  20. package/dist/components/MenuRoom/MenuRoom.d.ts +2 -0
  21. package/dist/components/MessageBubble/CustomDivider.d.ts +3 -1
  22. package/dist/components/MessageBubble/MessageReply.d.ts +3 -1
  23. package/dist/components/Modals/FilePreviewModal/ViewerChrome.d.ts +54 -18
  24. package/dist/components/Modals/MessageSearchModal/MessageHitResults.d.ts +3 -1
  25. package/dist/components/Modals/ModalReportChat/ModalReportChat.d.ts +3 -1
  26. package/dist/components/Modals/NewChatModal/styledNewChatComponents.d.ts +15 -5
  27. package/dist/components/Modals/SettingsModals/SharedStyledComponents.d.ts +27 -15
  28. package/dist/components/Modals/SettingsModals/Visibility/StyledComponents.d.ts +18 -6
  29. package/dist/components/Modals/SideDrawer/DrawerProfileParts.d.ts +27 -9
  30. package/dist/components/Modals/SideDrawer/SideDrawer.d.ts +39 -13
  31. package/dist/components/Modals/motionVariants.d.ts +8 -10
  32. package/dist/components/Modals/styledModalComponents.d.ts +53 -25
  33. package/dist/components/Notification/index.d.ts +3 -1
  34. package/dist/components/RoomComponents/styled/StyledRoomComponents.d.ts +24 -10
  35. package/dist/components/UsersList/StyledComponents.d.ts +18 -6
  36. package/dist/components/styled/ChatWrapperBox.d.ts +9 -2
  37. package/dist/components/styled/DateLabel.d.ts +3 -1
  38. package/dist/components/styled/Loader.d.ts +3 -1
  39. package/dist/components/styled/MediaModal.d.ts +6 -2
  40. package/dist/components/styled/MediaSurface.d.ts +9 -3
  41. package/dist/components/styled/NewMessageLabel.d.ts +3 -1
  42. package/dist/components/styled/RoomListComponents/index.d.ts +66 -22
  43. package/dist/components/styled/StyledComponents.d.ts +137 -46
  44. package/dist/components/styled/StyledInputComponents/MediaComponents.d.ts +63 -21
  45. package/dist/components/styled/StyledInputComponents/StyledInputComponents.d.ts +60 -22
  46. package/dist/components/styled/StyledInputComponents/VideoComponents.d.ts +18 -6
  47. package/dist/components/styled/TreadLabel.d.ts +3 -1
  48. package/dist/components/styled/VideoMessage.d.ts +3 -1
  49. package/dist/{handleCopyClick-DGaZhy_u.js → handleCopyClick-Cja-CefH.js} +1 -1
  50. package/dist/helpers/mediaBubble.d.ts +28 -0
  51. package/dist/helpers/receiptState.d.ts +48 -0
  52. package/dist/helpers/textareaFit.d.ts +31 -0
  53. package/dist/helpers/timestamp.d.ts +7 -0
  54. package/dist/helpers/userEntry.d.ts +14 -0
  55. package/dist/hooks/useMediaProbe.d.ts +1 -0
  56. package/dist/hooks/useRoomInitialization.d.ts +2 -2
  57. package/dist/hooks/useRoomUrl.d.ts +2 -2
  58. package/dist/{main-CUB_w8AZ.js → main-DbepSYi_.js} +16681 -15285
  59. package/dist/main.js +1 -1
  60. package/dist/networking/api-requests/auth.api.d.ts +11 -5
  61. package/dist/networking/api-requests/rooms.api.d.ts +21 -0
  62. package/dist/networking/api-requests/user.api.d.ts +4 -4
  63. package/dist/networking/deliveryReceiptBatcher.d.ts +53 -0
  64. package/dist/networking/displayedMarkerThrottle.d.ts +29 -0
  65. package/dist/networking/receiptsPolicy.d.ts +32 -0
  66. package/dist/networking/xmppClient.d.ts +3 -3
  67. package/dist/roomStore/roomsSlice.d.ts +5 -1
  68. package/dist/types/models/config.model.d.ts +35 -0
  69. package/dist/types/models/room.model.d.ts +1 -0
  70. package/package.json +8 -7
package/dist/main.js CHANGED
@@ -1,4 +1,4 @@
1
- import { br as a, bs as r, bt as b, bu as o, bv as t, bw as u, bx as i, by as p, bz as h, bA as n, bB as d, i as l, bC as y, S as I, bD as T, bE as f, bF as m, bG as R, bH as g, bI as c, bJ as C, bK as A, bL as E, aq as F, ar as M, bM as Q, bN as k } from "./main-CUB_w8AZ.js";
1
+ import { br as a, bs as r, bt as b, bu as o, bv as t, bw as u, bx as i, by as p, bz as h, bA as n, bB as d, i as l, bC as y, S as I, bD as T, bE as f, bF as m, bG as R, bH as g, bI as c, bJ as C, bK as A, bL as E, aq as F, ar as M, bM as Q, bN as k } from "./main-DbepSYi_.js";
2
2
  export {
3
3
  a as AuthTestIds,
4
4
  r as Chat,
@@ -5,10 +5,16 @@ export declare function loginEmail(email: string, password: string): Promise<imp
5
5
  refreshToken: string;
6
6
  token: string;
7
7
  fileToken?: string;
8
- }, any, {}>>;
9
- export declare function loginSocial(idToken: string, accessToken: string, loginType: string, authToken?: string): Promise<import('axios').AxiosResponse<any, any, {}>>;
10
- export declare function registerSocial(idToken: string, accessToken: string, authToken: string, loginType: string, signUpPlan?: string): Promise<import('axios').AxiosResponse<any, any, {}>>;
11
- export declare function checkEmailExist(email: string): Promise<import('axios').AxiosResponse<any, any, {}>>;
8
+ }, any, {}, any>>;
9
+ export declare function loginSocial(idToken: string, accessToken: string, loginType: string, authToken?: string): Promise<import('axios').AxiosResponse<any, any, {}, any>>;
10
+ export declare function registerSocial(idToken: string, accessToken: string, authToken: string, loginType: string, signUpPlan?: string): Promise<import('axios').AxiosResponse<any, {
11
+ idToken: string;
12
+ accessToken: string;
13
+ loginType: string;
14
+ authToken: string;
15
+ signupPlan: string;
16
+ }, {}, any>>;
17
+ export declare function checkEmailExist(email: string): Promise<import('axios').AxiosResponse<any, any, {}, any>>;
12
18
  export declare function loginViaJwt(clientToken: string): Promise<User>;
13
19
  export declare const signInWithGoogle: () => Promise<{
14
20
  user?: undefined;
@@ -30,5 +36,5 @@ export interface UploadFileOptions {
30
36
  /** Upload progress for this request, 0 to 100. */
31
37
  onProgress?: (percent: number) => void;
32
38
  }
33
- export declare function uploadFile(formData: FormData, activeRoomJID: string, options?: UploadFileOptions): Promise<import('axios').AxiosResponse<any, any, {}>>;
39
+ export declare function uploadFile(formData: FormData, activeRoomJID: string, options?: UploadFileOptions): Promise<import('axios').AxiosResponse<any, FormData, {}, any>>;
34
40
  export declare function ensureUserFromMy(user: User | null | undefined): Promise<User | null>;
@@ -14,6 +14,27 @@ export declare function unmuteRoom(chatName: string): Promise<{
14
14
  chatName: string;
15
15
  muted: boolean;
16
16
  } | undefined>;
17
+ export declare function leaveRoom(chatName: string): Promise<{
18
+ chatName: string;
19
+ left: boolean;
20
+ } | undefined>;
21
+ export declare function blockUser(peer: string): Promise<{
22
+ user: string;
23
+ blocked: boolean;
24
+ } | undefined>;
25
+ export declare function unblockUser(peer: string): Promise<{
26
+ user: string;
27
+ blocked: boolean;
28
+ } | undefined>;
29
+ export interface BlockedUser {
30
+ _id: string;
31
+ firstName?: string;
32
+ lastName?: string;
33
+ profileImage?: string;
34
+ xmppUsername?: string;
35
+ createdAt?: string;
36
+ }
37
+ export declare function getBlockedUsers(): Promise<BlockedUser[]>;
17
38
  export declare function postRoom(data: PostRoom): Promise<any>;
18
39
  export declare function postPrivateRoom(username: string, e2ee?: boolean): Promise<ApiRoom>;
19
40
  export declare function postReportRoom(data: PostReportRoom): Promise<any>;
@@ -4,10 +4,10 @@ interface GetMyUserOptions {
4
4
  endpoint?: string;
5
5
  signal?: AbortSignal;
6
6
  }
7
- export declare function getDocuments(walletAddress: string): Promise<import('axios').AxiosResponse<any, any, {}>>;
8
- export declare function getExportMyData(): Promise<import('axios').AxiosResponse<any, any, {}>>;
9
- export declare function deleteMe(): Promise<import('axios').AxiosResponse<any, any, {}>>;
10
- export declare function updateMe(data: any): Promise<import('axios').AxiosResponse<any, any, {}>>;
7
+ export declare function getDocuments(walletAddress: string): Promise<import('axios').AxiosResponse<any, any, {}, any>>;
8
+ export declare function getExportMyData(): Promise<import('axios').AxiosResponse<any, any, {}, any>>;
9
+ export declare function deleteMe(): Promise<import('axios').AxiosResponse<any, any, {}, any>>;
10
+ export declare function updateMe(data: any): Promise<import('axios').AxiosResponse<any, any, {}, any>>;
11
11
  export declare function updateProfile(fd: FormData): Promise<{
12
12
  user: User;
13
13
  }>;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Coalesces XEP-0184 delivery receipts per room.
3
+ *
4
+ * Per room, the first message enqueued while no window is open is acked at
5
+ * once (leading edge). Everything that arrives during the following
6
+ * `coalesceMs` window is held, keeping only the newest message by timestamp,
7
+ * and that one is acked when the window closes (trailing edge). A receipt
8
+ * for a message implies delivery of everything before it, so one stanza can
9
+ * stand for a whole burst or a whole history replay.
10
+ */
11
+ export interface DeliveryReceiptBatcherOptions {
12
+ /** Sends one `<received/>` for `messageId` in `roomJid`. */
13
+ send: (roomJid: string, messageId: string) => void;
14
+ /** Injectable timer for tests. */
15
+ setTimer?: (fn: () => void, ms: number) => unknown;
16
+ /** Injectable timer cancel for tests. */
17
+ clearTimer?: (handle: unknown) => void;
18
+ /** Injectable clock, used when `enqueue` gets no timestamp. */
19
+ now?: () => number;
20
+ /** Bound on remembered acked ids (oldest forgotten first). */
21
+ maxAckedIds?: number;
22
+ }
23
+ export declare class DeliveryReceiptBatcher {
24
+ private readonly options;
25
+ private readonly rooms;
26
+ private readonly acked;
27
+ private readonly maxAckedIds;
28
+ private readonly setTimer;
29
+ private readonly clearTimer;
30
+ private readonly now;
31
+ constructor(options: DeliveryReceiptBatcherOptions);
32
+ /**
33
+ * Queues a receipt for `messageId`. `coalesceMs` is the window length used
34
+ * when a window opens; 0 sends every call straight away.
35
+ */
36
+ enqueue(roomJid: string, messageId: string, coalesceMs: number, timestamp?: number): void;
37
+ /** Cancels the window and drops the held message for one room. */
38
+ clearRoom(roomJid: string): void;
39
+ /**
40
+ * Cancels every window and drops every held message (disconnect). Acked
41
+ * ids are kept so a reconnect replay does not ack them twice.
42
+ */
43
+ clear(): void;
44
+ /** Full teardown: timers, held messages and the acked-id memory. */
45
+ destroy(): void;
46
+ /** Test/inspection helper: number of rooms with an open window. */
47
+ openWindowCount(): number;
48
+ private stateFor;
49
+ private ack;
50
+ private onWindowEnd;
51
+ private cancel;
52
+ private remember;
53
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Rate-limits XEP-0333 `<displayed/>` markers for one room. The first mark
3
+ * goes out at once; marks inside `minIntervalMs` of the last send are held
4
+ * and only the newest id is sent when the interval ends (trailing edge), so
5
+ * the final read watermark is never lost. `dispose()` sends a held mark
6
+ * immediately, then cancels the timer.
7
+ */
8
+ export interface DisplayedMarkerThrottleOptions {
9
+ minIntervalMs: number;
10
+ send: (messageId: string) => void;
11
+ now?: () => number;
12
+ setTimer?: (fn: () => void, ms: number) => unknown;
13
+ clearTimer?: (handle: unknown) => void;
14
+ }
15
+ export declare class DisplayedMarkerThrottle {
16
+ private readonly options;
17
+ private lastSentAt;
18
+ private pendingId;
19
+ private timer;
20
+ private disposed;
21
+ private readonly now;
22
+ private readonly setTimer;
23
+ private readonly clearTimer;
24
+ constructor(options: DisplayedMarkerThrottleOptions);
25
+ mark(messageId: string): void;
26
+ dispose(): void;
27
+ private onTimer;
28
+ private fire;
29
+ }
@@ -0,0 +1,32 @@
1
+ import { ReceiptsConfig } from '../types/models/config.model';
2
+ import { IMessage, IRoom } from '../types/types';
3
+ export declare const RECEIPTS_DEFAULTS: {
4
+ mode: "auto";
5
+ deliveryMaxRoomSize: number;
6
+ readMaxRoomSize: number;
7
+ maxAgeDays: number;
8
+ coalesceMs: number;
9
+ };
10
+ export interface ResolvedReceiptsConfig {
11
+ mode: 'auto' | 'all' | 'off';
12
+ deliveryMaxRoomSize: number;
13
+ readMaxRoomSize: number;
14
+ maxAgeDays: number;
15
+ coalesceMs: number;
16
+ }
17
+ /** Fills every unset or invalid option with its default. */
18
+ export declare const resolveReceiptsConfig: (cfg?: ReceiptsConfig | null) => ResolvedReceiptsConfig;
19
+ /**
20
+ * Other members of the room (the sender excluded). The larger of the true
21
+ * `usersCnt` and the loaded `members` list wins. 0 when the size is unknown.
22
+ */
23
+ export declare const roomOtherMembers: (room?: Pick<IRoom, "usersCnt" | "members"> | null) => number;
24
+ /** Timestamp of a message in ms (date, timestamp, then id), 0 when unreadable. */
25
+ export declare const messageTimestamp: (message: Pick<IMessage, "id" | "date" | "timestamp">) => number;
26
+ /**
27
+ * Whether this client should send a XEP-0184 `<received/>` for `message`.
28
+ * The caller has already excluded our own messages and MAM/echo duplicates.
29
+ */
30
+ export declare const shouldSendDelivery: (room: Pick<IRoom, "type" | "usersCnt" | "members"> | null | undefined, message: Pick<IMessage, "id" | "date" | "timestamp" | "isSystemMessage">, cfg: ResolvedReceiptsConfig, now: number) => boolean;
31
+ /** Whether this client should send a XEP-0333 `<displayed/>` for the room. */
32
+ export declare const shouldSendRead: (room: Pick<IRoom, "type" | "usersCnt" | "members"> | null | undefined, cfg: ResolvedReceiptsConfig) => boolean;
@@ -64,8 +64,7 @@ export declare class XmppClient implements XmppClientInterface {
64
64
  private historyQueueWorkerScheduled;
65
65
  private historyQueueWorkerTimer;
66
66
  private roomHistoryEpoch;
67
- private historyReceiptAckedIds;
68
- private readonly maxHistoryReceiptAckedIds;
67
+ private readonly deliveryBatcher;
69
68
  private criticalSendUntil;
70
69
  private activeRoomBoostUntil;
71
70
  private sendStartedAtById;
@@ -317,7 +316,8 @@ export declare class XmppClient implements XmppClientInterface {
317
316
  sendMessageReactionStanza(messageId: string, roomJid: string, reactionsList: string[], reactionSymbol?: any): void;
318
317
  sendReceivedReceiptStanza(messageId: string, roomJid: string): void;
319
318
  sendDisplayedMarkerStanza(messageId: string, roomJid: string): void;
320
- sendHistoryReceivedReceiptStanza(roomJid: string, messageId: string): void;
319
+ queueDeliveryReceipt(roomJid: string, messageId: string, timestamp?: number, coalesceMs?: number): void;
320
+ sendHistoryReceivedReceiptStanza(roomJid: string, messageId: string, timestamp?: number, coalesceMs?: number): void;
321
321
  sendTypingRequestStanza(chatId: string, fullName: string, start: boolean): void;
322
322
  getChatsPrivateStoreRequestStanza: () => Promise<Record<string, string | number> | null>;
323
323
  actionSetTimestampToPrivateStoreStanza(chatId: string, timestamp: number, chats?: string[]): Promise<void>;
@@ -193,7 +193,11 @@ export declare const addRoom: import('@reduxjs/toolkit').ActionCreatorWithOption
193
193
  messageId: string;
194
194
  fromXmppUsername: string;
195
195
  timestamp: number;
196
- }, "roomMessages/setReceivedReceipt">, setDisplayedMarker: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
196
+ }, "roomMessages/setReceivedReceipt">, setReceivedWatermark: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
197
+ roomJID: string;
198
+ xmppUsername: string;
199
+ messageId: string;
200
+ }, "roomMessages/setReceivedWatermark">, setDisplayedMarker: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
197
201
  roomJID: string;
198
202
  xmppUsername: string;
199
203
  messageId: string;
@@ -160,6 +160,32 @@ export type ChatErrorFallbackRenderer = (context: ChatErrorInfo & {
160
160
  reset: () => void;
161
161
  }) => React.ReactNode;
162
162
  export type ChatColorScheme = 'light' | 'dark' | 'system';
163
+ /**
164
+ * Delivery (XEP-0184 `<received/>`) and read (XEP-0333 `<displayed/>`)
165
+ * receipts. The client always DISPLAYS the receipts it receives; these
166
+ * options only control what this client SENDS, so large rooms do not turn
167
+ * every message into a broadcast to every occupant.
168
+ *
169
+ * - `mode`: `'auto'` (default) sends delivery receipts only in rooms of up to
170
+ * `deliveryMaxRoomSize` other members and read markers only in rooms of up
171
+ * to `readMaxRoomSize` other members; `'all'` always sends (still coalesced);
172
+ * `'off'` never sends a delivery or read signal.
173
+ * - `deliveryMaxRoomSize` / `readMaxRoomSize`: room size (other members, the
174
+ * sender excluded) above which the signal is skipped in `'auto'`. Private
175
+ * 1:1 rooms and rooms whose size is unknown always send.
176
+ * - `maxAgeDays`: in `'auto'`, messages older than this are not acknowledged
177
+ * (old history replays do not generate receipts).
178
+ * - `coalesceMs`: delivery receipts are sent at most once per window per
179
+ * room; the first one goes out at once, the rest of the window is collapsed
180
+ * into one receipt for the newest message.
181
+ */
182
+ export interface ReceiptsConfig {
183
+ mode?: 'auto' | 'all' | 'off';
184
+ deliveryMaxRoomSize?: number;
185
+ readMaxRoomSize?: number;
186
+ maxAgeDays?: number;
187
+ coalesceMs?: number;
188
+ }
163
189
  export interface IConfig {
164
190
  appId?: string;
165
191
  /**
@@ -335,6 +361,13 @@ export interface IConfig {
335
361
  * outright even then, e.g. because it renders its own mute control.
336
362
  */
337
363
  disableRoomMute?: boolean;
364
+ /**
365
+ * Hide the "Block user" action from a 1-1 private chat's header menu. Block
366
+ * is only ever offered for private rooms with a resolvable peer; this flag
367
+ * lets a host hide it outright (e.g. it renders its own block control, or
368
+ * the backend predates the /v1/chats/blocked route).
369
+ */
370
+ disableUserBlock?: boolean;
338
371
  defaultRooms?: ConfigRoom[];
339
372
  /**
340
373
  * The "Files" tab shown in the sidebar alongside "Chats". Backed by
@@ -553,6 +586,8 @@ export interface IConfig {
553
586
  };
554
587
  newArch?: boolean;
555
588
  disableLastRead?: boolean;
589
+ /** Delivery and read receipt sending policy. See ReceiptsConfig. */
590
+ receipts?: ReceiptsConfig;
556
591
  qrUrl?: string;
557
592
  secondarySendButton?: {
558
593
  enabled: boolean;
@@ -63,6 +63,7 @@ export interface IRoom {
63
63
  lastViewedTimestamp?: number;
64
64
  unreadBaselineTimestamp?: number;
65
65
  lastDisplayedByUser?: Record<string, string>;
66
+ lastReceivedByUser?: Record<string, string>;
66
67
  unreadMessages?: number;
67
68
  /**
68
69
  * Unread count the server reported for this room in `GET /v1/chats/my`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ethora/chat-component",
3
- "version": "26.10.1",
3
+ "version": "26.10.4",
4
4
  "type": "module",
5
5
  "main": "dist/main.js",
6
6
  "types": "dist/main.d.ts",
@@ -33,9 +33,13 @@
33
33
  "scripts": {
34
34
  "dev": "vite",
35
35
  "build": "tsc && vite build",
36
- "sync:lib": "node ./scripts/sync-lib.mjs",
37
- "build:lib": "npm run sync:lib && tsc --p ./tsconfig-build.json && vite build",
38
- "prepare": "npm run build && npm run build:lib",
36
+ "build:lib": "npm run build",
37
+ "prepare": "node scripts/install-git-hooks.mjs && npm run build",
38
+ "ci:local": "npm run lint && npm test && npm run test:release && npm run build",
39
+ "release": "node scripts/release.mjs",
40
+ "release:ci": "node scripts/release-ci.mjs",
41
+ "publish:npm": "node scripts/publish-npm.mjs",
42
+ "test:release": "node --test scripts/lib/version.check.mjs",
39
43
  "lint": "eslint . --quiet --report-unused-disable-directives",
40
44
  "lint:full": "eslint . --report-unused-disable-directives --max-warnings 0",
41
45
  "preview": "vite preview",
@@ -111,9 +115,6 @@
111
115
  "csstype": "3.1.3"
112
116
  },
113
117
  "description": "Open-source React chat and AI-agent UI SDK for Ethora: drop-in messaging, rooms, and AI bots with a self-hosted or cloud backend.",
114
- "directories": {
115
- "lib": "lib"
116
- },
117
118
  "author": "Ethora",
118
119
  "license": "MIT",
119
120
  "homepage": "https://ethora.com",