@ethora/chat-component 26.9.6 → 26.10.1

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 (133) hide show
  1. package/README.md +45 -3
  2. package/dist/{ChatProfileModal-DKuxJsdM.js → ChatProfileModal-Bf5jGPBZ.js} +566 -562
  3. package/dist/FilePreviewModal-Bwx0RZZw.js +1763 -0
  4. package/dist/{ManageDataModal-Dw4dCQll.js → ManageDataModal-Dhkp0LWH.js} +20 -20
  5. package/dist/{MarkdownBody-xtKDMR0h.js → MarkdownBody-_sUhW2O8.js} +1 -1
  6. package/dist/{MessageSearchModal-BVKU6H3v.js → MessageSearchModal-rwwWrILl.js} +106 -114
  7. package/dist/{PublicChatsModal-CscOnVaQ.js → PublicChatsModal-Byso6_vt.js} +13 -13
  8. package/dist/{SideDrawer-WneDrrWG.js → SideDrawer-CQPOf0ek.js} +38 -38
  9. package/dist/{UserProfileModal-CMHFq1I9.js → UserProfileModal-Vj2kICvc.js} +10 -10
  10. package/dist/{UserSettingsModal-BoZXZ0oR.js → UserSettingsModal-CioX4ZIV.js} +5 -5
  11. package/dist/{VideoCallOverlay-CQNi7wp5.js → VideoCallOverlay-DodAkiam.js} +3824 -3708
  12. package/dist/{VisibilityModal-BPsNiMzJ.js → VisibilityModal-CIl7rkBM.js} +12 -12
  13. package/dist/assets/icons.d.ts +2 -3
  14. package/dist/components/Files/fileCategory.d.ts +4 -2
  15. package/dist/components/InputComponents/MentionDropdown.d.ts +2 -0
  16. package/dist/components/MainComponents/AlbumGrid.d.ts +22 -0
  17. package/dist/components/MainComponents/AttachmentList.d.ts +6 -6
  18. package/dist/components/MainComponents/ImageUnavailable.d.ts +21 -0
  19. package/dist/components/MainComponents/MediaUploadOverlay.d.ts +31 -0
  20. package/dist/components/MainComponents/MessageContainer.d.ts +3 -0
  21. package/dist/components/MainComponents/scrollAnchor.d.ts +19 -0
  22. package/dist/components/MainComponents/useJumpToMessage.d.ts +42 -11
  23. package/dist/components/MainComponents/useScrollAnchor.d.ts +29 -0
  24. package/dist/components/MessageBubble/MediaCaption.d.ts +9 -0
  25. package/dist/components/MessageBubble/MessageAvatarPhoto.d.ts +17 -0
  26. package/dist/components/MessageBubble/MessageReaction.d.ts +6 -0
  27. package/dist/components/MessageBubble/QuickReplies.d.ts +2 -0
  28. package/dist/components/Modals/FilePreviewModal/FilePreviewModal.d.ts +8 -10
  29. package/dist/components/Modals/FilePreviewModal/ImageLightbox.d.ts +4 -0
  30. package/dist/components/Modals/FilePreviewModal/PdfView.d.ts +4 -0
  31. package/dist/components/Modals/FilePreviewModal/ViewerChrome.d.ts +64 -0
  32. package/dist/components/Modals/MessageSearchModal/MessageHitResults.d.ts +5 -0
  33. package/dist/components/Modals/styledModalComponents.d.ts +0 -8
  34. package/dist/components/RoomComponents/LastMessageItem.d.ts +2 -0
  35. package/dist/components/RoomComponents/styled/LastMessageMedia.d.ts +19 -0
  36. package/dist/components/RoomComponents/styled/LastTextMessage.d.ts +2 -2
  37. package/dist/components/RoomComponents/styled/StyledRoomComponents.d.ts +10 -3
  38. package/dist/components/VideoCalls/VideoCallSession.d.ts +13 -0
  39. package/dist/components/styled/FileCard.d.ts +20 -0
  40. package/dist/components/styled/MediaSurface.d.ts +50 -0
  41. package/dist/components/styled/MessageImage.d.ts +9 -10
  42. package/dist/components/styled/SendInput.d.ts +12 -2
  43. package/dist/components/styled/StyledComponents.d.ts +69 -2
  44. package/dist/components/styled/StyledInputComponents/MediaComponents.d.ts +36 -24
  45. package/dist/components/styled/UnsupportedType.d.ts +1 -0
  46. package/dist/components/styled/VideoMessage.d.ts +11 -6
  47. package/dist/e2ee/callKey.d.ts +32 -0
  48. package/dist/e2ee/fileEnvelope.d.ts +16 -5
  49. package/dist/e2ee/omemo.d.ts +18 -0
  50. package/dist/firebase-messaging-sw.js +20 -3
  51. package/dist/{handleCopyClick-B70vLjJD.js → handleCopyClick-DGaZhy_u.js} +1 -1
  52. package/dist/helpers/albumLayout.d.ts +53 -0
  53. package/dist/helpers/attachments.d.ts +22 -1
  54. package/dist/helpers/avatarImageCache.d.ts +9 -0
  55. package/dist/helpers/caption.d.ts +63 -0
  56. package/dist/helpers/checkUniqueUsers.d.ts +2 -2
  57. package/dist/helpers/composerFocus.d.ts +22 -0
  58. package/dist/helpers/createMainMessageForThread.d.ts +1 -1
  59. package/dist/helpers/dedupeMembers.d.ts +10 -0
  60. package/dist/helpers/downloadFile.d.ts +19 -0
  61. package/dist/helpers/fileKind.d.ts +17 -0
  62. package/dist/helpers/historyPreloadConfig.d.ts +25 -0
  63. package/dist/helpers/historyPreloadScheduler.d.ts +3 -0
  64. package/dist/helpers/insertMessageWithDelimiter.d.ts +1 -0
  65. package/dist/helpers/isMessageSearchEnabled.d.ts +9 -0
  66. package/dist/helpers/jumpThread.d.ts +56 -0
  67. package/dist/helpers/jumpWindow.d.ts +73 -0
  68. package/dist/helpers/lastMessagePreview.d.ts +55 -0
  69. package/dist/helpers/mapWithConcurrency.d.ts +8 -0
  70. package/dist/helpers/mediaBox.d.ts +110 -0
  71. package/dist/helpers/mediaBubble.d.ts +39 -0
  72. package/dist/helpers/mediaLabel.d.ts +10 -0
  73. package/dist/helpers/mediaSendRetry.d.ts +25 -0
  74. package/dist/helpers/messageRuns.d.ts +10 -0
  75. package/dist/helpers/openThreadForJump.d.ts +26 -0
  76. package/dist/helpers/pdf/loadPdfjs.d.ts +8 -1
  77. package/dist/helpers/repairFilenameEncoding.d.ts +11 -0
  78. package/dist/helpers/roomImageGallery.d.ts +34 -0
  79. package/dist/helpers/roomUserCount.d.ts +19 -0
  80. package/dist/helpers/safeUrl.d.ts +15 -0
  81. package/dist/helpers/secureFileUrl.d.ts +4 -1
  82. package/dist/helpers/userResolver.d.ts +57 -0
  83. package/dist/helpers/viewerSender.d.ts +47 -0
  84. package/dist/helpers/xmppIdShape.d.ts +1 -1
  85. package/dist/hooks/useAvatarImage.d.ts +21 -0
  86. package/dist/hooks/useChatWrapperInit.d.ts +6 -0
  87. package/dist/hooks/useIsMobileViewport.d.ts +5 -0
  88. package/dist/hooks/useMediaProbe.d.ts +40 -0
  89. package/dist/hooks/usePaneWidthVar.d.ts +11 -0
  90. package/dist/hooks/usePdfThumbnail.d.ts +2 -2
  91. package/dist/hooks/usePrefersReducedMotion.d.ts +2 -0
  92. package/dist/hooks/useRoomDirectory.d.ts +11 -0
  93. package/dist/hooks/useSendMessage.d.ts +4 -1
  94. package/dist/{main-rJpE4hh-.js → main-CUB_w8AZ.js} +29136 -24217
  95. package/dist/main.css +1 -1
  96. package/dist/main.js +16 -16
  97. package/dist/networking/api-requests/auth.api.d.ts +2 -0
  98. package/dist/networking/api-requests/roomMembers.api.d.ts +18 -0
  99. package/dist/networking/callKeyStanza.d.ts +7 -0
  100. package/dist/networking/ethoraEvents.d.ts +44 -0
  101. package/dist/networking/stanzaHandlers.d.ts +6 -4
  102. package/dist/networking/xmpp/allRoomPresences.xmpp.d.ts +18 -1
  103. package/dist/networking/xmpp/presenceInRoom.xmpp.d.ts +1 -1
  104. package/dist/networking/xmpp/sendDisplayedMarker.xmpp.d.ts +2 -0
  105. package/dist/networking/xmpp/sendMediaMessage.xmpp.d.ts +8 -0
  106. package/dist/networking/xmpp/sendReceivedReceipt.xmpp.d.ts +2 -0
  107. package/dist/networking/xmppClient.d.ts +61 -2
  108. package/dist/roomStore/Middleware/jumpThreadMiddleware.d.ts +2 -0
  109. package/dist/roomStore/callSlice.d.ts +6 -1
  110. package/dist/roomStore/index.d.ts +22 -0
  111. package/dist/roomStore/roomsSlice.d.ts +51 -4
  112. package/dist/roomStore/safeKey.d.ts +12 -0
  113. package/dist/roomStore/usersSetCap.d.ts +15 -0
  114. package/dist/styles/motion.d.ts +22 -2
  115. package/dist/types/models/attachment.model.d.ts +19 -0
  116. package/dist/types/models/config.model.d.ts +98 -5
  117. package/dist/types/models/customComponents.model.d.ts +2 -0
  118. package/dist/types/models/message.model.d.ts +9 -0
  119. package/dist/types/models/modal.model.d.ts +19 -0
  120. package/dist/types/models/room.model.d.ts +30 -0
  121. package/dist/types/models/xmpp.model.d.ts +24 -1
  122. package/dist/utils/messageNotificationContent.d.ts +34 -0
  123. package/dist/version.d.ts +1 -1
  124. package/package.json +2 -1
  125. package/public/firebase-messaging-sw.js +20 -3
  126. package/dist/FilePreviewModal-BcaSoO3-.js +0 -743
  127. package/dist/components/MainComponents/computeAnchoredScrollTop.d.ts +0 -9
  128. package/dist/components/Modals/NewChatModal/UpdNewChatModal.d.ts +0 -3
  129. package/dist/components/RoomComponents/styled/LastAudioMessage.d.ts +0 -6
  130. package/dist/components/RoomComponents/styled/LastMessageFile.d.ts +0 -6
  131. package/dist/components/RoomComponents/styled/LastMessagePhoto.d.ts +0 -6
  132. package/dist/components/RoomComponents/styled/LastMessageVideo.d.ts +0 -6
  133. package/dist/components/SortDropDown/index.d.ts +0 -8
