@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
@@ -0,0 +1,33 @@
1
+ import { IAttachment, IMessage } from '../types/types';
2
+ /**
3
+ * Hard ceiling on how many attachments we will serialise into a stanza.
4
+ * The composer caps the picker far below this; this is the parser-side
5
+ * guard so a malformed or hostile stanza cannot make us render an
6
+ * unbounded grid.
7
+ */
8
+ export declare const MAX_ATTACHMENTS_PER_MESSAGE = 10;
9
+ /**
10
+ * Turns one raw wire/API object into an `IAttachment`, or null when there is
11
+ * nothing renderable in it. `location` is allowed to be empty: that is what
12
+ * an optimistic (still-uploading) attachment looks like.
13
+ */
14
+ export declare const normalizeAttachment: (raw: unknown) => IAttachment | null;
15
+ /** Accepts the parsed array, the raw JSON string, or junk. Never throws. */
16
+ export declare const parseAttachments: (raw: unknown) => IAttachment[];
17
+ /**
18
+ * The wire form: a JSON string on the `<data attachments="...">` attribute.
19
+ * Only the fields a receiver renders are sent - the stanza is already wide
20
+ * and every byte here is duplicated per attachment.
21
+ */
22
+ export declare const serializeAttachments: (attachments: IAttachment[]) => string;
23
+ /**
24
+ * The only supported way to read a media message's files.
25
+ *
26
+ * Order of truth: the multi-attach payload when present, otherwise the flat
27
+ * legacy fields synthesised into a single attachment. Every message that
28
+ * predates this feature - and every message from a client that does not
29
+ * speak it - takes the second path.
30
+ */
31
+ export declare const getMessageAttachments: (message?: Pick<IMessage, "attachments" | "location" | "locationPreview" | "mimetype" | "originalName" | "fileName" | "size"> | null) => IAttachment[];
32
+ /** Name to show for an attachment, with the URL tail as the last resort. */
33
+ export declare const getAttachmentName: (attachment: IAttachment) => string;
@@ -0,0 +1,30 @@
1
+ import { QuickReply } from './quickReplies';
2
+ /**
3
+ * Buttons an AI agent writes straight into its reply text.
4
+ *
5
+ * Bots built on the SDK attach buttons as the `quickReplies` attribute of the
6
+ * stanza's <data> element. An LLM agent cannot do that: all it controls is the
7
+ * text of its answer. So its system prompt teaches it an in-band form of the
8
+ * same thing, part of the Ethora chat protocol:
9
+ *
10
+ * <xml>
11
+ * <bot-data type="buttons">[Accept],[Reject]</bot-data>
12
+ * <body>Choose one please</body>
13
+ * </xml>
14
+ *
15
+ * This module turns that text into the same QuickReply buttons the attribute
16
+ * produces, plus the text that should actually be shown. It is written for
17
+ * model output, so it is deliberately forgiving about the envelope (the
18
+ * <xml>/<body> wrappers are optional, code fences are unwrapped, an
19
+ * HTML-escaped copy is accepted) and deliberately strict about what it
20
+ * hides: only a `type="buttons"` block is consumed, and nothing is stripped
21
+ * from a message that does not contain one.
22
+ */
23
+ export interface BotMarkup {
24
+ /** Text to display, with the machine-readable markup removed. */
25
+ text: string;
26
+ buttons: QuickReply[];
27
+ }
28
+ export declare const parseBotMarkup: (body: unknown) => BotMarkup;
29
+ /** The displayable text of a body, for previews and copy. */
30
+ export declare const stripBotMarkup: (body: unknown) => string;
@@ -4,12 +4,16 @@ export declare const MODAL_TYPES: {
4
4
  CHAT_PROFILE: string;
5
5
  MANAGE_DATA: string;
6
6
  VISIBILITY: string;
7
- PROFILE_SHARES: string;
8
- DOCUMENT_SHARES: string;
9
- BLOCKED_USERS: string;
10
- REFERRALS: string;
11
7
  FILE_PREVIEW: string;
12
8
  };
