@ethora/chat-component 26.7.4 → 26.7.6

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 (152) hide show
  1. package/README.md +72 -16
  2. package/dist/ChatProfileModal-hxsWI8cx.js +2139 -0
  3. package/dist/FilePreviewModal-CwWnHsB1.js +736 -0
  4. package/dist/ManageDataModal-CDPXcC93.js +159 -0
  5. package/dist/MarkdownBody-DBJGraWM.js +16874 -0
  6. package/dist/SideDrawer-Dr_lkaOW.js +226 -0
  7. package/dist/UserProfileModal-Q_jhXpsX.js +568 -0
  8. package/dist/UserSettingsModal-OcAJao-d.js +50 -0
  9. package/dist/{VideoCallOverlay-BUfKp87t.js → VideoCallOverlay-CC4sShdK.js} +9 -10
  10. package/dist/VisibilityModal-DkMhx_2k.js +132 -0
  11. package/dist/assets/icons.d.ts +30 -0
  12. package/dist/components/ContextMenu/ContextMenuComponents.d.ts +1 -3
  13. package/dist/components/EmojiPicker/LazyEmojiPicker.d.ts +3 -0
  14. package/dist/components/ExampleComponents/CustomMessage.d.ts +3 -3
  15. package/dist/components/Files/FilesList.d.ts +12 -0
  16. package/dist/components/Files/FilesPanel.d.ts +3 -0
  17. package/dist/components/Files/fileCategory.d.ts +13 -0
  18. package/dist/components/InputComponents/AttachmentPreview.d.ts +17 -0
  19. package/dist/components/InputComponents/MentionDropdown.d.ts +13 -0
  20. package/dist/components/InputComponents/MentionPickerModal.d.ts +19 -0
  21. package/dist/components/MainComponents/AttachmentList.d.ts +7 -0
  22. package/dist/components/MainComponents/ChatErrorBoundary.d.ts +53 -0
  23. package/dist/components/MainComponents/ChatErrorScreen.d.ts +24 -0
  24. package/dist/components/MainComponents/HeaderLogo.d.ts +18 -0
  25. package/dist/components/MainComponents/MediaMessage.d.ts +5 -0
  26. package/dist/components/MainComponents/MessageContainer.d.ts +2 -2
  27. package/dist/components/MainComponents/ReduxWrapper.d.ts +1 -2
  28. package/dist/components/MainComponents/SystemMessage.d.ts +1 -2
  29. package/dist/components/MenuRoom/MenuRoom.d.ts +1 -0
  30. package/dist/components/MessageBubble/CustomDivider.d.ts +3 -3
  31. package/dist/components/MessageBubble/Message.d.ts +2 -2
  32. package/dist/components/MessageBubble/MessageTranslate.d.ts +28 -10
  33. package/dist/components/MessageBubble/QuickReplies.d.ts +19 -0
  34. package/dist/components/Modals/ChatProfileModal/ChatProfileMemberRow.d.ts +18 -0
  35. package/dist/components/Modals/FilePreviewModal/FilePreviewModal.d.ts +17 -0
  36. package/dist/components/Modals/FilePreviewModal/ImageLightbox.d.ts +35 -0
  37. package/dist/components/Modals/FilePreviewModal/PdfView.d.ts +14 -3
  38. package/dist/components/Modals/NewChatModal/styledNewChatComponents.d.ts +12 -0
  39. package/dist/components/Modals/SettingsModals/Visibility/StyledComponents.d.ts +1 -1
  40. package/dist/components/Modals/SideDrawer/DrawerProfileParts.d.ts +13 -0
  41. package/dist/components/Modals/SideDrawer/SideDrawer.d.ts +44 -0
  42. package/dist/components/Modals/modalComponents.d.ts +6 -0
  43. package/dist/components/Modals/styledModalComponents.d.ts +14 -3
  44. package/dist/components/RoomComponents/ChatRoomItem.d.ts +0 -1
  45. package/dist/components/RoomComponents/OnlineUsersPopover.d.ts +0 -1
  46. package/dist/components/UsersList/StyledComponents.d.ts +5 -1
  47. package/dist/components/UsersList/UsersList.d.ts +23 -0
  48. package/dist/components/styled/Button.d.ts +11 -1
  49. package/dist/components/styled/ChatWrapperBox.d.ts +3 -1
  50. package/dist/components/styled/PdfMessage.d.ts +13 -0
  51. package/dist/components/styled/RoomListComponents/index.d.ts +22 -1
  52. package/dist/components/styled/SendInput.d.ts +19 -5
  53. package/dist/components/styled/StyledComponents.d.ts +1 -1
  54. package/dist/components/styled/StyledInputComponents/MediaComponents.d.ts +18 -0
  55. package/dist/components/styled/StyledInputComponents/StyledInputComponents.d.ts +21 -0
  56. package/dist/components/styled/TreadLabel.d.ts +2 -2
  57. package/dist/components/styled/UnsupportedType.d.ts +1 -1
  58. package/dist/examples/quiz/QuizDemo.d.ts +3 -0
  59. package/dist/examples/quiz/quizScript.d.ts +38 -0
  60. package/dist/examples/quiz/useDemoSession.d.ts +42 -0
  61. package/dist/examples/quiz/useQuizFlow.d.ts +47 -0
  62. package/dist/firebase-config.d.ts +2 -4
  63. package/dist/handleCopyClick-CpmPa6VI.js +32 -0
  64. package/dist/helpers/MarkdownBody.d.ts +7 -0
  65. package/dist/helpers/attachments.d.ts +33 -0
  66. package/dist/helpers/botMarkup.d.ts +30 -0
  67. package/dist/helpers/constants/MODAL_TYPES.d.ts +11 -7
  68. package/dist/helpers/ethoraLogger.d.ts +8 -0
  69. package/dist/helpers/fileKind.d.ts +17 -0
  70. package/dist/helpers/hiddenRooms.d.ts +1 -1
  71. package/dist/helpers/historyPreloadScheduler.d.ts +1 -0
  72. package/dist/helpers/lazyEmoji.d.ts +3 -0
  73. package/dist/helpers/localStorageUser.d.ts +1 -1
  74. package/dist/helpers/markdownSanitizeSchema.d.ts +3 -0
  75. package/dist/helpers/mentions.d.ts +63 -0
  76. package/dist/helpers/parseMessageBody.d.ts +12 -2
  77. package/dist/helpers/pdf/loadPdfjs.d.ts +64 -0
  78. package/dist/helpers/quickReplies.d.ts +31 -0
  79. package/dist/helpers/remarkLineBreaks.d.ts +42 -0
  80. package/dist/helpers/resolveMessageTranslation.d.ts +34 -0
  81. package/dist/helpers/roomImageGallery.d.ts +53 -0
  82. package/dist/helpers/secureFileUrl.d.ts +8 -0
  83. package/dist/helpers/sendFailureWatchdog.d.ts +62 -0
  84. package/dist/helpers/transformTranslatations.d.ts +1 -2
  85. package/dist/hooks/pushSubscriptionSweep.d.ts +41 -0
  86. package/dist/hooks/useChatWrapperInit.d.ts +1 -0
  87. package/dist/hooks/useFilesEndpointSupport.d.ts +9 -0
  88. package/dist/hooks/useInView.d.ts +17 -0
  89. package/dist/hooks/useMentionComposer.d.ts +45 -0
  90. package/dist/hooks/useMessageHeapState.d.ts +1 -1
  91. package/dist/hooks/useMessageTranslation.d.ts +4 -3
  92. package/dist/hooks/useModalDismiss.d.ts +27 -0
  93. package/dist/hooks/useMyFiles.d.ts +16 -0
  94. package/dist/hooks/usePdfThumbnail.d.ts +22 -0
  95. package/dist/hooks/useRoomMute.d.ts +19 -0
  96. package/dist/hooks/useRoomState.d.ts +1 -1
  97. package/dist/hooks/useSendMessage.d.ts +4 -3
  98. package/dist/hooks/useTypography.d.ts +1 -1
  99. package/dist/hooks/useUnreadMessagesCounter.d.ts +11 -2
  100. package/dist/index.esm-B-3fyDHY.js +28 -0
  101. package/dist/index.esm-CRM4VCRa.js +943 -0
  102. package/dist/index.esm-msbp26WN.js +3352 -0
  103. package/dist/index.esm2017-CL0mvq6y.js +1578 -0
  104. package/dist/main-B-8IhwaP.js +31837 -0
  105. package/dist/main.css +1 -1
  106. package/dist/main.d.ts +16 -0
  107. package/dist/main.js +19 -18
  108. package/dist/module-DGXkb0R_.js +2960 -0
  109. package/dist/native-CDi4YD_s.js +13 -0
  110. package/dist/networking/api-requests/files.api.d.ts +19 -0
  111. package/dist/networking/api-requests/rooms.api.d.ts +10 -2
  112. package/dist/networking/api-requests/translate.api.d.ts +15 -0
  113. package/dist/networking/api-requests/user.api.d.ts +2 -0
  114. package/dist/networking/sharedRequest.d.ts +4 -0
  115. package/dist/networking/xmpp/allRoomPresences.xmpp.d.ts +3 -1
  116. package/dist/networking/xmpp/createTimeoutPromise.xmpp.d.ts +14 -0
  117. package/dist/networking/xmpp/sendTextMessage.xmpp.d.ts +2 -1
  118. package/dist/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.d.ts +2 -1
  119. package/dist/networking/xmppClient.d.ts +12 -3
  120. package/dist/pdf-B5b7qHu6.js +17130 -0
  121. package/dist/pdf.worker.min-COYn-gId.js +24385 -0
  122. package/dist/roomStore/index.d.ts +20 -0
  123. package/dist/roomStore/persistEncryption.d.ts +51 -0
  124. package/dist/roomStore/roomsSlice.d.ts +49 -6
  125. package/dist/styles/classNames.d.ts +35 -0
  126. package/dist/styles/motion.d.ts +44 -0
  127. package/dist/styles/tokens.d.ts +27 -0
  128. package/dist/testIds.d.ts +1 -1
  129. package/dist/types/models/attachment.model.d.ts +33 -0
  130. package/dist/types/models/config.model.d.ts +179 -19
  131. package/dist/types/models/file.model.d.ts +20 -0
  132. package/dist/types/models/message.model.d.ts +16 -6
  133. package/dist/types/models/room.model.d.ts +12 -2
  134. package/dist/types/types.d.ts +3 -1
  135. package/dist/utils/resendMessage.d.ts +22 -1
  136. package/dist/utils/translateModePolicy.d.ts +1 -0
  137. package/dist/wavesurfer.esm-D_a19qBW.js +952 -0
  138. package/package.json +22 -2
  139. package/dist/VideoCallOverlay-BUfKp87t.js.map +0 -1
  140. package/dist/components/MainComponents/VirtualList.d.ts +0 -10
  141. package/dist/components/Modals/SettingsModals/BlockedUsers/BlockedUsersModal.d.ts +0 -6
  142. package/dist/components/Modals/SettingsModals/DocumentShares/DocumentSharesModal.d.ts +0 -6
  143. package/dist/components/Modals/SettingsModals/ManageDataModal/StyledComponents.d.ts +0 -0
  144. package/dist/components/Modals/SettingsModals/ProfileShares/ProfileShares.d.ts +0 -6
  145. package/dist/components/Modals/SettingsModals/Referrals/Referrals.d.ts +0 -6
  146. package/dist/hooks/useMessageQueue.d.ts +0 -4
  147. package/dist/main-DFqqDiy-.js +0 -55707
  148. package/dist/main-DFqqDiy-.js.map +0 -1
  149. package/dist/main.js.map +0 -1
  150. package/dist/networking/xmpp/getHistory.xmpp.d.ts +0 -3
  151. package/dist/networking/xmpp/listenerManager.d.ts +0 -17
  152. package/dist/utils/xmppMemoryMonitor.d.ts +0 -16