package/dist/main.css CHANGED
@@ -1 +1 @@
1
- .ethora-chat-root{font-family:var( --ethora-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif );-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.ethora-chat-root code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}.ethora-chat-root,.ethora-chat-root *{scrollbar-width:thin;scrollbar-color:rgba(20,20,20,.22) transparent;scrollbar-color:var( --ethora-scrollbar-thumb, color-mix(in srgb, var(--ethora-icon-color, #0052cd) 35%, transparent) ) var(--ethora-scrollbar-track, transparent)}.ethora-chat-root::-webkit-scrollbar,.ethora-chat-root ::-webkit-scrollbar{width:8px;height:8px}.ethora-chat-root::-webkit-scrollbar-thumb,.ethora-chat-root ::-webkit-scrollbar-thumb{background-color:#14141438;background-color:var( --ethora-scrollbar-thumb, color-mix(in srgb, var(--ethora-icon-color, #0052cd) 35%, transparent) );border-radius:var(--ethora-radius-full, 999px);border:2px solid transparent;background-clip:content-box}.ethora-chat-root::-webkit-scrollbar-thumb:hover,.ethora-chat-root ::-webkit-scrollbar-thumb:hover{background-color:#14141466;background-color:var( --ethora-scrollbar-thumb-hover, color-mix(in srgb, var(--ethora-icon-color, #0052cd) 60%, transparent) );background-clip:content-box}.ethora-chat-root::-webkit-scrollbar-track,.ethora-chat-root ::-webkit-scrollbar-track{background-color:var(--ethora-scrollbar-track, transparent)}.ethora-apple-emoji{font-family:"Apple Color Emoji","Segoe UI Emoji","Noto Color Emoji",sans-serif}.ethora-chat-root .emoji-mart{max-width:320px;max-height:360px;overflow-y:auto;border-radius:12px;box-shadow:0 4px 12px #0000001a}[data-message-id].ethora-message-highlight{animation:ethoraMessageFlash 1s ease-out;border-radius:var(--ethora-radius-md, 12px)}@keyframes ethoraMessageFlash{0%{background-color:color-mix(in srgb,var(--ethora-color-primary, #0052cd) 35%,transparent)}to{background-color:transparent}}.ethora-message-highlight{animation:ethoraMessageHighlight 1s ease-out;transition:background-color .3s ease}@keyframes ethoraMessageHighlight{0%{background-color:#2196f366;transform:scale(1.02);box-shadow:0 0 0 2px #2196f333}30%{background-color:#2196f34d;box-shadow:0 0 0 2px #2196f326}60%{background-color:#2196f333;box-shadow:0 0 0 1px #2196f31a}to{background-color:transparent;transform:scale(1);box-shadow:none}}@media(max-width:768px){.ethora-message-notification-container{left:50%!important;right:auto!important;transform:translate(-50%)!important;align-items:center!important}.ethora-message-notification-container[data-position-horizontal=left],.ethora-message-notification-container[data-position-horizontal=right]{left:50%!important;right:auto!important;transform:translate(-50%)!important}}
1
+ .ethora-chat-root{font-family:var( --ethora-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif );-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.ethora-chat-root code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}.ethora-chat-root,.ethora-chat-root *{scrollbar-width:thin;scrollbar-color:rgba(20,20,20,.22) transparent;scrollbar-color:var( --ethora-scrollbar-thumb, color-mix(in srgb, var(--ethora-icon-color, #0052cd) 35%, transparent) ) var(--ethora-scrollbar-track, transparent)}.ethora-chat-root::-webkit-scrollbar,.ethora-chat-root ::-webkit-scrollbar{width:8px;height:8px}.ethora-chat-root::-webkit-scrollbar-thumb,.ethora-chat-root ::-webkit-scrollbar-thumb{background-color:#14141438;background-color:var( --ethora-scrollbar-thumb, color-mix(in srgb, var(--ethora-icon-color, #0052cd) 35%, transparent) );border-radius:var(--ethora-radius-full, 999px);border:2px solid transparent;background-clip:content-box}.ethora-chat-root::-webkit-scrollbar-thumb:hover,.ethora-chat-root ::-webkit-scrollbar-thumb:hover{background-color:#14141466;background-color:var( --ethora-scrollbar-thumb-hover, color-mix(in srgb, var(--ethora-icon-color, #0052cd) 60%, transparent) );background-clip:content-box}.ethora-chat-root::-webkit-scrollbar-track,.ethora-chat-root ::-webkit-scrollbar-track{background-color:var(--ethora-scrollbar-track, transparent)}.ethora-apple-emoji{font-family:"Apple Color Emoji","Segoe UI Emoji","Noto Color Emoji",sans-serif}.ethora-chat-root .emoji-mart{max-width:320px;max-height:360px;overflow-y:auto;border-radius:12px;box-shadow:0 4px 12px #0000001a}[data-message-id].ethora-message-highlight{animation:none;transition:none}[data-message-id].ethora-message-highlight [data-ethora-bubble]{animation:ethoraBubblePulse 1s ease-out forwards}[data-message-id].ethora-message-highlight [data-ethora-bubble]:after{content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;z-index:1;background-color:color-mix(in srgb,var(--ethora-color-primary, #0052cd) 22%,transparent);animation:ethoraBubbleTint 1s ease-out forwards}@keyframes ethoraBubblePulse{0%{box-shadow:0 0 color-mix(in srgb,var(--ethora-color-primary, #0052cd) 55%,transparent)}35%{box-shadow:0 0 0 3px color-mix(in srgb,var(--ethora-color-primary, #0052cd) 60%,transparent),0 0 14px 2px color-mix(in srgb,var(--ethora-color-primary, #0052cd) 35%,transparent)}to{box-shadow:0 0 0 9px color-mix(in srgb,var(--ethora-color-primary, #0052cd) 0%,transparent)}}@keyframes ethoraBubbleTint{0%{opacity:0}20%{opacity:1}to{opacity:0}}@media(prefers-reduced-motion:reduce){[data-message-id].ethora-message-highlight [data-ethora-bubble]{animation:none;box-shadow:0 0 0 2px var(--ethora-color-primary, #0052cd)}[data-message-id].ethora-message-highlight [data-ethora-bubble]:after{animation:none;display:none}}.ethora-message-highlight{animation:ethoraMessageHighlight 1s ease-out;transition:background-color .3s ease}@keyframes ethoraMessageHighlight{0%{background-color:#2196f366;transform:scale(1.02);box-shadow:0 0 0 2px #2196f333}30%{background-color:#2196f34d;box-shadow:0 0 0 2px #2196f326}60%{background-color:#2196f333;box-shadow:0 0 0 1px #2196f31a}to{background-color:transparent;transform:scale(1);box-shadow:none}}@media(max-width:768px){.ethora-message-notification-container{left:50%!important;right:auto!important;transform:translate(-50%)!important;align-items:center!important}.ethora-message-notification-container[data-position-horizontal=left],.ethora-message-notification-container[data-position-horizontal=right]{left:50%!important;right:auto!important;transform:translate(-50%)!important}}
package/dist/main.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ba as a, bb as r, bc as b, bd as o, be as t, bf as u, bg as i, bh as p, bi as h, bj as n, bk as l, bl as d, bm as f, L as m, bn as T, bo as g, bp as y, bq as I, br as R, bs as c, bt as k, bu as C, bv as A, ak as E, al as Q, bw as v, bx as F } from "./main-rJpE4hh-.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-CUB_w8AZ.js";
2
2
  export {
3
3
  a as AuthTestIds,
4
4
  r as Chat,
@@ -10,21 +10,21 @@ export {
10
10
  p as applyTypography,
11
11
  h as clearTypography,
12
12
  n as formatFingerprint,
13
- l as handleQRChatId,
14
- d as isE2eeEnabled,
15
- f as isRefreshFatalError,
16
- m as logoutService,
13
+ d as handleQRChatId,
14
+ l as isE2eeEnabled,
15
+ y as isRefreshFatalError,
16
+ I as logoutService,
17
17
  T as omemo,
18
- g as parseQuickReplies,
19
- y as refreshAuthTokens,
20
- I as refreshAuthTokensQuietly,
21
- R as resendMessage,
18
+ f as parseQuickReplies,
19
+ m as refreshAuthTokens,
20
+ R as refreshAuthTokensQuietly,
21
+ g as resendMessage,
22
22
  c as useInAppNotifications,
23
- k as useIsUserOnline,
24
- C as usePushNotifications,
25
- A as useQRCodeChat,
26
- E as useRoomMute,
27
- Q as useRoomPresence,
28
- v as useTypography,
29
- F as useUnread
23
+ C as useIsUserOnline,
24
+ A as usePushNotifications,
25
+ E as useQRCodeChat,
26
+ F as useRoomMute,
27
+ M as useRoomPresence,
28
+ Q as useTypography,
29
+ k as useUnread
30
30
  };
@@ -27,6 +27,8 @@ export interface UploadFileOptions {
27
27
  * BEFORE the secure/legacy split so both routes carry it.
28
28
  */
29
29
  clientEncrypted?: boolean;
30
+ /** Upload progress for this request, 0 to 100. */
31
+ onProgress?: (percent: number) => void;
30
32
  }
31
33
  export declare function uploadFile(formData: FormData, activeRoomJID: string, options?: UploadFileOptions): Promise<import('axios').AxiosResponse<any, any, {}>>;
32
34
  export declare function ensureUserFromMy(user: User | null | undefined): Promise<User | null>;
@@ -1,2 +1,20 @@
1
1
  import { RoomMember } from '../../types/types';
2
+ export type UserLookupRoute = 'auto' | 'v1' | 'v2';
3
+ export type UserLookupOutcome = {
4
+ status: 'ok';
5
+ user: RoomMember;
6
+ } | {
7
+ status: 'notfound' | 'forbidden' | 'error';
8
+ };
9
+ /** Module-level override of config.userLookupRoute (tests, non-React hosts). */
10
+ export declare const setUserLookupRoute: (route: UserLookupRoute | null) => void;
11
+ /** Forgets what was learned about the routes (account change, logout, tests). */
12
+ export declare const resetUserLookupRoute: () => void;
13
+ /** Visible for tests and diagnostics. */
14
+ export declare const isUserV1RouteUnsupported: () => boolean;
15
+ /**
16
+ * One user by `appId_userId`, through the route chain above. Never throws.
17
+ * `forbidden` and `notfound` are per-user answers, `error` is transient.
18
+ */
19
+ export declare function lookupUser(id: string, token: string): Promise<UserLookupOutcome>;
2
20
  export declare function getUserByXmppUsername(xmppUsername: string | undefined, token: string): Promise<RoomMember | null>;
@@ -0,0 +1,7 @@
1
+ import { Element } from 'ltx';
2
+ /**
3
+ * Claims a decrypted <call-key> signal, feeding the media key into the call
4
+ * slice. Returns true when it handled the stanza (so the caller stops
5
+ * dispatching it to the chat handlers).
6
+ */
7
+ export declare function onCallKeyStanza(stanza: Element): boolean;
@@ -0,0 +1,44 @@
1
+ import { Element } from 'ltx';
2
+ /**
3
+ * Server push `<ethora-event xmlns="urn:ethora:events:1" .../>` inside a
4
+ * `type="headline"` message from the server's admin account:
5
+ *
6
+ * user-profile-updated xmppUsername="<appId>_<userId>" uuid appId
7
+ * chat-meta-updated chatName="<appId>_<roomId>" appId
8
+ *
9
+ * The payload is a pointer only. The client re-reads the user or the room
10
+ * through the normal API, so nothing in the stanza is ever shown or stored.
11
+ * Everything in the stanza is untrusted input until proven otherwise.
12
+ */
13
+ export declare const ETHORA_EVENT_XMLNS = "urn:ethora:events:1";
14
+ export declare const ETHORA_EVENT_USER_PROFILE = "user-profile-updated";
15
+ export declare const ETHORA_EVENT_CHAT_META = "chat-meta-updated";
16
+ type HostLike = {
17
+ host?: string;
18
+ client?: {
19
+ jid?: {
20
+ getDomain?: () => string;
21
+ };
22
+ };
23
+ };
24
+ /**
25
+ * True when a headline came from the server and not from a user or a room.
26
+ * - from must be a BARE jid (a user's c2s stanza or a MUC occupant always
27
+ * carries a resource, and the server stamps it, so it cannot be spoofed)
28
+ * - its domain must be the session's own XMPP domain (not conference.*, not
29
+ * another server)
30
+ * - when config.trustedEventSenders is set, the sender must be listed
31
+ * The server's admin local part is deployment config (XMPP_ADMIN), so it
32
+ * cannot be hard-coded here.
33
+ */
34
+ export declare const isTrustedEventSender: (stanza: Element, xmppWs: HostLike | undefined) => boolean;
35
+ /**
36
+ * Handles `<ethora-event>` headlines. Returns true when the stanza carried
37
+ * one (trusted or not), so the caller can stop routing it. Unknown types,
38
+ * malformed attributes and untrusted senders are ignored silently. Never
39
+ * throws.
40
+ */
41
+ export declare function onEthoraEvent(stanza: Element, xmppWs?: HostLike): boolean;
42
+ /** Drops timers and per-room state (tests, logout). */
43
+ export declare function resetEthoraEvents(): void;
44
+ export {};
@@ -4,9 +4,11 @@ import { default as XmppClient } from './xmppClient';
4
4
  declare const onRealtimeMessage: (stanza: Element, xmppClient?: XmppClient) => Promise<IMessage>;
5
5
  declare const onReactionMessage: (stanza: Element) => Promise<void>;
6
6
  declare const onDeleteMessage: (stanza: Element) => Promise<void>;
7
+ declare const onReceivedReceipt: (stanza: Element) => Promise<void>;
8
+ declare const onDisplayedMarker: (stanza: Element) => Promise<void>;
7
9
  declare const onEditMessage: (stanza: Element) => Promise<void>;
8
10
  declare const onReactionHistory: (stanza: any) => Promise<void>;
9
- declare const onMessageHistory: (stanza: any) => Promise<void>;
11
+ declare const onMessageHistory: (stanza: any, xmppClient?: XmppClient) => Promise<void>;
10
12
  declare const handleComposing: (stanza: Element, currentUser: string) => Promise<void>;
11
13
  declare const onPresenceInRoom: (stanza: Element | any) => void;
12
14
  declare const onChatInvite: (stanza: Element, client: XmppClient) => Promise<void>;
@@ -29,6 +31,6 @@ export type XMPPErrorInfo = {
29
31
  message: string;
30
32
  };
31
33
  export declare const handleErrorMessageStanza: (stanza: Element) => XMPPErrorInfo | null;
32
- declare const onUserUpdate: (stanza: Element) => Promise<void>;
33
- declare const onChatUpdate: (stanza: Element) => Promise<void>;
34
- export { onRealtimeMessage, onMessageHistory, onPresenceInRoom, onReactionHistory, onGetLastMessageArchive, handleComposing, onGetChatRooms, onNewRoomCreated, onGetMembers, onGetRoomInfo, onDeleteMessage, onEditMessage, onReactionMessage, onChatInvite, onRoomKicked, onRoomMembershipChange, onMembersRefreshSignal, onMessageError, onUserUpdate, onChatUpdate, };
34
+ declare const onUserUpdate: (stanza: Element, xmppWs?: XmppClient) => Promise<void>;
35
+ declare const onChatUpdate: (stanza: Element, xmppWs?: XmppClient) => Promise<void>;
36
+ export { onRealtimeMessage, onMessageHistory, onPresenceInRoom, onReactionHistory, onGetLastMessageArchive, handleComposing, onGetChatRooms, onNewRoomCreated, onGetMembers, onGetRoomInfo, onDeleteMessage, onEditMessage, onReactionMessage, onReceivedReceipt, onDisplayedMarker, onChatInvite, onRoomKicked, onRoomMembershipChange, onMembersRefreshSignal, onMessageError, onUserUpdate, onChatUpdate, };
@@ -11,4 +11,21 @@ export interface AllRoomPresenceSummary {
11
11
  }>;
12
12
  }
13
13
  export type RoomJoiner = (roomJid: string) => Promise<boolean>;
14
- export declare function allRoomPresences(client: Client, join?: RoomJoiner): Promise<AllRoomPresenceSummary>;
14
+ export interface AllRoomPresencesOptions {
15
+ /** Parallel joins. Default 5. */
16
+ concurrency?: number;
17
+ /** Pause after each join, per worker. Default 30 ms. */
18
+ pacingMs?: number;
19
+ /** Rooms the connection already joined: skipped, no round trip. */
20
+ isJoined?: (roomJid: string) => boolean;
21
+ /** The room the user has open: always joined next, ahead of the queue. */
22
+ getActiveRoomJid?: () => string | null | undefined;
23
+ /** Higher = joined earlier (recent activity). Default: store order. */
24
+ rank?: (roomJid: string) => number;
25
+ /** True once this sweep is obsolete (connection dropped and replaced). */
26
+ isCancelled?: () => boolean;
27
+ /** Fired once the first wave (the active room plus one pool-width of the
28
+ * most recent rooms) has settled, long before the rest of the sweep. */
29
+ onPriorityDone?: () => void;
30
+ }
31
+ export declare function allRoomPresences(client: Client, join?: RoomJoiner, options?: AllRoomPresencesOptions): Promise<AllRoomPresenceSummary>;
@@ -1,3 +1,3 @@
1
1
  import { Client } from '@xmpp/client';
2
2
  import { Element } from '@xmpp/xml';
3
- export declare const presenceInRoom: (client: Client, roomJID: string, delay?: number, timeoutMs?: number) => Promise<Element>;
3
+ export declare const presenceInRoom: (client: Client, roomJID: string, delay?: number, timeoutMs?: number, historyStanzas?: number) => Promise<Element>;
@@ -0,0 +1,2 @@
1
+ import { Client } from '@xmpp/client';
2
+ export declare function sendDisplayedMarker(client: Client, messageId: string, roomJid: string): void;
@@ -1,4 +1,5 @@
1
1
  import { Client } from '@xmpp/client';
2
+ import { IMentionSpan } from '../../types/types';
2
3
  /**
3
4
  * What <body> carries in an e2ee room: the key material for each sealed
4
5
  * attachment, in the same order as `attachments` / the flat fields on <data>.
@@ -9,5 +10,12 @@ import { Client } from '@xmpp/client';
9
10
  export interface E2eeMediaBody {
10
11
  v: 1;
11
12
  keys: string[];
13
+ /**
14
+ * The text typed with the files. It lives here, inside the envelope, and
15
+ * nowhere on <data>, so an e2ee caption is never readable in transit.
16
+ * Older readers only look at `keys` and ignore the rest.
17
+ */
18
+ caption?: string;
19
+ captionMentions?: IMentionSpan[];
12
20
  }
13
21
  export declare function sendMediaMessage(client: Client, roomJID: string, data: any, id: string): Promise<void>;
@@ -0,0 +1,2 @@
1
+ import { Client } from '@xmpp/client';
2
+ export declare function sendReceivedReceipt(client: Client, messageId: string, roomJid: string): void;
@@ -2,6 +2,18 @@ import { Client } from '@xmpp/client';
2
2
  import { Iso639_1Codes, XmppClientInterface, xmppSettingsInterface, IMessage, IMentionSpan } from '../types/types';
3
3
  import { AllRoomPresenceSummary } from './xmpp/allRoomPresences.xmpp';
4
4
  type HistorySource = 'active' | 'send_ack' | 'background' | 'default';
5
+ /** What the server's <fin> said about one windowed MAM page. */
6
+ export interface HistoryWindowFin {
7
+ complete: boolean;
8
+ first: number | null;
9
+ last: number | null;
10
+ }
11
+ /** One page of a windowed history query, returned instead of stored. */
12
+ export interface HistoryWindowPage extends HistoryWindowFin {
13
+ ok: boolean;
14
+ /** Ascending by time (oldest first), whichever direction was paged. */
15
+ messages: IMessage[];
16
+ }
5
17
  export type XmppCredentialsProvider = () => Promise<{
6
18
  username: string;
7
19
  password: string;
@@ -24,6 +36,12 @@ export declare class XmppClient implements XmppClientInterface {
24
36
  private reconnecting;
25
37
  private reconnectPromise;
26
38
  presencesReady: boolean;
39
+ priorityPresencesReady: boolean;
40
+ private sweepEpoch;
41
+ private sweepRetryTimer;
42
+ private sweepRetryRunning;
43
+ private static readonly SWEEP_RETRY_DELAYS_MS;
44
+ private joinConcurrency;
27
45
  private sendAllPresencesInFlight;
28
46
  private connectionSteps;
29
47
  private messageQueue;
@@ -46,11 +64,16 @@ export declare class XmppClient implements XmppClientInterface {
46
64
  private historyQueueWorkerScheduled;
47
65
  private historyQueueWorkerTimer;
48
66
  private roomHistoryEpoch;
67
+ private historyReceiptAckedIds;
68
+ private readonly maxHistoryReceiptAckedIds;
49
69
  private criticalSendUntil;
50
70
  private activeRoomBoostUntil;
51
71
  private sendStartedAtById;
72
+ private sessionOnlineAt;
73
+ private lastSentAtById;
52
74
  private pendingSendIdsByRoom;
53
75
  private maxInFlightHistory;
76
+ private joinHistoryStanzas;
54
77
  private softPauseAfterSendMs;
55
78
  private activeRoomBoostTtlMs;
56
79
  private activeSendBoostMs;
@@ -183,13 +206,21 @@ export declare class XmppClient implements XmppClientInterface {
183
206
  private startAdaptivePing;
184
207
  private markActivity;
185
208
  sendAllPresencesAndMarkReady(): Promise<void>;
209
+ private hasUnjoinedRooms;
186
210
  private runAllPresencesSweep;
211
+ private clearSweepRetry;
212
+ private nextSweepRetryAt;
213
+ private scheduleSweepRetry;
214
+ private runSweepRetry;
187
215
  reconnect(): Promise<void>;
188
- allRoomPresencesStanza(): Promise<AllRoomPresenceSummary>;
216
+ allRoomPresencesStanza(epoch?: number): Promise<AllRoomPresenceSummary>;
189
217
  ensureConnected(timeout?: number): Promise<void>;
190
218
  close(): Promise<void>;
191
219
  wrapWithConnectionCheck<T>(operation: () => Promise<T>): Promise<T>;
192
220
  private clearMamRegistry;
221
+ private static readonly HISTORY_WATCHDOG_MS;
222
+ private historyQueueGeneration;
223
+ private abandonMamRequest;
193
224
  private clearHistoryQueue;
194
225
  private getSendLane;
195
226
  setActiveRoomJid(roomJID: string | null): void;
@@ -210,6 +241,16 @@ export declare class XmppClient implements XmppClientInterface {
210
241
  private pickNextHistoryTask;
211
242
  private processHistoryQueue;
212
243
  private executeHistoryTask;
244
+ /**
245
+ * Writes one archive page into the store. A LATEST page (no `before`)
246
+ * that is full and starts after everything the room has cached means more
247
+ * than a page arrived while this client was away: merging it would leave a
248
+ * silent hole between the cache and the page, so the cache is replaced
249
+ * (the same "cache reset" the startup catch-up does when its anchor is
250
+ * missing). Anything overlapping the cache is merged as before.
251
+ */
252
+ private storeHistoryPage;
253
+ private clearPreloadError;
213
254
  private parseMamMessages;
214
255
  private routeMamStanza;
215
256
  private requestMamHistory;
@@ -247,7 +288,22 @@ export declare class XmppClient implements XmppClientInterface {
247
288
  coalesceRoom?: boolean;
248
289
  skipIfPreloaded?: boolean;
249
290
  source?: HistorySource;
250
- }) => Promise<any>;
291
+ reissued?: boolean;
292
+ }) => Promise<IMessage[] | undefined>;
293
+ private guardHistoryRequest;
294
+ /**
295
+ * One page of archive for a window around a message, RETURNED rather than
296
+ * stored: no setRoomMessages, no messageStats / historyComplete update, so
297
+ * the live history is untouched. Pass `before` for the page ending just
298
+ * before that archive id, or `after` for the page starting just after it
299
+ * (both exclusive; microsecond MAM ids). Messages come back oldest first.
300
+ */
301
+ getHistoryWindow: (chatJID: string, max: number, cursor: {
302
+ before?: number;
303
+ after?: number;
304
+ start?: string;
305
+ end?: string;
306
+ }) => Promise<HistoryWindowPage>;
251
307
  getLastMessageArchiveStanza(roomJID: string): void;
252
308
  setRoomImageStanza: (roomJid: string, roomThumbnail: string, type: string, roomBackground?: string) => void;
253
309
  getRoomInfoStanza: (roomJID: string) => void;
@@ -259,6 +315,9 @@ export declare class XmppClient implements XmppClientInterface {
259
315
  deleteMessageStanza(room: string, msgId: string): void;
260
316
  editMessageStanza(room: string, msgId: string, text: string): void;
261
317
  sendMessageReactionStanza(messageId: string, roomJid: string, reactionsList: string[], reactionSymbol?: any): void;
318
+ sendReceivedReceiptStanza(messageId: string, roomJid: string): void;
319
+ sendDisplayedMarkerStanza(messageId: string, roomJid: string): void;
320
+ sendHistoryReceivedReceiptStanza(roomJid: string, messageId: string): void;
262
321
  sendTypingRequestStanza(chatId: string, fullName: string, start: boolean): void;
263
322
  getChatsPrivateStoreRequestStanza: () => Promise<Record<string, string | number> | null>;
264
323
  actionSetTimestampToPrivateStoreStanza(chatId: string, timestamp: number, chats?: string[]): Promise<void>;
@@ -0,0 +1,2 @@
1
+ import { Middleware } from '@reduxjs/toolkit';
2
+ export declare const jumpThreadMiddleware: Middleware;
@@ -11,6 +11,8 @@ export interface CallState {
11
11
  callId: string | null;
12
12
  peerXmppUsername: string | null;
13
13
  token: string | null;
14
+ e2eeKey: string | null;
15
+ e2eeKeyCallId: string | null;
14
16
  error: string | null;
15
17
  startedAt: number | null;
16
18
  connectedAt: number | null;
@@ -33,6 +35,9 @@ export declare const startOutgoingCall: import('@reduxjs/toolkit').ActionCreator
33
35
  roomJid: string;
34
36
  token: string;
35
37
  callId?: string | null;
36
- }, "call/setOutgoingCallToken">, acceptIncomingCall: import('@reduxjs/toolkit').ActionCreatorWithoutPayload<"call/acceptIncomingCall">, declineIncomingCall: import('@reduxjs/toolkit').ActionCreatorWithoutPayload<"call/declineIncomingCall">, endCall: import('@reduxjs/toolkit').ActionCreatorWithoutPayload<"call/endCall">, setCallError: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<string, "call/setCallError">, setCallPhase: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<CallPhase, "call/setCallPhase">, setCallKind: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<CallKind, "call/setCallKind">, resetCall: import('@reduxjs/toolkit').ActionCreatorWithoutPayload<"call/resetCall">;
38
+ }, "call/setOutgoingCallToken">, acceptIncomingCall: import('@reduxjs/toolkit').ActionCreatorWithoutPayload<"call/acceptIncomingCall">, declineIncomingCall: import('@reduxjs/toolkit').ActionCreatorWithoutPayload<"call/declineIncomingCall">, endCall: import('@reduxjs/toolkit').ActionCreatorWithoutPayload<"call/endCall">, setCallError: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<string, "call/setCallError">, setCallPhase: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<CallPhase, "call/setCallPhase">, setCallKind: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<CallKind, "call/setCallKind">, setCallE2eeKey: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
39
+ key: string;
40
+ callId: string | null;
41
+ }, "call/setCallE2eeKey">, resetCall: import('@reduxjs/toolkit').ActionCreatorWithoutPayload<"call/resetCall">;
37
42
  declare const _default: import('redux').Reducer<import('immer').WritableDraft<CallState>>;
38
43
  export default _default;
@@ -32,6 +32,28 @@ export declare const optimizePersistedRooms: (rooms: Record<string, IRoom>, char
32
32
  export declare const compactUsersSetForPersist: (usersSet: Record<string, any>) => Record<string, any>;
33
33
  /** Puts back what compactUsersSetForPersist folded into the key. */
34
34
  export declare const restoreUsersSetForRehydrate: (usersSet: Record<string, any>) => Record<string, any>;
35
+ /**
36
+ * Per-session facts about a room that must not survive a reload. A persisted
37
+ * 'done' made the next page load treat the cached transcript as current:
38
+ * the preload sweep skipped the room and opening it returned the cache
39
+ * without asking the server, so anything sent while the page was closed was
40
+ * missing until a manual refetch. 'done'/'loading' go back to
41
+ * 'partial'/'idle' (the room is refetched once per session, and a page that
42
+ * does not overlap the cache replaces it instead of leaving a hole). The
43
+ * API's unread count is likewise a snapshot of that moment: the next
44
+ * /chats/my response re-seeds it.
45
+ */
46
+ export declare const resetSessionRoomState: (roomsMap: Record<string, IRoom>) => Record<string, IRoom>;
47
+ /**
48
+ * A transcript cached by a build that did not persist reactions repaints every
49
+ * bubble without its reaction chips, and the archive will not re-deliver them
50
+ * for the cached middle (the latest page is merged onto the cache and the
51
+ * backfill resumes from the cache's oldest message). Such a cache is dropped
52
+ * once - the room shell stays, the transcript is refetched from MAM - exactly
53
+ * like the cache-budget eviction, except the paging cursors go with it: a
54
+ * stale first/last id would make the backfill start behind the dropped range.
55
+ */
56
+ export declare const dropReactionlessCaches: (roomsMap: Record<string, IRoom>) => Record<string, IRoom>;
35
57
  export declare const limitMessagesTransform: import('redux-persist').Transform<any, any, Record<string, any>, Record<string, any>>;
36
58
  export declare const scrubSensitiveChatStateTransform: import('redux-persist').Transform<any, any, Record<string, any>, Record<string, any>>;
37
59
  export declare const sanitizeRoomsStateTransform: import('redux-persist').Transform<any, any, Record<string, any>, Record<string, any>>;
@@ -1,4 +1,4 @@
1
- import { AddRoomMessageAction, ApiRoom, EditAction, IMessage, IRoom, ReactionAction, RoomMember, Translation } from '../types/types';
1
+ import { AddRoomMessageAction, ApiRoom, EditAction, IAttachment, IMessage, IRoom, ReactionAction, RoomMember, Translation } from '../types/types';
2
2
  import { default as XmppClient } from '../networking/xmppClient';
3
3
  /**
4
4
  * Draft caps. Drafts ride along in the persisted rooms slice, so they have
@@ -44,6 +44,27 @@ export interface PendingJump {
44
44
  /** Epoch ms the request was made, to drop one nobody ever fulfilled. */
45
45
  at: number;
46
46
  }
47
+ /**
48
+ * A short slice of a room's archive around one message, shown INSTEAD of the
49
+ * live list while the reader is away from the tail (a search jump to a message
50
+ * far back). Separate from room.messages on purpose: it is not contiguous with
51
+ * the live history, so it must not touch messageStats or historyComplete.
52
+ */
53
+ export interface JumpWindow {
54
+ roomJID: string;
55
+ /** Ascending by time, like room.messages. */
56
+ messages: IMessage[];
57
+ /** Id of the message the jump named, as it appears in `messages`. */
58
+ targetId: string;
59
+ /** RSM cursor (microseconds) to continue older paging from. */
60
+ olderCursor: number | null;
61
+ hasOlder: boolean;
62
+ /** RSM cursor (microseconds) to continue newer paging from. */
63
+ newerCursor: number | null;
64
+ hasNewer: boolean;
65
+ }
66
+ /** The window is trimmed on the far side past this many messages. */
67
+ export declare const JUMP_WINDOW_MAX_MESSAGES = 300;
47
68
  interface RoomMessagesState {
48
69
  rooms: {
49
70
  [jid: string]: IRoom;
@@ -64,6 +85,7 @@ interface RoomMessagesState {
64
85
  roomsLoadError: boolean;
65
86
  pendingJump: PendingJump | null;
66
87
  archivedMessage: ArchivedMessage | null;
88
+ jumpWindow: JumpWindow | null;
67
89
  joiningRoomJID: string | null;
68
90
  drafts: Record<string, string>;
69
91
  }
@@ -107,7 +129,13 @@ export declare const addRoom: import('@reduxjs/toolkit').ActionCreatorWithOption
107
129
  }, "roomMessages/setMessageSendFailed">, setMessageSendRetrying: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
108
130
  roomJID: string;
109
131
  messageId: string;
110
- }, "roomMessages/setMessageSendRetrying">, editRoomMessage: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
132
+ }, "roomMessages/setMessageSendRetrying">, setMediaUploadState: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
133
+ roomJID: string;
134
+ messageId: string;
135
+ attachments?: IAttachment[];
136
+ pending?: boolean;
137
+ failed?: boolean;
138
+ }, "roomMessages/setMediaUploadState">, editRoomMessage: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
111
139
  roomJID: string;
112
140
  messageId: string;
113
141
  text: string;
@@ -139,7 +167,17 @@ export declare const addRoom: import('@reduxjs/toolkit').ActionCreatorWithOption
139
167
  createdAt?: string;
140
168
  body?: string;
141
169
  preview?: ArchivedMessage;
142
- }, "roomMessages/requestJumpToMessage">, clearPendingJump: import('@reduxjs/toolkit').ActionCreatorWithoutPayload<"roomMessages/clearPendingJump">, showArchivedMessage: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<ArchivedMessage, "roomMessages/showArchivedMessage">, clearArchivedMessage: import('@reduxjs/toolkit').ActionCreatorWithoutPayload<"roomMessages/clearArchivedMessage">, setJoiningRoom: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<string, "roomMessages/setJoiningRoom">, clearJoiningRoom: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<string, "roomMessages/clearJoiningRoom">, setCurrentRoom: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
170
+ }, "roomMessages/requestJumpToMessage">, clearPendingJump: import('@reduxjs/toolkit').ActionCreatorWithoutPayload<"roomMessages/clearPendingJump">, showArchivedMessage: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<ArchivedMessage, "roomMessages/showArchivedMessage">, clearArchivedMessage: import('@reduxjs/toolkit').ActionCreatorWithoutPayload<"roomMessages/clearArchivedMessage">, setJumpWindow: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<JumpWindow, "roomMessages/setJumpWindow">, prependJumpWindowMessages: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
171
+ roomJID: string;
172
+ messages: IMessage[];
173
+ olderCursor: number | null;
174
+ hasOlder: boolean;
175
+ }, "roomMessages/prependJumpWindowMessages">, appendJumpWindowMessages: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
176
+ roomJID: string;
177
+ messages: IMessage[];
178
+ newerCursor: number | null;
179
+ hasNewer: boolean;
180
+ }, "roomMessages/appendJumpWindowMessages">, clearJumpWindow: import('@reduxjs/toolkit').ActionCreatorWithoutPayload<"roomMessages/clearJumpWindow">, setJoiningRoom: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<string, "roomMessages/setJoiningRoom">, clearJoiningRoom: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<string, "roomMessages/clearJoiningRoom">, setCurrentRoom: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
143
181
  roomJID: string | null;
144
182
  }, "roomMessages/setCurrentRoom">, setMemberOnline: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