13
- export declare const MODAL_COMPONENTS: Record<string, React.FC<{
14
- handleCloseModal: () => void;
15
- }>>;
9
+ /**
10
+ * Panels reached from inside Settings. Their back button (and Escape) steps
11
+ * back to the Settings list rather than closing the drawer outright.
12
+ */
13
+ export declare const SETTINGS_SUB_MODAL_TYPES: string[];
14
+ /**
15
+ * Panels that open as a right-hand side drawer instead of a centred card:
16
+ * the profile/settings family. Everything else (file preview) keeps the
17
+ * centred-dialog presentation.
18
+ */
19
+ export declare const DRAWER_MODAL_TYPES: string[];
@@ -4,4 +4,12 @@ export declare const ethoraLogger: {
4
4
  debug: (...args: unknown[]) => void;
5
5
  error: (...args: unknown[]) => void;
6
6
  always: (...args: unknown[]) => void;
7
+ /**
8
+ * For failures that must never be silent, whatever the verbose-console
9
+ * setting is: today, an uncaught render error caught by the SDK's error
10
+ * boundary. `error` above stays gated so routine debug noise remains
11
+ * opt-in; this one does not, because a swallowed crash leaves the host
12
+ * with a replaced chat pane and no explanation anywhere.
13
+ */
14
+ criticalError: (...args: unknown[]) => void;
7
15
  };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * One place that answers "what do we render for this file?".
3
+ *
4
+ * Before this, every surface (bubble, room list, notification toast, the
5
+ * composer) re-derived the answer from `mimetype` alone with a slightly
6
+ * different set of `startsWith` checks, and none of them looked at the file
7
+ * name. That is why a PDF uploaded as `application/octet-stream` (which our
8
+ * storage does for anything it cannot sniff) ended up in the audio branch.
9
+ */
10
+ export type FileKind = 'image' | 'video' | 'audio' | 'pdf' | 'file';
11
+ /** Lowercase extension without the dot; '' when the name carries none. */
12
+ export declare const getFileExtension: (name?: string) => string;
13
+ export declare const isPdfFile: (mimetype?: string, name?: string) => boolean;
14
+ export declare const getFileKind: (mimetype?: string, name?: string) => FileKind;
15
+ export declare const formatFileSize: (sizeInBytes?: string | number) => string;
16
+ /** Middle-truncates the base name, always keeping the extension visible. */
17
+ export declare const formatFileName: (name: string, maxLength: number) => string;
@@ -2,7 +2,7 @@ import { IConfig, IRoom } from '../types/types';
2
2
  type RoomLike = Pick<IRoom, 'jid' | 'title' | 'name'>;
3
3
  /**
4
4
  * True when the room is listed in `config.hiddenRooms` (by JID or by
5
- * case-insensitive title). Hidden rooms stay joined — they are only
5
+ * case-insensitive title). Hidden rooms stay joined - they are only
6
6
  * suppressed from the room list and the unread counters.
7
7
  */
8
8
  export declare const isRoomHidden: (room: RoomLike | null | undefined, config?: Pick<IConfig, "hiddenRooms"> | null) => boolean;
@@ -9,6 +9,7 @@ interface HistoryPreloadSchedulerOptions {
9
9
  selectedRoomJid?: string | null;
10
10
  defaultRoomJids?: string[];
11
11
  forceReload?: boolean;
12
+ completionState?: 'done' | 'partial';
12
13
  }
13
14
  export declare const runHistoryPreloadScheduler: (options: HistoryPreloadSchedulerOptions) => Promise<void>;
14
15
  export default runHistoryPreloadScheduler;