@@ -3,6 +3,26 @@ import { IMessage, IRoom } from '../types/types';
3
3
  export declare const sanitizeRoomsMap: (roomsMap: Record<string, any>) => Record<string, IRoom>;
4
4
  export declare const MAX_MESSAGES_PER_ROOM = 100;
5
5
  export declare const MAX_PERSISTED_ROOMS = 100;
6
+ /**
7
+ * Composer drafts are a NEW top-level key of the rooms slice, so they are
8
+ * persisted deliberately rather than by accident: they are the one piece of
9
+ * live composer state worth surviving a reload, and unlike `members` (see
10
+ * REFETCHED_ROOM_FIELDS below) nothing on the server can rebuild them.
11
+ *
12
+ * They are also kept OFF the room objects on purpose. The rooms map is what
13
+ * the char budget fights over, and its last-resort eviction can only drop
14
+ * `messages` - a draft riding on a room would compete with the message
15
+ * cache for that budget and could not be evicted independently. As its own
16
+ * key it is bounded on its own terms by the caps in roomsSlice:
17
+ * MAX_PERSISTED_DRAFTS (30, least recently typed dropped first, using the
18
+ * map's insertion order as recency) x MAX_DRAFT_LENGTH (2,000 chars) =
19
+ * 60,000 chars, i.e. 6% of PERSISTED_ROOMS_CHAR_BUDGET even at the worst
20
+ * case, and typically a few hundred bytes.
21
+ *
22
+ * Applied on the way out AND on the way back in (sanitizeRoomsSliceKey), so
23
+ * an older or hand-edited blob cannot rehydrate an unbounded map either.
24
+ */
25
+ export declare const compactDraftsForPersist: (drafts: unknown) => Record<string, string>;
6
26
  export declare const PERSISTED_ROOMS_CHAR_BUDGET = 1000000;