145
183
  roomJID: string;
@@ -150,7 +188,16 @@ export declare const addRoom: import('@reduxjs/toolkit').ActionCreatorWithOption
150
188
  }, "roomMessages/setMemberOffline">, setChatUiVisible: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<boolean, "roomMessages/setChatUiVisible">, setRoomRole: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
151
189
  chatJID: string;
152
190
  role: string;
153
- }, "roomMessages/setRoomRole">, setReactions: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<ReactionAction, "roomMessages/setReactions">, setLogoutState: import('@reduxjs/toolkit').ActionCreatorWithoutPayload<"roomMessages/setLogoutState">, setActiveMessage: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
191
+ }, "roomMessages/setRoomRole">, setReactions: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<ReactionAction, "roomMessages/setReactions">, setReceivedReceipt: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
192
+ roomJID: string;
193
+ messageId: string;
194
+ fromXmppUsername: string;
195
+ timestamp: number;
196
+ }, "roomMessages/setReceivedReceipt">, setDisplayedMarker: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
197
+ roomJID: string;
198
+ xmppUsername: string;
199
+ messageId: string;
200
+ }, "roomMessages/setDisplayedMarker">, setLogoutState: import('@reduxjs/toolkit').ActionCreatorWithoutPayload<"roomMessages/setLogoutState">, setActiveMessage: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
154
201
  id: string;