@@ -0,0 +1,3 @@
1
+ export declare const loadEmojiData: () => Promise<any>;
2
+ export declare const getEmojiNativeById: (id: string) => string;
3
+ export declare const useEmojiData: () => boolean;
@@ -1 +1 @@
1
- export declare const getLocalStorageUser: () => import('../types/types').User;
1
+ export declare const getLocalStorageUser: () => import('../main').User;
@@ -0,0 +1,3 @@
1
+ import { Options as SanitizeSchema } from 'rehype-sanitize';
2
+ export declare const markdownSanitizeSchema: SanitizeSchema;
3
+ export default markdownSanitizeSchema;
@@ -0,0 +1,63 @@
1
+ import { IMentionSpan, RoomMember } from '../types/types';
2
+ export interface MentionCandidate {
3
+ jid: string;
4
+ name: string;
5
+ }
6
+ export declare const memberJid: (member: RoomMember) => string;
7
+ export declare const memberFullName: (member: RoomMember) => string;
8
+ /**
9
+ * Filters + ranks a room's members against a typed `@query` for the mention
10
+ * autocomplete dropdown. Prefix matches outrank mid-string substring
11
+ * matches; ties break alphabetically. The current user is always excluded
12
+ * (no self-mention suggestion).
13
+ */
14
+ export declare const rankMentionCandidates: (query: string, members: RoomMember[], selfId?: string) => MentionCandidate[];
15
+ /**
16
+ * Detects an in-progress "@query" the caret is currently inside, so the
17
+ * autocomplete dropdown knows whether to be open and what to filter by.
18
+ * Only triggers when the `@` starts a "word" (start of input, or preceded
19
+ * by whitespace) so it doesn't fire inside an email-looking string or an
20
+ * existing word.
21
+ */
22
+ export declare const findActiveMentionQuery: (text: string, caret: number) => {
23
+ start: number;
24
+ query: string;
25
+ } | null;
26
+ /**
27
+ * Re-anchors mention spans after a text edit. `editStart`/`editEnd` are the
28
+ * [start, end) range of the OLD text that was replaced, `insertedLength` is
29
+ * how long the replacement text is. Spans entirely before the edit are left
30
+ * alone; spans entirely after are shifted by the net length delta; a span
31
+ * that overlaps the edited range is dropped (its underlying text is no
32
+ * longer intact, so it's no longer a valid mention).
33
+ */
34
+ export declare const shiftMentionSpans: (spans: IMentionSpan[], editStart: number, editEnd: number, insertedLength: number) => IMentionSpan[];
35
+ /**
36
+ * Drops any span whose text no longer reads "@Name" in the current body
37
+ * (e.g. a multi-character selection delete clipped part of it). Safety net
38
+ * on top of shiftMentionSpans for edits that aren't tracked precisely.
39
+ */
40
+ export declare const validateMentionSpans: (text: string, spans: IMentionSpan[]) => IMentionSpan[];
41
+ /** Finds a mention span whose token ends exactly at `caret` (i.e. the caret
42
+ * sits right after a complete "@Name" token) - used to make Backspace
43
+ * delete the whole token atomically instead of one character. */
44
+ export declare const findMentionEndingAt: (spans: IMentionSpan[], caret: number) => IMentionSpan | undefined;
45
+ /**
46
+ * Diffs two versions of the composer text (before/after a single onChange)
47
+ * down to the edited range, via longest common prefix/suffix. Good enough
48
+ * for the single-edit-at-a-time case a controlled <input>/<textarea>
49
+ * onChange always is (typing, pasting, cutting, IME commit): everything
50
+ * outside the diffed range is provably unchanged, which is what
51
+ * shiftMentionSpans needs to re-anchor mention offsets correctly.
52
+ */
53
+ export declare const computeEditRange: (oldText: string, newText: string) => {
54
+ start: number;
55
+ end: number;
56
+ insertedLength: number;
57
+ };
58
+ /**
59
+ * Splices mention spans into `text` as raw `<mention>` tags for the
60
+ * markdown/rehype-raw pipeline to pick up (see parseMessageBody.tsx). Spans
61
+ * are applied back-to-front so earlier offsets stay valid while splicing.
62
+ */
63
+ export declare const spliceMentionMarkup: (text: string, spans: IMentionSpan[] | undefined) => string;
@@ -1,4 +1,14 @@
1
- export declare const parseMessageBody: ({ text }: {
1
+ import { IMentionSpan } from '../types/types';
2
+ export interface MentionClickHandler {
3
+ (mention: {
4
+ jid: string;
5
+ name: string;
6
+ }): void;
7
+ }
8
+ interface ParseMessageBodyArgs {
2
9
  text: string;
3
- }) => import("react/jsx-runtime").JSX.Element;
10
+ mentions?: IMentionSpan[];
11
+ onMentionClick?: MentionClickHandler;
12
+ }
13
+ export declare const parseMessageBody: ({ text, mentions, onMentionClick, }: ParseMessageBodyArgs) => import("react/jsx-runtime").JSX.Element;
4
14
  export default parseMessageBody;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Lazy, failure-tolerant access to pdf.js.