7
27
  /** ~1.4x: AES + base64 overhead the encrypt transform adds on top. */
8
28
  export declare const ENCRYPTION_INFLATION_FACTOR = 1.4;
@@ -0,0 +1,51 @@
1
+ import { User } from '../types/types';
2
+ /**
3
+ * WHICH CLAIMS, AND WHY NOT THE WHOLE TOKEN.
4
+ *
5
+ * The obvious input, `user.token`, is the wrong one: it rotates.
6
+ * `authRefresh.ts` runs refresh-token rotation with reuse detection, so
7
+ * the access token string changes several times an hour. Key the
8
+ * encryption on the whole token and every rotation orphans the blob
9
+ * that the previous token wrote: the user would lose their entire chat
10
+ * cache on a schedule, silently, and each reload would start from an
11
+ * empty transcript until MAM caught up.
12
+ *
13
+ * So we derive from the parts of the token that are stable for the life
14
+ * of the ACCOUNT rather than the life of the credential: the `userId`
15
+ * and `appId` claims out of the JWT payload (Ethora issues
16
+ * `{ data: { userId, appId }, iat, exp }`). `iat` / `exp` / the
17
+ * signature are deliberately excluded - they are exactly the parts that
18
+ * change on refresh.
19
+ *
20
+ * `appToken` joins them as a build-time constant so two different apps
21
+ * embedding the SDK on one origin never derive the same key. It adds no
22
+ * secrecy on its own (it is in the bundle), which is why it is a
23
+ * component of the key and not the key.
24
+ */
25
+ export interface PersistKeyIdentity {
26
+ userId: string;
27
+ appId: string;
28
+ }
29
+ /** The key used when there is no session: SSR, logged out, first paint. */
30
+ export declare const ANONYMOUS_PERSIST_SECRET_KEY: string;
31
+ /**
32
+ * Stable account identity for the key, read from the JWT payload first
33
+ * and from the stored user record second. The fallback matters for
34
+ * hosts that hand the SDK a user object without an Ethora access token
35
+ * (they run their own auth and supply `refreshTokens.refreshFunction`).
36
+ */
37
+ export declare const readStableIdentity: (user?: Partial<User> | null) => PersistKeyIdentity | null;
38
+ export declare const derivePersistSecretKey: (identity?: PersistKeyIdentity | null) => string;
39
+ export declare const getPersistSecretKey: () => string;
40
+ /** Test seam, and the hook logout could use if it ever needs to. */
41
+ export declare const resetPersistSecretKeyCache: () => void;
42
+ /**
43
+ * Drop-in replacement for the old fixed-key `encryptTransform`.
44
+ *
45
+ * It has to resolve the key lazily, per call: `encryptTransform`
46
+ * captures `secretKey` when it is constructed, and this module is
47
+ * imported at store-creation time, long before a `<Chat>` consumer has
48
+ * logged anybody in. Resolving on each in/out means the first write
49
+ * after login already uses that user's key.
50
+ */
51
+ export declare const sessionEncryptTransform: import('redux-persist').Transform<any, any, any, any>;
@@ -1,5 +1,21 @@
1
- import { AddRoomMessageAction, ApiRoom, EditAction, IMessage, IRoom, ReactionAction, RoomMember } from '../types/types';
1
+ import { AddRoomMessageAction, ApiRoom, EditAction, IMessage, IRoom, ReactionAction, RoomMember, Translation } from '../types/types';
2
2
  import { default as XmppClient } from '../networking/xmppClient';