155
202
  chatJID: string;
156
203
  }, "roomMessages/setActiveMessage">, setCloseActiveMessage: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
@@ -0,0 +1,12 @@
1
+ /** True when `key` is a non-empty string that is safe to use as an object key. */
2
+ export declare const isSafeKey: (key: unknown) => key is string;
3
+ /** True when `name` is a property name every object already has. */
4
+ export declare const isReservedName: (name: string) => boolean;
5
+ /**
6
+ * True for an `<appId>_<rest>` id that is safe to send and to store: the whole
7
+ * id and every underscore-separated segment after the first must not be a
8
+ * reserved object name, and no segment may start with `__` (which also
9
+ * rejects `<appId>___proto__`, where the split yields an empty segment and a
10
+ * `__`-prefixed one).
11
+ */
12
+ export declare const hasSafeIdSegments: (id: string) => boolean;
@@ -0,0 +1,15 @@
1
+ import { RoomMember } from '../types/types';
2
+ /**
3
+ * usersSet is persisted, and lazily resolved senders keep adding to it, so
4
+ * it needs a ceiling. Object key order is insertion order, so "least
5
+ * recently inserted" is simply the first keys.
6
+ */
7
+ export declare const USERS_SET_CAP = 5000;
8
+ /** Drops the oldest-inserted entries until at most `cap` remain. */
9
+ export declare const capUsersSet: (usersSet: Record<string, RoomMember>, cap?: number) => void;
10
+ /**
11
+ * Merges the members of a fresh /chats/my payload into usersSet: fresh
12
+ * entries win, entries fetched lazily earlier stay. A refreshed key moves to
13
+ * the newest position so the cap evicts genuinely stale ones first.
14
+ */
15
+ export declare const mergeUsersSet: (usersSet: Record<string, RoomMember>, fresh: Record<string, RoomMember>, cap?: number) => void;
@@ -20,7 +20,17 @@ export declare const MOTION_BASE_MS = 220;
20
20
  export declare const fadeIn: import('styled-components/dist/models/Keyframes').default;