3
+ *
4
+ * Two things drive the shape of this module:
5
+ *
6
+ * 1. pdf.js is ~1.5 MB. It must never land in the chunk that a host pays for
7
+ * on first paint, so it is only ever reached through `import()` from
8
+ * here, and this module is itself only imported dynamically.
9
+ *
10
+ * 2. Wiring `GlobalWorkerOptions.workerSrc` from inside a library is a
11
+ * portability trap: the URL has to survive our Vite lib build AND the
12
+ * host's bundler (Vite, webpack, Next, CRA), and every published recipe
13
+ * for it is bundler-specific. So by default we do not use a worker file
14
+ * at all - importing the worker module and parking it on
15
+ * `globalThis.pdfjsWorker` makes pdf.js run its message handler on the
16
+ * main thread (see PDFWorker's `#mainThreadWorkerMessageHandler`). For a
17
+ * one-page thumbnail that is a few milliseconds of main-thread work, and
18
+ * it is bundler-agnostic. Hosts that render large PDFs can hand us a real
19
+ * `workerSrc` through `config.pdfPreview.workerSrc` and get the worker
20
+ * path back.
21
+ *
22
+ * Any failure (module missing, CSP blocking eval, unsupported browser)
23
+ * resolves to `null`; callers fall back to the static document card.
24
+ */
25
+ export interface PdfRenderTask {
26
+ promise: Promise<void>;
27
+ cancel: () => void;
28
+ }
29
+ export interface PdfViewport {
30
+ width: number;
31
+ height: number;
32
+ }
33
+ export interface PdfPageProxy {
34
+ getViewport: (params: {
35
+ scale: number;
36
+ }) => PdfViewport;
37
+ render: (params: {
38
+ canvasContext: CanvasRenderingContext2D;
39
+ viewport: PdfViewport;
40
+ }) => PdfRenderTask;
41
+ cleanup: () => void;
42
+ }
43
+ export interface PdfDocumentProxy {
44
+ numPages: number;
45
+ getPage: (pageNumber: number) => Promise<PdfPageProxy>;
46
+ destroy: () => Promise<void>;
47
+ }
48
+ export interface PdfDocumentLoadingTask {
49
+ promise: Promise<PdfDocumentProxy>;
50
+ destroy: () => Promise<void>;
51
+ }
52
+ export interface PdfjsLib {
53
+ getDocument: (params: {
54
+ url: string;
55
+ withCredentials?: boolean;
56
+ isEvalSupported?: boolean;
57
+ }) => PdfDocumentLoadingTask;
58
+ GlobalWorkerOptions: {
59
+ workerSrc: string;
60
+ };
61
+ }
62
+ /** Test seam - the module-level cache would otherwise leak between cases. */
63
+ export declare const resetPdfjsCache: () => void;
64
+ export declare const loadPdfjs: (workerSrc?: string) => Promise<PdfjsLib | null>;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * One bot-offered button, as carried by the `quickReplies` attribute on a
3
+ * message stanza's <data> element. The wire format is the one the existing
4
+ * Ethora bots already emit (see ethora-backend/tools/web3-bots/*: they stamp
5
+ * `quickReplies: JSON.stringify(message.buttons)` with `{name, value}`
6
+ * objects), so nothing on the bot side has to change to light these up.
7
+ *
8
+ * `questionId` is the only addition: quiz-style flows need a stable id for
9
+ * the answer they collect, and it is optional so plain "yes/no" bots keep
10
+ * working. When it is absent we fall back to the button's index.
11
+ */
12
+ export interface QuickReply {
13
+ /** Label shown on the chip. */
14
+ name: string;
15
+ /** Text sent back to the room when the chip is tapped. */
16
+ value: string;
17
+ /** Quiz question this button answers, used to build the answer URL. */
18
+ questionId?: string;
19
+ }
20
+ /**
21
+ * Decode the `quickReplies` payload into buttons. Deliberately forgiving:
22
+ * the attribute is written by bots (and, once the LLM-driven flow lands, by
23
+ * a model), so anything unparseable must degrade to "no buttons" rather
24
+ * than break the message. Accepts the already-decoded array too, since
25
+ * getDataFromXml decodes it in place the same way it does `mentions`.
26
+ */
27
+ export declare const parseQuickReplies: (rawValue: unknown) => QuickReply[];
28
+ export declare const markQuickRepliesAnswered: (messageId: string) => void;
29
+ export declare const hasAnsweredQuickReplies: (messageId: string) => boolean;
30
+ /** Test seam - the set is process-wide. */
31
+ export declare const resetAnsweredQuickReplies: () => void;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * A tiny local stand-in for `remark-breaks`.
3
+ *
4
+ * In CommonMark a single "\n" inside a paragraph is a "soft break": it is
5
+ * kept as a literal newline character inside the surrounding mdast `text`
6
+ * node, and every renderer (ours included) turns that into a plain space.
7
+ * That is what made a message typed with real line breaks collapse into
8
+ * one line once it went through react-markdown.
9
+ *
10
+ * `remark-breaks` fixes this by rewriting each soft break into a real
11
+ * mdast `break` node (which renders as `<br />`). We do the same thing by
12
+ * hand instead of adding that package (or `unist-util-visit`) as a new
13
+ * dependency: this is a published SDK, and the tree walk below is a dozen
14
+ * lines.
15
+ *
16
+ * Only `text` nodes carry this problem. Fenced/indented code, inline code
17
+ * spans and raw HTML passthrough all keep their literal newlines as part
18
+ * of their own syntax (a `code`/`inlineCode` node's content lives in
19
+ * `value`, not in child `text` nodes, and an `html` node is a leaf too),
20
+ * so the walk below never touches them - it only ever rewrites `text`
21
+ * children, and only descends into nodes that actually have children.
22
+ *
23
+ * Existing hard breaks (two trailing spaces, or a trailing backslash,
24
+ * before a newline) are already parsed into their own `break` node by the
25
+ * time a remark plugin sees the tree - the newline itself is consumed by
26
+ * that syntax and never ends up inside a `text` node's value. So there is
27
+ * nothing left for this plugin to find there, and it cannot double them.
28
+ */
29
+ export interface MarkdownAstNode {
30
+ type: string;
31
+ value?: string;
32
+ children?: MarkdownAstNode[];
33
+ [key: string]: unknown;
34
+ }
35
+ /**
36
+ * Remark plugin: turns every soft line break in the document into a real
37
+ * `break` node. Usage mirrors any other remark plugin:
38
+ *
39
+ * remarkPlugins={[remarkGfm, remarkLineBreaks]}
40
+ */
41
+ export declare const remarkLineBreaks: () => (tree: MarkdownAstNode) => void;
42
+ export default remarkLineBreaks;
@@ -0,0 +1,34 @@
1
+ import { IMessage } from '../types/types';
2
+ export interface ResolvedMessageTranslation {
3
+ /**
4
+ * Whether this message would need translating for this reader at all: a
5
+ * known source language whose base differs from the reader's, and actual
6
+ * translatable text. False means there is nothing to reveal, no matter
7
+ * what a host `onTranslate` or a translate service might otherwise find -
8
+ * a bare number, an emoji-only reaction, an untagged message, or a
9
+ * message already in the reader's language.
10
+ */
11
+ needsTranslation: boolean;
12
+ /**
13
+ * True when a real, non-identical translation is already attached to the
14
+ * message (`message.translations`) for this reader - safe to render with
15
+ * no network call at all.
16
+ */
17
+ hasTranslation: boolean;
18
+ originalText: string;
19
+ /** hasTranslation ? the translation : originalText - always safe to render directly. */
20
+ displayText: string;
21
+ }
22
+ /**
23
+ * Resolve "what translation do we have for this message, for this reader" -
24
+ * pure, synchronous, no HTTP. This is the ONE place that reads
25
+ * `message.translations` and decides whether what it finds counts as a real
26
+ * translation.
27
+ *
28
+ * Auto mode (`useMessageTranslation`) and manual mode (`MessageTranslate`)
29
+ * both call this instead of keeping their own copies of the lookup, so they
30
+ * can never drift into showing different things for the same message: manual
31
+ * mode's click-to-reveal shows exactly what auto mode would have rendered
32
+ * automatically, because both ask this same function the same question.
33
+ */
34
+ export declare const resolveMessageTranslation: (message: Pick<IMessage, "body" | "langSource" | "translations">, readerLocale?: string) => ResolvedMessageTranslation;
@@ -0,0 +1,53 @@
1
+ import { IMessage, ModalFile } from '../types/types';
2
+ /**
3
+ * One navigable entry in the media viewer's gallery.
4
+ *
5
+ * Shaped as a `ModalFile` (plus a render key) on purpose: navigating is just
6
+ * `dispatch(setActiveFile(entry))`, so every downstream consumer - the
7
+ * download action, the secure `?ft=` token treatment, the alt text - keeps
8
+ * reading exactly the fields it read when a single file was opened by hand.
9
+ */
10
+ export interface GalleryImage extends ModalFile {
11
+ /** Stable per message+slot, so two identical URLs are still two entries. */
12
+ key: string;
13
+ }
14
+ /**
15
+ * The gallery the lightbox pages through.
16
+ *
17
+ * Scope, deliberately narrow and honest: **image attachments carried by the
18
+ * messages currently in the store for this room, in chronological order**.
19
+ * That is not "every image ever posted here":
20
+ *
21
+ * - History is paged from MAM. Only what has been loaded so far is here, so
22
+ * the gallery grows as the user (or the background preloader) pulls older
23
+ * history in. The counter re-renders with it.
24
+ * - MessageList's render window (120 initial / 60 per step) does NOT bound
25
+ * this: the window controls how many messages are *mounted*, while the
26
+ * store keeps every loaded message. So the gallery routinely contains
27
+ * images that are not currently in the DOM, which is what makes paging
28
+ * past the top of the viewport work at all.
29
+ * - `useMyFiles` (/v2/files) was the other candidate source and is the wrong
30
+ * one: that endpoint lists the *caller's own* uploads with no server-side
31
+ * room filter, so it would show the viewer their own files from other
32
+ * rooms while hiding every image anybody else posted in this one.
33
+ *
34
+ * Non-images (video, PDF, audio, arbitrary files) are excluded, so opening a
35
+ * PDF simply produces an empty gallery and no navigation chrome.
36
+ */
37
+ export declare const collectRoomImages: (messages?: Partial<IMessage>[] | null) => GalleryImage[];
38
+ /**
39
+ * Position of the open file inside the gallery, or -1 when it is not part of
40
+ * it (a PDF, a video, a file opened from the Files panel of another room).
41
+ *
42
+ * Matching is by URL with the secure-file `?ft=` token stripped from both
43
+ * sides: it is the one field every entry point fills in, but the URL that
44
+ * reached the store when the bubble was clicked can already carry a token
45
+ * (they are minted per viewer and expire in about an hour) while the copy
46
+ * read back from the message does not. Comparing the raw strings therefore
47
+ * failed for exactly the secure rooms this feature has to work in.
48
+ *
49
+ * Two posts of the same URL collapse onto the first: a duplicate image is
50
+ * the same picture, so paging to it twice would only look like the arrows
51
+ * were stuck.
52
+ */
53
+ export declare const findGalleryIndex: (images: GalleryImage[], activeFile?: ModalFile | null) => number;
@@ -1,4 +1,12 @@
1
1
  export declare const isSecureFileUrl: (url?: string | null) => boolean;
2
2
  export declare const appendFileToken: (url: string | null | undefined, fileToken: string | null | undefined) => string;
3
+ /**
4
+ * The same URL with any `?ft=` token removed, for comparing two references
5
+ * to the same file. Tokens are per-viewer and short-lived, so the URL a
6
+ * message bubble handed the store can carry one while the copy read back
7
+ * from the message's attachments does not (or carries an older one). Only
8
+ * the token-free form is a stable identity.
9
+ */
10
+ export declare const withoutFileToken: (url?: string | null) => string;
3
11
  export declare const withFileToken: (url?: string | null) => string;
4
12
  export declare const requestFileTokenRecovery: () => Promise<boolean>;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * How long a send may stay unacknowledged before we show the sender a
3
+ * "not delivered" state with a retry control.
4
+ *
5
+ * Why 12s, and not something snappier:
6
+ *
7
+ * - The only acknowledgement this protocol gives us is the MUC reflecting
8
+ * our own message back (`addRoomMessage` clears `pending` on it). There
9
+ * is no delivery receipt, so "no echo yet" is the ONLY failure signal we
10
+ * have, and it is a weak one - it is equally consistent with a healthy
11
+ * but slow send.
12
+ * - A healthy echo lands in ~100-300ms. xmppClient's own SLA warning fires
13
+ * at a p95 click-to-echo of 1500ms (`sendSlaWarningThresholdMs`), i.e.
14
+ * the client already considers 1.5s the outer edge of normal. 12s is 8x
15
+ * that outer edge.
16
+ * - The catch-up probe (`scheduleAckCatchup`, 2500ms) re-asserts presence
17
+ * and pulls MAM when the echo is missing. That probe gets a full ~9.5s
18
+ * to reconcile the message before this watchdog is allowed to call it
19
+ * failed, so the common "our presence silently dropped" case is repaired
20
+ * without the user ever seeing a failure.
21
+ * - Bursts and long messages are covered by WHERE the timer is armed, not
22
+ * by its length: it starts when the optimistic bubble appears and is
23
+ * re-armed on retry, while the send queue is serialised. A message stuck
24
+ * behind a burst still has its own 12s window measured from the moment
25
+ * the user could first see it.
26
+ *
27
+ * Erring long is deliberate. React Native defect #31 (26.7.1) shipped the
28
+ * opposite trade: a trigger-happy timeout showed accepted messages as
29
+ * failed and the resulting retries delivered them twice. A late "failed" is
30
+ * a cosmetic annoyance; an early one produces duplicates for the recipient.
31
+ */
32
+ export declare const SEND_FAILURE_TIMEOUT_MS = 12000;
33
+ /**
34
+ * Sanity check, not a runtime knob: the watchdog must never be able to fire
35
+ * before the catch-up probe has had a chance to repair the send.
36
+ */
37
+ export declare const SEND_FAILURE_MIN_TIMEOUT_MS: number;
38
+ /** True while a manual retry for this message id is on the wire. */
39
+ export declare const isSendRetryInFlight: (messageId: string) => boolean;
40
+ /**
41
+ * Claims the retry slot for a message id. Returns false when another retry
42
+ * for the same id already holds it, which is what makes a double-tap (or a
43
+ * second component instance rendering the same message) send exactly once.
44
+ */
45
+ export declare const beginSendRetry: (messageId: string) => boolean;
46
+ export declare const endSendRetry: (messageId: string) => void;
47
+ export declare const clearSendFailureWatchdog: (messageId?: string) => void;
48
+ export declare const clearAllSendFailureWatchdogs: () => void;
49
+ /** Whether the store currently shows this message as failed-to-send. */
50
+ export declare const isMessageMarkedFailed: (roomJID: string, messageId: string) => boolean;
51
+ /**
52
+ * Arms the "this send was never acknowledged" timer for one message.
53
+ *
54
+ * Re-arming for the same id replaces the previous timer rather than adding
55
+ * a second one, so a retry cannot leave two watchdogs racing on one message.
56
+ */
57
+ export declare const armSendFailureWatchdog: (params: {
58
+ roomJID: string;
59
+ messageId: string;
60
+ body: string;
61
+ timeoutMs?: number;
62
+ }) => void;
@@ -1,4 +1,4 @@
1
- interface Translation {
1
+ export interface Translation {
2
2
  translatedText: string;
3
3
  language: string;
4
4
  languageName: string;
@@ -7,4 +7,3 @@ export interface TranslationObject {
7
7
  [language: string]: Translation;
8
8
  }
9
9
  export declare const transformArrayToObject: (array: Translation[]) => TranslationObject;
10
- export {};
@@ -0,0 +1,41 @@
1
+ /**
2
+ * pushSubscriptionSweep.ts
3
+ *
4
+ * Pure, hook-free worker pool for usePushNotifications' per-room push
5
+ * subscription sweep. Kept in its own module (no React, no Firebase, no
6
+ * Redux imports) so it can be unit tested without pulling in the heavy
7
+ * side effects those modules carry at import time.
8
+ */
9
+ export type PushSubscribeResult = {
10
+ ok: boolean;
11
+ reason?: string;
12
+ message?: string;
13
+ };
14
+ export type PushSweepGuards = {
15
+ getStatus: (jid: string) => string | undefined;
16
+ getRetryAt: (jid: string) => number | undefined;
17
+ isInFlight: (jid: string) => boolean;
18
+ markInFlight: (jid: string) => void;
19
+ clearInFlight: (jid: string) => void;
20
+ setStatus: (jid: string, status: string) => void;
21
+ subscribe: (jid: string) => Promise<PushSubscribeResult>;
22
+ onSubscribed?: (jid: string) => void;
23
+ onFailed?: (jid: string, message?: string) => void;
24
+ };
25
+ /** Number of rooms subscribed to push in parallel during a sweep. */
26
+ export declare const PUSH_SWEEP_CONCURRENCY = 4;
27
+ /**
28
+ * Sweeps `roomJIDs` for push subscription, previously a strictly serial
29
+ * for-loop with a fixed 100ms sleep between every room (so N rooms took at
30
+ * least N*100ms even though subscribing does not depend on room order).
31
+ * This runs a small worker pool instead, bounded by `concurrency`, while
32
+ * keeping every existing guard intact: status checks (subscribed / pending
33
+ * / blocked / error are skipped), the roomRetryAtRef backoff, and the
34
+ * roomSubscribeInFlightRef in-flight lock. The caller owns the
35
+ * roomProcessRunningRef / lastRoomsHashRef gates around the whole sweep.
36
+ *
37
+ * `roomJIDs` are processed by a fixed-size pool of workers pulling from a
38
+ * shared index, so at most `concurrency` subscribe calls are in flight at
39
+ * once regardless of how many rooms need syncing.
40
+ */
41
+ export declare const sweepPushSubscriptions: (roomJIDs: string[], guards: PushSweepGuards, concurrency?: number) => Promise<void>;
@@ -17,5 +17,6 @@ interface useChatWrapperInitResult {
17
17
  }
18
18
  export declare const resolveLegacyTranslatesLangSource: (translatesConfig?: IConfig["translates"]) => Iso639_1Codes | undefined;
19
19
  export declare const resolveExternalReaderLocaleLangSource: (readerLocale?: string) => Iso639_1Codes | undefined;
20
+ export declare const isStatusConnectionLost: (status?: string | null) => boolean;
20
21
  declare const useChatWrapperInit: ({ roomJID, wasAutoSelected, config, }: useChatWrapperInitProps) => useChatWrapperInitResult;
21
22
  export default useChatWrapperInit;
@@ -0,0 +1,9 @@
1
+ import { FilesEndpointSupport } from '../networking/api-requests/files.api';
2
+ /**
3
+ * Whether the current user's backend actually has /v2/files. Starts
4
+ * 'unknown' and flips to 'supported'/'unsupported' the first time a real
5
+ * files list request resolves (see files.api.ts) - there's no separate probe
6
+ * request, this just observes the outcome of whatever the Files panel itself
7
+ * already does.
8
+ */
9
+ export declare function useFilesEndpointSupport(): FilesEndpointSupport;
@@ -0,0 +1,17 @@
1
+ interface UseInViewOptions {
2
+ /** Start work slightly before the element scrolls into view. */
3
+ rootMargin?: string;
4
+ /** Once true, stay true - re-rendering a thumbnail on every scroll pass is waste. */
5
+ once?: boolean;
6
+ }
7
+ /**
8
+ * Viewport gate for expensive per-message work (PDF rendering today).
9
+ * Environments without IntersectionObserver - jsdom, older Safari - report
10
+ * "in view" immediately, which degrades to the previous eager behaviour
11
+ * rather than to a blank card.
12
+ */
13
+ export declare const useInView: <T extends Element>({ rootMargin, once, }?: UseInViewOptions) => {
14
+ ref: import('react').MutableRefObject<T>;
15
+ inView: boolean;
16
+ };
17
+ export {};