3
+ /**
4
+ * Draft caps. Drafts ride along in the persisted rooms slice, so they have
5
+ * to be bounded before they are written, not after:
6
+ *
7
+ * 30 rooms x 2,000 chars = 60,000 chars worst case, ~84,000 after the
8
+ * encrypt transform's ~1.4x inflation (~168 KB of the origin's ~5 MB).
9
+ *
10
+ * That is small next to PERSISTED_ROOMS_CHAR_BUDGET (1,000,000 chars) and,
11
+ * because drafts are their own top-level slice key, it can never squeeze
12
+ * the message cache the way the member roster once did. 2,000 chars is far
13
+ * longer than any message anyone actually leaves half-typed; anything past
14
+ * it is truncated rather than dropped, so the user still gets most of it
15
+ * back.
16
+ */
17
+ export declare const MAX_DRAFT_LENGTH = 2000;
18
+ export declare const MAX_PERSISTED_DRAFTS = 30;
3
19
  interface RoomMessagesState {
4
20
  rooms: {
5
21
  [jid: string]: IRoom;
@@ -16,12 +32,13 @@ interface RoomMessagesState {
16
32
  subscribedRooms: string[];
17
33
  pushSubscriptionStatus: Record<string, 'pending' | 'subscribed' | 'error' | 'blocked'>;
18
34
  loadingText?: string;
35
+ drafts: Record<string, string>;
19
36
  }
20
37
  interface PreloadRoomUpdate {
21
38
  jid: string;
22
39
  messages?: IMessage[];
23
40
  unreadCapped?: boolean;
24
- historyPreloadState?: 'idle' | 'loading' | 'done' | 'error';
41
+ historyPreloadState?: 'idle' | 'loading' | 'partial' | 'done' | 'error';
25
42
  }
26
43
  export declare const addRoomViaApi: import('@reduxjs/toolkit').AsyncThunk<void, {
27
44
  room: IRoom;
@@ -48,11 +65,26 @@ export declare const addRoom: import('@reduxjs/toolkit').ActionCreatorWithOption
48
65
  }, "roomMessages/replaceRoomMessages">, addRoomMessage: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<AddRoomMessageAction, "roomMessages/addRoomMessage">, deleteRoomMessage: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
49
66
  roomJID: string;
50
67
  messageId: string;
51
- }, "roomMessages/deleteRoomMessage">, setEditAction: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<EditAction, "roomMessages/setEditAction">, editRoomMessage: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
68
+ }, "roomMessages/deleteRoomMessage">, removeRoomMessage: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
69
+ roomJID: string;
70
+ messageId: string;
71
+ }, "roomMessages/removeRoomMessage">, setEditAction: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<EditAction, "roomMessages/setEditAction">, setMessageSendFailed: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
72
+ roomJID: string;
73
+ messageId: string;
74
+ }, "roomMessages/setMessageSendFailed">, setMessageSendRetrying: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
75
+ roomJID: string;
76
+ messageId: string;
77
+ }, "roomMessages/setMessageSendRetrying">, editRoomMessage: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
52
78
  roomJID: string;
53
79
  messageId: string;
54
80
  text: string;
55
- }, "roomMessages/editRoomMessage">, setComposing: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
81
+ isEdited?: boolean;
82
+ }, "roomMessages/editRoomMessage">, setMessageTranslation: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
83
+ roomJID: string;
84
+ messageId: string;
85
+ locale: string;
86
+ entry: Translation;
87
+ }, "roomMessages/setMessageTranslation">, setComposing: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
56
88
  chatJID: string;
57
89
  composing: boolean;
58
90
  composingList?: string[];
@@ -87,7 +119,10 @@ export declare const addRoom: import('@reduxjs/toolkit').ActionCreatorWithOption
87
119
  }, "roomMessages/deleteRoom">, updateRoom: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
88
120
  jid: string;
89
121
  updates: Partial<IRoom>;
90
- }, "roomMessages/updateRoom">, updateUsersSet: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
122
+ }, "roomMessages/updateRoom">, updateRooms: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
123
+ jid: string;
124
+ updates: Partial<IRoom>;
125
+ }[], "roomMessages/updateRooms">, updateUsersSet: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
91
126
  rooms: ApiRoom[];
92
127
  }, "roomMessages/updateUsersSet">, setOpenReportModal: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
93
128
  isOpen: boolean;
@@ -98,6 +133,14 @@ export declare const addRoom: import('@reduxjs/toolkit').ActionCreatorWithOption
98
133
  status: "pending" | "subscribed" | "error" | "blocked";
99
134
  }, "roomMessages/setPushSubscriptionStatus">, clearPushSubscriptions: import('@reduxjs/toolkit').ActionCreatorWithoutPayload<"roomMessages/clearPushSubscriptions">, applyRoomsPreloadBatch: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
100
135
  rooms: PreloadRoomUpdate[];
101
- }, "roomMessages/applyRoomsPreloadBatch">;
136
+ }, "roomMessages/applyRoomsPreloadBatch">, setRoomDraft: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
137
+ jid: string;
138
+ text: string;
139
+ }, "roomMessages/setRoomDraft">, clearRoomDraft: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
140
+ jid: string;
141
+ }, "roomMessages/clearRoomDraft">, setRoomMuted: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
142
+ jid: string;
143
+ muted: boolean | undefined;
144
+ }, "roomMessages/setRoomMuted">;
102
145
  declare const _default: import('redux').Reducer<RoomMessagesState>;
103
146
  export default _default;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Global CSS class names owned by this SDK.