21
21
  export declare const fadeInUp: import('styled-components/dist/models/Keyframes').default;
22
22
  export declare const scaleIn: import('styled-components/dist/models/Keyframes').default;
23
- export declare const shimmer: import('styled-components/dist/models/Keyframes').default;
23
+ /** Skeleton timing: one sweep cycle, and the delay before anything shows. */
24
+ export declare const SHIMMER_SWEEP_MS = 2000;
25
+ export declare const SHIMMER_DELAY_MS = 120;
26
+ export declare const SHIMMER_FADE_IN_MS = 240;
27
+ /**
28
+ * The loading band drifts left to right (reading direction). Keyframes move
29
+ * the band's own transform, not background-position, so the browser can run
30
+ * it on the compositor. The band is 60% of the host wide, so -100% parks it
31
+ * just off the left edge and 166.67% just off the right edge.
32
+ */
33
+ export declare const shimmerSweep: import('styled-components/dist/models/Keyframes').default;
24
34
  /**
25
35
  * Right-hand side drawer entrance. Travels further than `fadeInUp` (a panel
26
36
  * sliding in from the screen edge reads as motion only if it actually
@@ -47,7 +57,17 @@ export declare const fadeInAnimation: import('styled-components').RuleSet<object
47
57
  export declare const scaleInAnimation: import('styled-components').RuleSet<object>;
48
58
  /** Convenience: side-drawer slide-in shorthand, respecting reduced motion. */