3
+ *
4
+ * This package is embedded into host applications, so every class name it
5
+ * writes into the DOM shares a namespace with the host's own stylesheet.
6
+ * Anything declared in `src/index.css` must therefore be prefixed with
7
+ * `ethora-` (so it cannot collide with a host class of the same name) and,
8
+ * where possible, nested under {@link CHAT_ROOT_CLASS} (so it cannot leak
9
+ * styling onto host markup).
10
+ *
11
+ * Keep these constants as the single source of truth: `index.css` and every
12
+ * component that adds/removes one of these classes must agree, and a plain
13
+ * string literal in a `classList.add(...)` call is easy to drift.
14
+ */
15
+ /**
16
+ * Marks a subtree as "owned by the chat".
17
+ *
18
+ * Applied to:
19
+ * - the invisible (`display: contents`) wrapper `<Chat>` renders around its
20
+ * whole tree, so every chat node is a descendant, and
21
+ * - UI the SDK renders OUTSIDE that tree (the in-app notification container,
22
+ * the portalled language modal), which would otherwise lose the chat's
23
+ * scrollbar/typography defaults.
24
+ *
25
+ * Everything in `index.css` that is not itself `ethora-`-prefixed (scrollbars,
26
+ * base font) is scoped to this class, which is what keeps the host page's own
27
+ * scrollbars and body typography untouched.
28
+ */
29
+ export declare const CHAT_ROOT_CLASS = "ethora-chat-root";
30
+ /** Transient highlight flashed on a message when a notification is opened. */
31
+ export declare const MESSAGE_HIGHLIGHT_CLASS = "ethora-message-highlight";
32
+ /** Fixed-position container holding the in-app notification toasts. */
33
+ export declare const NOTIFICATION_CONTAINER_CLASS = "ethora-message-notification-container";
34
+ /** Forces the colour-emoji font on nodes that render a bare emoji glyph. */
35
+ export declare const APPLE_EMOJI_CLASS = "ethora-apple-emoji";
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Shared motion primitives for the chat UI.
3
+ *
4
+ * Keep durations/easing in sync with the tokens published by
5
+ * `styles/tokens.ts` (`--ethora-motion-fast` = 150ms, `--ethora-motion-base`
6
+ * = 220ms, `--ethora-motion-ease`). The literal values are duplicated here
7
+ * (rather than reading the CSS variables inside `@keyframes`, which most
8
+ * browsers don't support) but must stay numerically identical.
9
+ */
10
+ export declare const MOTION_FAST = "150ms";
11
+ export declare const MOTION_BASE = "220ms";
12
+ export declare const MOTION_EASE = "cubic-bezier(.2,.8,.2,1)";
13
+ export declare const fadeIn: import('styled-components/dist/models/Keyframes').default;
14
+ export declare const fadeInUp: import('styled-components/dist/models/Keyframes').default;
15
+ export declare const scaleIn: import('styled-components/dist/models/Keyframes').default;
16
+ export declare const shimmer: import('styled-components/dist/models/Keyframes').default;
17
+ /**
18
+ * Right-hand side drawer entrance. Travels further than `fadeInUp` (a panel
19
+ * sliding in from the screen edge reads as motion only if it actually
20
+ * covers some distance) but keeps the same duration/easing as everything
21
+ * else, so a drawer does not feel like a different app.
22
+ */
23
+ export declare const slideInRight: import('styled-components/dist/models/Keyframes').default;
24
+ /**
25
+ * Disables all animations/transitions when the user prefers reduced motion.
26
+ * Spread this LAST in a styled-components template so it wins the cascade,
27
+ * e.g.:
28
+ *
29
+ * const Row = styled.div`
30
+ * animation: ${fadeInUp} ${MOTION_BASE} ${MOTION_EASE};
31
+ * ${reducedMotion}
32
+ * `;
33
+ */
34
+ export declare const reducedMotion: import('styled-components').RuleSet<object>;
35
+ /** Convenience: fade+rise-in animation shorthand, respecting reduced motion. */
36
+ export declare const fadeInUpAnimation: import('styled-components').RuleSet<object>;
37
+ /** Convenience: cross-fade animation shorthand, respecting reduced motion. */
38
+ export declare const fadeInAnimation: import('styled-components').RuleSet<object>;
39
+ /** Convenience: scale-in animation shorthand, respecting reduced motion. */
40
+ export declare const scaleInAnimation: import('styled-components').RuleSet<object>;
41
+ /** Convenience: side-drawer slide-in shorthand, respecting reduced motion. */
42
+ export declare const slideInRightAnimation: import('styled-components').RuleSet<object>;
43
+ /** Shimmering skeleton background, respecting reduced motion (stays static). */
44
+ export declare const shimmerBackground: import('styled-components').RuleSet<object>;
@@ -0,0 +1,27 @@
1
+ import { CSSProperties } from 'react';
2
+ import { IConfig, TypographyConfig } from '../types/types';
3
+ /** Mix `hex` toward white by `amount` (0-1). Used for soft tints. */
4
+ export declare const tintColor: (hex: string, amount: number) => string;
5
+ /** Mix `hex` toward black by `amount` (0-1). Used for hover/pressed shades. */
6
+ export declare const shadeColor: (hex: string, amount: number) => string;
7
+ export type ThemeTokenMap = Record<string, string>;
8
+ /**
9
+ * Build the full `--ethora-*` CSS variable map from the host's config.
10
+ * Pure function - safe to call outside the browser (SSR, tests).
11
+ */
12
+ export declare function buildThemeTokens(colors?: IConfig['colors'], typography?: TypographyConfig): ThemeTokenMap;
13
+ /**
14
+ * The class the chat's root element carries; useful for scoping global CSS.
15
+ * Re-exported from `styles/classNames`, which owns every class name this
16
+ * SDK writes into the DOM, so the literal exists in exactly one place.
17
+ */
18
+ export { CHAT_ROOT_CLASS } from './classNames';
19
+ /**
20
+ * Memoized React.CSSProperties built from the token map - spread this onto
21
+ * the chat root element's inline `style` so the variables are scoped to the
22
+ * chat subtree wherever the root can carry them.
23
+ */
24
+ export declare function useThemeTokenStyle(colors?: IConfig['colors'], typography?: TypographyConfig): CSSProperties;
25
+ export declare function ThemeTokens({ config, }: {
26
+ config?: Pick<IConfig, 'colors' | 'typography'>;
27
+ }): null;
package/dist/testIds.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * Kotlin objects (in `chat-ui/src/main/.../components/*Kt`) and iOS's
7
7
  * `*AccessibilityID` Swift enums (in `XMPPChatUI/AccessibilityIdentifiers.swift`),
8
8
  * so a single Maestro YAML flow exercises the same intent on either
9
- * mobile platform — and Playwright tests in host apps using this
9
+ * mobile platform - and Playwright tests in host apps using this
10
10
  * component (e.g. `ethora-app-reactjs`) can resolve the same nodes
11
11
  * via `data-testid`.
12
12
  *
@@ -0,0 +1,33 @@
1
+ /**
2
+ * One uploaded file carried by a media message.
3
+ *
4
+ * Historically a media message described exactly one file through flat
5
+ * `IMessage` fields (`location`, `mimetype`, `originalName`, ...). Those
6
+ * fields are still written (they are what every already-shipped client
7
+ * reads), and they now double as attachment #0 of `IMessage.attachments`.
8
+ * Anything past #0 only exists in the `attachments` payload, so old clients
9
+ * degrade to "shows the first file" instead of breaking.
10
+ */
11
+ export interface IAttachment {
12
+ /** `_id` returned by POST /files/ - stable per upload, used as a render key. */
13
+ attachmentId?: string;
14
+ /** Canonical file URL. */
15
+ location: string;
16
+ /** Server-side generated preview (images always, other types sometimes). */
17
+ locationPreview?: string;
18
+ mimetype?: string;
19
+ /** Name the user picked. */
20
+ originalName?: string;
21
+ /** Name on the storage side. */
22
+ fileName?: string;
23
+ /** Bytes, as a string - that is how it travels on the wire. */
24
+ size?: string;
25
+ duration?: string;
26
+ ownerKey?: string;
27
+ userId?: string;
28
+ createdAt?: string;
29
+ updatedAt?: string;
30
+ expiresAt?: string;
31
+ isVisible?: boolean;
32
+ isPrivate?: boolean;
33
+ }
@@ -1,8 +1,9 @@
1
1
  import { User } from './user.model';
2
2
  import { xmppSettingsInterface } from './xmpp.model';
3
- import { PartialRoomWithMandatoryKeys, ConfigRoom } from './room.model';
4
- import { MessageBubble, MessageProps, IMessage } from './message.model';
3
+ import { ConfigRoom } from './room.model';
4
+ import { MessageProps, IMessage } from './message.model';
5
5
  import { Iso639_1Codes } from './language.model';
6
+ import { QuickReply } from '../../helpers/quickReplies';
6
7
  import { default as React } from 'react';
7
8
  import { MessageNotificationToastProps } from '../../components/MessageNotification/MessageNotificationToast';
8
9
  /**
@@ -55,7 +56,7 @@ export interface FBConfig {
55
56
  }
56
57
  /**
57
58
  * A single self-hosted font file to register via an injected `@font-face`
58
- * rule. Use this for fonts that are not on Google Fonts — e.g. the Ukrainian
59
+ * rule. Use this for fonts that are not on Google Fonts - e.g. the Ukrainian
59
60
  * government "e-Ukraine" family distributed from thedigital.gov.ua. Host the
60
61
  * `.woff2`/`.ttf` somewhere reachable and point `src` at it.
61
62
  */
@@ -113,10 +114,54 @@ export interface TypographyConfig {
113
114
  bold?: number;
114
115
  };
115
116
  }
117
+ /** Which part of the SDK an uncaught render error came from. */
118
+ export type ChatErrorScope = 'chat' | 'call-overlay';
119
+ /**
120
+ * What the SDK reports when its error boundary catches an uncaught render
121
+ * error: the error itself, React's component stack (when the runtime gives
122
+ * one) and which subtree crashed.
123
+ */
124
+ export interface ChatErrorInfo {
125
+ error: Error;
126
+ componentStack?: string;
127
+ scope: ChatErrorScope;
128
+ }
129
+ /**
130
+ * Render-prop form of `fallbackScreens.error`. `reset()` clears the boundary
131
+ * and re-mounts the chat subtree, so a host can build its own "try again".
132
+ */
133
+ export type ChatErrorFallbackRenderer = (context: ChatErrorInfo & {
134
+ reset: () => void;
135
+ }) => React.ReactNode;
116
136
  export interface IConfig {
117
137
  appId?: string;
118
138
  disableHeader?: boolean;
119
139
  disableMedia?: boolean;
140
+ /** Composer attachment limits. Applies to the built-in SendInput. */
141
+ attachments?: {
142
+ /**
143
+ * How many files one message may carry. Defaults to 5 and is hard-capped
144
+ * at 10 by the stanza payload (MAX_ATTACHMENTS_PER_MESSAGE).
145
+ */
146
+ maxFiles?: number;
147
+ /** Per-file ceiling in MB. Omit to skip the client-side size check. */
148
+ maxFileSizeMb?: number;
149
+ /** `accept` attribute handed to the file picker. Omit to allow anything. */
150
+ accept?: string;
151
+ };
152
+ /** First-page PDF thumbnails inside message bubbles. */
153
+ pdfPreview?: {
154
+ /** Set false to always show the static document card. Default true. */
155
+ enabled?: boolean;
156
+ /**
157
+ * URL of a pdf.js worker script. Omit and pdf.js runs on the main
158
+ * thread, which needs no bundler-specific asset wiring - fine for
159
+ * thumbnails, worth overriding if your users open large documents.
160
+ */
161
+ workerSrc?: string;
162
+ /** Documents above this are not auto-rendered. Default 25. */
163
+ maxFileSizeMb?: number;
164
+ };
120
165
  colors?: {
121
166
  primary: string;
122
167
  secondary: string;
@@ -156,6 +201,25 @@ export interface IConfig {
156
201
  noConnection?: React.ReactNode;
157
202
  /** Shown when the user has no chat room to display. */
158
203
  noRoom?: React.ReactNode;
204
+ /**
205
+ * Shown when an uncaught render error inside the chat is caught by the
206
+ * SDK's error boundary (instead of white-screening the host app).
207
+ *
208
+ * Same shape as the screens above (string = centered text, React node =
209
+ * rendered as-is) plus a render-function form, which is what you want if
210
+ * the replacement screen needs its own retry control or wants to show
211
+ * something about the error:
212
+ *
213
+ * fallbackScreens: {
214
+ * error: ({ error, reset }) => (
215
+ * <MyErrorPanel message={error.message} onRetry={reset} />
216
+ * ),
217
+ * }
218
+ *
219
+ * Omit it to keep the built-in calm, themed, translated screen with its
220
+ * own "try again" button.
221
+ */
222
+ error?: React.ReactNode | ChatErrorFallbackRenderer;
159
223
  };
160
224
  /**
161
225
  * Hide specific rooms from the room list and unread counters without
@@ -192,13 +256,35 @@ export interface IConfig {
192
256
  defaultLogin?: boolean;
193
257
  disableInteractions?: boolean;
194
258
  chatHeaderBurgerMenu?: boolean;
195
- forceSetRoom?: boolean;
196
259
  roomListStyles?: React.CSSProperties;
197
260
  chatRoomStyles?: React.CSSProperties;
198
261
  noMessagesPlaceholder?: React.ComponentType;
199
262
  setRoomJidInPath?: boolean;
200
263
  disableRoomMenu?: boolean;
264
+ /**
265
+ * Hide the "Mute/Unmute notifications" toggle from the chat header menu
266
+ * and the room profile drawer. The toggle is only ever offered when the
267
+ * backend has reported a `muted` boolean for the room in the first place
268
+ * (see IRoom.muted) - this flag is for a host that wants to hide it
269
+ * outright even then, e.g. because it renders its own mute control.
270
+ */
271
+ disableRoomMute?: boolean;
201
272
  defaultRooms?: ConfigRoom[];
273
+ /**
274
+ * The "Files" tab shown in the sidebar alongside "Chats". Backed by
275
+ * `GET /v2/files`, which not every backend has yet, so this defaults to
276
+ * an auto-detecting mode rather than always-on:
277
+ * - `enabled: true` always shows the tab - use this once you know your
278
+ * backend has /v2/files.
279
+ * - `enabled: false` always hides it.
280
+ * - omitted (default): the tab shows optimistically and only hides
281
+ * itself if the first files list request (when the user opens the tab)
282
+ * comes back 404/501, meaning this backend doesn't have the endpoint.
283
+ * No extra probe request is made just to decide this up front.
284
+ */
285
+ filesTab?: {
286
+ enabled?: boolean;
287
+ };
202
288
  refreshTokens?: {
203
289
  enabled: boolean;
204
290
  /**
@@ -226,15 +312,27 @@ export interface IConfig {
226
312
  color?: string;
227
313
  image?: string | File;
228
314
  };
229
- bubleMessage?: MessageBubble;
315
+ /**
316
+ * Host logo rendered at the left of the chat header, before the back
317
+ * button / burger menu. A plain string is treated as an image URL and
318
+ * rendered as an `<img>` (32px tall, aspect preserved); any other React
319
+ * element is rendered as-is. Same string-or-node duality as
320
+ * `fallbackScreens`. Omit to render no logo (the default).
321
+ */
230
322
  headerLogo?: string | React.ReactElement;
323
+ /**
324
+ * Takes over the room-list burger menu. When set, the built-in
325
+ * Profile/Settings/Logout dropdown is not rendered at all: the same burger
326
+ * button just calls this handler, so the host can open its own account
327
+ * menu or drawer.
328
+ */
231
329
  headerMenu?: () => void;
330
+ /**
331
+ * Same idea for the chat header's room menu (the "more" button carrying
332
+ * Report and Leave). When set, that dropdown is not rendered and the
333
+ * button calls this handler instead.
334
+ */
232
335
  headerChatMenu?: () => void;
233
- customRooms?: {
234
- rooms: PartialRoomWithMandatoryKeys[];
235
- disableGetRooms?: boolean;
236
- singleRoom: boolean;
237
- };
238
336
  translates?: {
239
337
  enabled: boolean;
240
338
  translations?: Iso639_1Codes;
@@ -243,11 +341,17 @@ export interface IConfig {
243
341
  * "Translate" link the reader clicks (LinkedIn-style), then renders the
244
342
  * result inline with a "Show original" toggle. Default 'auto'.
245
343
  *
246
- * Neither mode calls any translation service - both only ever display
344
+ * 'auto' is purely a display choice: it only ever reads
247
345
  * `message.translations`, whatever arrived attached to the stanza (see
248
- * `readerLocale` below for how a message gets translated in the first
249
- * place). This is purely a display choice: show it immediately, or let
250
- * the reader ask for it.
346
+ * `readerLocale` below), and never calls a translation service itself.
347
+ * 'manual' shows exactly the same thing on click, via the same lookup -
348
+ * no host function and no `endpoint` means no network request, just the
349
+ * translation already attached (if any). 'manual' does one thing more:
350
+ * on a click, if nothing is attached and the host hasn't supplied
351
+ * `onTranslate`, it fetches a translation from `endpoint` ONLY when the
352
+ * host has set that explicitly (see below) and caches the result onto
353
+ * `message.translations` so a second click - and 'auto' mode, if the
354
+ * reader switches to it - see it for free.
251
355
  */
252
356
  mode?: 'auto' | 'manual';
253
357
  /**
@@ -294,8 +398,9 @@ export interface IConfig {
294
398
  /**
295
399
  * Host-provided translation function. When set, the manual Translate
296
400
  * action calls this - wire it to your own service - instead of reading
297
- * `message.translations`. Optional; omit to use only what already
298
- * arrived over XMPP.
401
+ * `message.translations` or falling back to the built-in translate
402
+ * service. Optional; omit to use only what already arrived over XMPP
403
+ * (and, in manual mode, the fetch described on `endpoint` below).
299
404
  */
300
405
  onTranslate?: (text: string, ctx: {
301
406
  sourceLocale?: string;
@@ -305,9 +410,30 @@ export interface IConfig {
305
410
  /**
306
411
  * Host predicate deciding whether to show the Translate action for a given
307
412
  * message. When omitted, the component compares base languages (message
308
- * source vs reader, region ignored) and shows the action when they differ.
413
+ * source vs reader, region ignored) and shows the action when they differ,
414
+ * AND only when something could actually answer the click (a translation
415
+ * is already attached, `onTranslate` is set, or `endpoint` is set below) -
416
+ * otherwise no Translate link is shown at all, rather than one that could
417
+ * only ever end in "Could not translate".
309
418
  */
310
419
  showTranslateForMessage?: (message: IMessage) => boolean;
420
+ /**
421
+ * Explicit opt-in override for the built-in translate service's URL (e.g.
422
+ * "https://translate.api.example.com/translate"), used by manual mode's
423
+ * on-demand fetch (see `mode` above) when nothing is attached to the
424
+ * message and the host hasn't supplied `onTranslate`.
425
+ *
426
+ * This is strictly opt-in: the built-in HTTP call is never made unless
427
+ * this is set explicitly - there is no default derived from
428
+ * `config.baseUrl`. Leave it unset to rely only on `onTranslate` and
429
+ * whatever translation already arrived over XMPP. The call is made
430
+ * directly from the browser, so a self-hosted endpoint must send
431
+ * `Access-Control-Allow-Origin` for this origin or the fetch is blocked
432
+ * with no error shown beyond the "Could not translate" retry link; if you
433
+ * can't set that on the endpoint, use `onTranslate` and proxy the call
434
+ * through your own backend instead.
435
+ */
436
+ endpoint?: string;
311
437
  };
312
438
  /**
313
439
  * Static UI i18n (interface captions like "Search...", "Type message").
@@ -321,10 +447,20 @@ export interface IConfig {
321
447
  locale?: string;
322
448
  strings?: Record<string, string>;
323
449
  };
450
+ /**
451
+ * Hide every control that mutates a room's configuration: the room avatar
452
+ * upload/remove overlay, the "Delete chat" entry in the chat-details menu,
453
+ * the add-members action and the per-member moderator menu (appoint as
454
+ * admin / remove member). Read-only room details stay visible.
455
+ */
324
456
  disableRoomConfig?: boolean;
325
457
  disableProfilesInteractions?: boolean;
458
+ /**
459
+ * Hide the member-count subtitle in the chat header ("N users", plus the
460
+ * online-users popover that hangs off it). The 1:1 online/offline line is
461
+ * a presence state, not a count, so it is unaffected.
462
+ */
326
463
  disableUserCount?: boolean;
327
- clearStoreBeforeInit?: boolean;
328
464
  disableSentLogic?: boolean;
329
465
  initBeforeLoad?: boolean;
330
466
  initBeforeLoadAuth?: {
@@ -350,7 +486,6 @@ export interface IConfig {
350
486
  enabled: boolean;
351
487
  element: any;
352
488
  };
353
- botMessageAutoScroll?: boolean;
354
489
  messageTextFilter?: {
355
490
  enabled: boolean;
356
491
  filterFunction: (text: string) => string;
@@ -375,6 +510,31 @@ export interface IConfig {
375
510
  roomJID: string;
376
511
  user: any;
377
512
  }) => void;
513
+ /**
514
+ * Called after the user taps a button a bot attached to its message.
515
+ * The answer has ALREADY been sent into the room as an ordinary message
516
+ * by the time this fires - this is a notification, not an interception
517
+ * point, and a throw inside it is caught and logged.
518
+ *
519
+ * It exists so a host can drive a scripted flow (quiz, intake form,
520
+ * onboarding) on top of the same buttons a bot would send.
521
+ */
522
+ onQuickReply?: (event: {
523
+ /** Id of the bot message that offered the buttons. */
524
+ messageId: string;
525
+ roomJID: string;
526
+ /** The button that was tapped. */
527
+ reply: QuickReply;
528
+ /** `reply.questionId`, or the button's index when the bot sent none. */
529
+ questionId: string;
530
+ }) => void | Promise<void>;
531
+ /**
532
+ * Called when the SDK's error boundary catches an uncaught render error,
533
+ * i.e. the crash that would otherwise have taken the host app down with
534
+ * it. Wire it to your own crash reporter (Sentry, Datadog, ...). A throw
535
+ * inside this handler is caught and logged, never rethrown.
536
+ */
537
+ onError?: (event: ChatErrorInfo) => void;
378
538
  };
379
539
  disableTypingIndicator?: boolean;
380
540
  blockMessageSendingWhenProcessing?: boolean | {
@@ -0,0 +1,20 @@
1
+ export type ApiFile = {
2
+ _id: string;
3
+ userId: string;
4
+ ownerKey?: string;
5
+ roomName?: string;
6
+ location: string;
7
+ locationPreview?: string;
8
+ originalname: string;
9
+ filename?: string;
10
+ mimetype?: string;
11
+ size?: number;
12
+ expiresAt?: number;
13
+ isPrivate?: boolean;
14
+ duration?: string;
15
+ encrypted?: boolean;
16
+ key?: string;
17
+ iv?: string;
18
+ createdAt?: string;
19
+ updatedAt?: string;
20
+ };