49
59
  export declare const slideInRightAnimation: import('styled-components').RuleSet<object>;
50
- /** Shimmering skeleton background, respecting reduced motion (stays static). */
60
+ /**
61
+ * Loading skeleton: a calm, slightly tinted base with one soft band sweeping
62
+ * left to right every 2s. The band fades in with its own transparent edges,
63
+ * so there is no hard leading or trailing line. Nothing shows for the first
64
+ * 120ms, so an image that arrives quickly never flashes a placeholder.
65
+ *
66
+ * The host must be positioned (`position: relative` or `absolute`, set by the
67
+ * host itself so this helper cannot override it); the band is an ::after
68
+ * clipped by the host's `overflow: hidden`. Reduced motion keeps only the
69
+ * static base.
70
+ */
51
71
  export declare const shimmerBackground: import('styled-components').RuleSet<object>;
52
72
  export declare const fadeOut: import('styled-components/dist/models/Keyframes').default;
53
73
  export declare const scaleOut: import('styled-components/dist/models/Keyframes').default;
@@ -23,6 +23,9 @@ export interface IAttachment {
23
23
  /** Bytes, as a string - that is how it travels on the wire. */
24
24
  size?: string;
25
25
  duration?: string;
26
+ /** Natural pixel size, when the storage recorded it. Sizes the solo preview box. */
27
+ width?: number;
28
+ height?: number;
26
29
  ownerKey?: string;
27
30
  userId?: string;
28
31
  createdAt?: string;
@@ -30,4 +33,20 @@ export interface IAttachment {
30
33
  expiresAt?: string;
31
34
  isVisible?: boolean;
32
35
  isPrivate?: boolean;
36
+ /**
37
+ * Local-only state of an outgoing attachment, set by the sender while its
38
+ * message is still being sent. Never written to the wire.
39
+ * uploading - the file is going up (or every file is up and the message
40
+ * is waiting on its siblings); `uploadProgress` is 0 to 100.
41
+ * failed - this file did not upload; the message is held until it
42
+ * is retried.
43
+ */
44
+ uploadStatus?: 'uploading' | 'failed';
45
+ uploadProgress?: number;
46
+ /**
47
+ * Local-only `blob:` URL of the sender's own picture, so an outgoing image
48
+ * shows at once and stays on screen while the server copy loads. Never
49
+ * written to the wire.
50
+ */
51
+ localUrl?: string;
33
52
  }