@ethora/chat-component-rn 26.5.7 → 26.5.11

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 (417) hide show
  1. package/CHANGELOG.md +191 -0
  2. package/README.md +95 -10
  3. package/index.js +24 -0
  4. package/lib/commonjs/assets/icons.js +5 -1
  5. package/lib/commonjs/assets/icons.js.map +1 -1
  6. package/lib/commonjs/components/ContextMenu/ContextMenuComponents.js +11 -5
  7. package/lib/commonjs/components/ContextMenu/ContextMenuComponents.js.map +1 -1
  8. package/lib/commonjs/components/MainComponents/ChatHeader.js +31 -16
  9. package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
  10. package/lib/commonjs/components/MainComponents/ChatRoom.js +78 -38
  11. package/lib/commonjs/components/MainComponents/ChatRoom.js.map +1 -1
  12. package/lib/commonjs/components/MainComponents/ChatWrapper.js +74 -27
  13. package/lib/commonjs/components/MainComponents/ChatWrapper.js.map +1 -1
  14. package/lib/commonjs/components/MainComponents/MediaMessage.js +16 -11
  15. package/lib/commonjs/components/MainComponents/MediaMessage.js.map +1 -1
  16. package/lib/commonjs/components/MainComponents/MessageContainer.js +2 -2
  17. package/lib/commonjs/components/MainComponents/MessageContainer.js.map +1 -1
  18. package/lib/commonjs/components/MainComponents/MessageList.js +165 -28
  19. package/lib/commonjs/components/MainComponents/MessageList.js.map +1 -1
  20. package/lib/commonjs/components/MainComponents/ReduxWrapper.js +17 -8
  21. package/lib/commonjs/components/MainComponents/ReduxWrapper.js.map +1 -1
  22. package/lib/commonjs/components/MainComponents/SystemMessage.js +34 -22
  23. package/lib/commonjs/components/MainComponents/SystemMessage.js.map +1 -1
  24. package/lib/commonjs/components/MessageBubble/InteractionsOverlay.js +119 -0
  25. package/lib/commonjs/components/MessageBubble/InteractionsOverlay.js.map +1 -0
  26. package/lib/commonjs/components/MessageBubble/Message.js +122 -41
  27. package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
  28. package/lib/commonjs/components/MessageBubble/MessageInteractions.js +132 -44
  29. package/lib/commonjs/components/MessageBubble/MessageInteractions.js.map +1 -1
  30. package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js +155 -52
  31. package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
  32. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +20 -5
  33. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  34. package/lib/commonjs/components/Modals/FilePreviewModal/DocumentViewer.js +104 -0
  35. package/lib/commonjs/components/Modals/FilePreviewModal/DocumentViewer.js.map +1 -0
  36. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +144 -24
  37. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  38. package/lib/commonjs/components/Modals/FilePreviewModal/PdfView.js +29 -1
  39. package/lib/commonjs/components/Modals/FilePreviewModal/PdfView.js.map +1 -1
  40. package/lib/commonjs/components/Modals/Modal/Modal.js +5 -1
  41. package/lib/commonjs/components/Modals/Modal/Modal.js.map +1 -1
  42. package/lib/commonjs/components/Modals/ModalHeaderComponent.js +10 -0
  43. package/lib/commonjs/components/Modals/ModalHeaderComponent.js.map +1 -1
  44. package/lib/commonjs/components/Modals/ModalWrapper/ModalWrapper.js +77 -47
  45. package/lib/commonjs/components/Modals/ModalWrapper/ModalWrapper.js.map +1 -1
  46. package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js +3 -3
  47. package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
  48. package/lib/commonjs/components/Modals/styledModalComponents.js +17 -13
  49. package/lib/commonjs/components/Modals/styledModalComponents.js.map +1 -1
  50. package/lib/commonjs/components/styled/MediaFilePreview.js +47 -28
  51. package/lib/commonjs/components/styled/MediaFilePreview.js.map +1 -1
  52. package/lib/commonjs/components/styled/MessageImage.js +49 -9
  53. package/lib/commonjs/components/styled/MessageImage.js.map +1 -1
  54. package/lib/commonjs/components/styled/NewMessageLabel.js +8 -6
  55. package/lib/commonjs/components/styled/NewMessageLabel.js.map +1 -1
  56. package/lib/commonjs/components/styled/SendInput.js +49 -27
  57. package/lib/commonjs/components/styled/SendInput.js.map +1 -1
  58. package/lib/commonjs/components/styled/StyledComponents.js +6 -3
  59. package/lib/commonjs/components/styled/StyledComponents.js.map +1 -1
  60. package/lib/commonjs/components/styled/StyledInputComponents/MediaComponents.js +14 -4
  61. package/lib/commonjs/components/styled/StyledInputComponents/MediaComponents.js.map +1 -1
  62. package/lib/commonjs/components/styled/StyledInputComponents/StyledInputComponents.js +7 -2
  63. package/lib/commonjs/components/styled/StyledInputComponents/StyledInputComponents.js.map +1 -1
  64. package/lib/commonjs/components/styled/UnsupportedType.js +5 -1
  65. package/lib/commonjs/components/styled/UnsupportedType.js.map +1 -1
  66. package/lib/commonjs/components/styled/VideoMessage.js +81 -16
  67. package/lib/commonjs/components/styled/VideoMessage.js.map +1 -1
  68. package/lib/commonjs/context/MessageNotificationContext.js +7 -7
  69. package/lib/commonjs/context/MessageNotificationContext.js.map +1 -1
  70. package/lib/commonjs/context/xmppProvider.js +244 -32
  71. package/lib/commonjs/context/xmppProvider.js.map +1 -1
  72. package/lib/commonjs/helpers/chatAutoEnterer.js +2 -7
  73. package/lib/commonjs/helpers/chatAutoEnterer.js.map +1 -1
  74. package/lib/commonjs/helpers/historyPreloadScheduler.js +4 -1
  75. package/lib/commonjs/helpers/historyPreloadScheduler.js.map +1 -1
  76. package/lib/commonjs/helpers/insertMessageWithDelimiter.js +15 -5
  77. package/lib/commonjs/helpers/insertMessageWithDelimiter.js.map +1 -1
  78. package/lib/commonjs/helpers/keyboardLayout.js +18 -0
  79. package/lib/commonjs/helpers/keyboardLayout.js.map +1 -0
  80. package/lib/commonjs/helpers/mediaDimensions.js +51 -0
  81. package/lib/commonjs/helpers/mediaDimensions.js.map +1 -0
  82. package/lib/commonjs/helpers/mimeToExtension.js +56 -0
  83. package/lib/commonjs/helpers/mimeToExtension.js.map +1 -1
  84. package/lib/commonjs/helpers/normalizeRoomJid.js +20 -0
  85. package/lib/commonjs/helpers/normalizeRoomJid.js.map +1 -0
  86. package/lib/commonjs/hooks/useChatRoomFocus.js +87 -0
  87. package/lib/commonjs/hooks/useChatRoomFocus.js.map +1 -0
  88. package/lib/commonjs/hooks/useHeapSender.js +1 -1
  89. package/lib/commonjs/hooks/useHeapSender.js.map +1 -1
  90. package/lib/commonjs/hooks/useLogout.js +36 -11
  91. package/lib/commonjs/hooks/useLogout.js.map +1 -1
  92. package/lib/commonjs/hooks/usePushNotifications.js +6 -2
  93. package/lib/commonjs/hooks/usePushNotifications.js.map +1 -1
  94. package/lib/commonjs/hooks/useRoomState.js +2 -0
  95. package/lib/commonjs/hooks/useRoomState.js.map +1 -1
  96. package/lib/commonjs/hooks/useSendMessage.js +201 -10
  97. package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
  98. package/lib/commonjs/hooks/useUnreadMessagesCounter.js +12 -14
  99. package/lib/commonjs/hooks/useUnreadMessagesCounter.js.map +1 -1
  100. package/lib/commonjs/main.js +7 -0
  101. package/lib/commonjs/main.js.map +1 -1
  102. package/lib/commonjs/networking/api-requests/auth.api.js +67 -0
  103. package/lib/commonjs/networking/api-requests/auth.api.js.map +1 -1
  104. package/lib/commonjs/networking/api-requests/rooms.api.js +29 -4
  105. package/lib/commonjs/networking/api-requests/rooms.api.js.map +1 -1
  106. package/lib/commonjs/networking/apiClient.js +2 -1
  107. package/lib/commonjs/networking/apiClient.js.map +1 -1
  108. package/lib/commonjs/networking/outboundQueue.js +92 -0
  109. package/lib/commonjs/networking/outboundQueue.js.map +1 -0
  110. package/lib/commonjs/networking/xmpp/createTimeoutPromise.xmpp.js +4 -1
  111. package/lib/commonjs/networking/xmpp/createTimeoutPromise.xmpp.js.map +1 -1
  112. package/lib/commonjs/networking/xmpp/flushLastViewedToPrivateStore.js +7 -8
  113. package/lib/commonjs/networking/xmpp/flushLastViewedToPrivateStore.js.map +1 -1
  114. package/lib/commonjs/networking/xmpp/getRoomsPaged.xmpp.js +26 -3
  115. package/lib/commonjs/networking/xmpp/getRoomsPaged.xmpp.js.map +1 -1
  116. package/lib/commonjs/networking/xmpp/presenceInRoom.xmpp.js +26 -8
  117. package/lib/commonjs/networking/xmpp/presenceInRoom.xmpp.js.map +1 -1
  118. package/lib/commonjs/networking/xmpp/sendTextMessage.xmpp.js +2 -1
  119. package/lib/commonjs/networking/xmpp/sendTextMessage.xmpp.js.map +1 -1
  120. package/lib/commonjs/networking/xmppClient.js +294 -35
  121. package/lib/commonjs/networking/xmppClient.js.map +1 -1
  122. package/lib/commonjs/roomStore/Middleware/unreadMidlleware.js +22 -6
  123. package/lib/commonjs/roomStore/Middleware/unreadMidlleware.js.map +1 -1
  124. package/lib/commonjs/roomStore/roomsSlice.js +157 -23
  125. package/lib/commonjs/roomStore/roomsSlice.js.map +1 -1
  126. package/lib/commonjs/utils/installPromiseRejectionTracker.js +83 -0
  127. package/lib/commonjs/utils/installPromiseRejectionTracker.js.map +1 -0
  128. package/lib/commonjs/utils/resendMessage.js +1 -1
  129. package/lib/commonjs/utils/resendMessage.js.map +1 -1
  130. package/lib/module/assets/icons.js +5 -1
  131. package/lib/module/assets/icons.js.map +1 -1
  132. package/lib/module/components/ContextMenu/ContextMenuComponents.js +11 -5
  133. package/lib/module/components/ContextMenu/ContextMenuComponents.js.map +1 -1
  134. package/lib/module/components/MainComponents/ChatHeader.js +31 -16
  135. package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
  136. package/lib/module/components/MainComponents/ChatRoom.js +80 -40
  137. package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
  138. package/lib/module/components/MainComponents/ChatWrapper.js +74 -27
  139. package/lib/module/components/MainComponents/ChatWrapper.js.map +1 -1
  140. package/lib/module/components/MainComponents/MediaMessage.js +17 -12
  141. package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
  142. package/lib/module/components/MainComponents/MessageContainer.js +2 -2
  143. package/lib/module/components/MainComponents/MessageContainer.js.map +1 -1
  144. package/lib/module/components/MainComponents/MessageList.js +166 -29
  145. package/lib/module/components/MainComponents/MessageList.js.map +1 -1
  146. package/lib/module/components/MainComponents/ReduxWrapper.js +18 -8
  147. package/lib/module/components/MainComponents/ReduxWrapper.js.map +1 -1
  148. package/lib/module/components/MainComponents/SystemMessage.js +33 -21
  149. package/lib/module/components/MainComponents/SystemMessage.js.map +1 -1
  150. package/lib/module/components/MessageBubble/InteractionsOverlay.js +112 -0
  151. package/lib/module/components/MessageBubble/InteractionsOverlay.js.map +1 -0
  152. package/lib/module/components/MessageBubble/Message.js +123 -42
  153. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  154. package/lib/module/components/MessageBubble/MessageInteractions.js +134 -46
  155. package/lib/module/components/MessageBubble/MessageInteractions.js.map +1 -1
  156. package/lib/module/components/Modals/AttachSheet/AttachSheet.js +155 -52
  157. package/lib/module/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
  158. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +20 -5
  159. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  160. package/lib/module/components/Modals/FilePreviewModal/DocumentViewer.js +99 -0
  161. package/lib/module/components/Modals/FilePreviewModal/DocumentViewer.js.map +1 -0
  162. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +148 -26
  163. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  164. package/lib/module/components/Modals/FilePreviewModal/PdfView.js +31 -3
  165. package/lib/module/components/Modals/FilePreviewModal/PdfView.js.map +1 -1
  166. package/lib/module/components/Modals/Modal/Modal.js +5 -1
  167. package/lib/module/components/Modals/Modal/Modal.js.map +1 -1
  168. package/lib/module/components/Modals/ModalHeaderComponent.js +10 -0
  169. package/lib/module/components/Modals/ModalHeaderComponent.js.map +1 -1
  170. package/lib/module/components/Modals/ModalWrapper/ModalWrapper.js +79 -49
  171. package/lib/module/components/Modals/ModalWrapper/ModalWrapper.js.map +1 -1
  172. package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js +3 -3
  173. package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
  174. package/lib/module/components/Modals/styledModalComponents.js +17 -13
  175. package/lib/module/components/Modals/styledModalComponents.js.map +1 -1
  176. package/lib/module/components/styled/MediaFilePreview.js +47 -28
  177. package/lib/module/components/styled/MediaFilePreview.js.map +1 -1
  178. package/lib/module/components/styled/MessageImage.js +51 -11
  179. package/lib/module/components/styled/MessageImage.js.map +1 -1
  180. package/lib/module/components/styled/NewMessageLabel.js +8 -6
  181. package/lib/module/components/styled/NewMessageLabel.js.map +1 -1
  182. package/lib/module/components/styled/SendInput.js +50 -28
  183. package/lib/module/components/styled/SendInput.js.map +1 -1
  184. package/lib/module/components/styled/StyledComponents.js +6 -3
  185. package/lib/module/components/styled/StyledComponents.js.map +1 -1
  186. package/lib/module/components/styled/StyledInputComponents/MediaComponents.js +14 -4
  187. package/lib/module/components/styled/StyledInputComponents/MediaComponents.js.map +1 -1
  188. package/lib/module/components/styled/StyledInputComponents/StyledInputComponents.js +7 -2
  189. package/lib/module/components/styled/StyledInputComponents/StyledInputComponents.js.map +1 -1
  190. package/lib/module/components/styled/UnsupportedType.js +5 -1
  191. package/lib/module/components/styled/UnsupportedType.js.map +1 -1
  192. package/lib/module/components/styled/VideoMessage.js +82 -17
  193. package/lib/module/components/styled/VideoMessage.js.map +1 -1
  194. package/lib/module/context/MessageNotificationContext.js +7 -7
  195. package/lib/module/context/MessageNotificationContext.js.map +1 -1
  196. package/lib/module/context/xmppProvider.js +246 -34
  197. package/lib/module/context/xmppProvider.js.map +1 -1
  198. package/lib/module/helpers/chatAutoEnterer.js +2 -7
  199. package/lib/module/helpers/chatAutoEnterer.js.map +1 -1
  200. package/lib/module/helpers/historyPreloadScheduler.js +4 -1
  201. package/lib/module/helpers/historyPreloadScheduler.js.map +1 -1
  202. package/lib/module/helpers/insertMessageWithDelimiter.js +15 -5
  203. package/lib/module/helpers/insertMessageWithDelimiter.js.map +1 -1
  204. package/lib/module/helpers/keyboardLayout.js +12 -0
  205. package/lib/module/helpers/keyboardLayout.js.map +1 -0
  206. package/lib/module/helpers/mediaDimensions.js +46 -0
  207. package/lib/module/helpers/mediaDimensions.js.map +1 -0
  208. package/lib/module/helpers/mimeToExtension.js +55 -0
  209. package/lib/module/helpers/mimeToExtension.js.map +1 -1
  210. package/lib/module/helpers/normalizeRoomJid.js +15 -0
  211. package/lib/module/helpers/normalizeRoomJid.js.map +1 -0
  212. package/lib/module/hooks/useChatRoomFocus.js +82 -0
  213. package/lib/module/hooks/useChatRoomFocus.js.map +1 -0
  214. package/lib/module/hooks/useHeapSender.js +1 -1
  215. package/lib/module/hooks/useHeapSender.js.map +1 -1
  216. package/lib/module/hooks/useLogout.js +36 -11
  217. package/lib/module/hooks/useLogout.js.map +1 -1
  218. package/lib/module/hooks/usePushNotifications.js +6 -2
  219. package/lib/module/hooks/usePushNotifications.js.map +1 -1
  220. package/lib/module/hooks/useRoomState.js +2 -0
  221. package/lib/module/hooks/useRoomState.js.map +1 -1
  222. package/lib/module/hooks/useSendMessage.js +203 -12
  223. package/lib/module/hooks/useSendMessage.js.map +1 -1
  224. package/lib/module/hooks/useUnreadMessagesCounter.js +12 -14
  225. package/lib/module/hooks/useUnreadMessagesCounter.js.map +1 -1
  226. package/lib/module/main.js +1 -0
  227. package/lib/module/main.js.map +1 -1
  228. package/lib/module/networking/api-requests/auth.api.js +67 -1
  229. package/lib/module/networking/api-requests/auth.api.js.map +1 -1
  230. package/lib/module/networking/api-requests/rooms.api.js +29 -4
  231. package/lib/module/networking/api-requests/rooms.api.js.map +1 -1
  232. package/lib/module/networking/apiClient.js +2 -1
  233. package/lib/module/networking/apiClient.js.map +1 -1
  234. package/lib/module/networking/outboundQueue.js +84 -0
  235. package/lib/module/networking/outboundQueue.js.map +1 -0
  236. package/lib/module/networking/xmpp/createTimeoutPromise.xmpp.js +4 -1
  237. package/lib/module/networking/xmpp/createTimeoutPromise.xmpp.js.map +1 -1
  238. package/lib/module/networking/xmpp/flushLastViewedToPrivateStore.js +7 -8
  239. package/lib/module/networking/xmpp/flushLastViewedToPrivateStore.js.map +1 -1
  240. package/lib/module/networking/xmpp/getRoomsPaged.xmpp.js +26 -3
  241. package/lib/module/networking/xmpp/getRoomsPaged.xmpp.js.map +1 -1
  242. package/lib/module/networking/xmpp/presenceInRoom.xmpp.js +26 -8
  243. package/lib/module/networking/xmpp/presenceInRoom.xmpp.js.map +1 -1
  244. package/lib/module/networking/xmpp/sendTextMessage.xmpp.js +2 -1
  245. package/lib/module/networking/xmpp/sendTextMessage.xmpp.js.map +1 -1
  246. package/lib/module/networking/xmppClient.js +294 -35
  247. package/lib/module/networking/xmppClient.js.map +1 -1
  248. package/lib/module/roomStore/Middleware/unreadMidlleware.js +22 -6
  249. package/lib/module/roomStore/Middleware/unreadMidlleware.js.map +1 -1
  250. package/lib/module/roomStore/roomsSlice.js +154 -22
  251. package/lib/module/roomStore/roomsSlice.js.map +1 -1
  252. package/lib/module/utils/installPromiseRejectionTracker.js +78 -0
  253. package/lib/module/utils/installPromiseRejectionTracker.js.map +1 -0
  254. package/lib/module/utils/resendMessage.js +1 -1
  255. package/lib/module/utils/resendMessage.js.map +1 -1
  256. package/lib/typescript/assets/icons.d.ts +8 -4
  257. package/lib/typescript/assets/icons.d.ts.map +1 -1
  258. package/lib/typescript/components/ContextMenu/ContextMenuComponents.d.ts.map +1 -1
  259. package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
  260. package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
  261. package/lib/typescript/components/MainComponents/ChatWrapper.d.ts.map +1 -1
  262. package/lib/typescript/components/MainComponents/MediaMessage.d.ts +1 -0
  263. package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
  264. package/lib/typescript/components/MainComponents/MessageContainer.d.ts.map +1 -1
  265. package/lib/typescript/components/MainComponents/MessageList.d.ts.map +1 -1
  266. package/lib/typescript/components/MainComponents/ReduxWrapper.d.ts +1 -0
  267. package/lib/typescript/components/MainComponents/ReduxWrapper.d.ts.map +1 -1
  268. package/lib/typescript/components/MainComponents/SystemMessage.d.ts +0 -7
  269. package/lib/typescript/components/MainComponents/SystemMessage.d.ts.map +1 -1
  270. package/lib/typescript/components/MessageBubble/InteractionsOverlay.d.ts +13 -0
  271. package/lib/typescript/components/MessageBubble/InteractionsOverlay.d.ts.map +1 -0
  272. package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
  273. package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts +4 -2
  274. package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts.map +1 -1
  275. package/lib/typescript/components/Modals/AttachSheet/AttachSheet.d.ts.map +1 -1
  276. package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
  277. package/lib/typescript/components/Modals/FilePreviewModal/DocumentViewer.d.ts +29 -0
  278. package/lib/typescript/components/Modals/FilePreviewModal/DocumentViewer.d.ts.map +1 -0
  279. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts +0 -1
  280. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
  281. package/lib/typescript/components/Modals/FilePreviewModal/PdfView.d.ts.map +1 -1
  282. package/lib/typescript/components/Modals/Modal/Modal.d.ts.map +1 -1
  283. package/lib/typescript/components/Modals/ModalHeaderComponent.d.ts.map +1 -1
  284. package/lib/typescript/components/Modals/ModalWrapper/ModalWrapper.d.ts.map +1 -1
  285. package/lib/typescript/components/Modals/UserProfileModal/UserProfileModal.d.ts.map +1 -1
  286. package/lib/typescript/components/Modals/styledModalComponents.d.ts.map +1 -1
  287. package/lib/typescript/components/styled/MediaFilePreview.d.ts.map +1 -1
  288. package/lib/typescript/components/styled/MessageImage.d.ts.map +1 -1
  289. package/lib/typescript/components/styled/NewMessageLabel.d.ts +1 -5
  290. package/lib/typescript/components/styled/NewMessageLabel.d.ts.map +1 -1
  291. package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
  292. package/lib/typescript/components/styled/StyledComponents.d.ts.map +1 -1
  293. package/lib/typescript/components/styled/StyledInputComponents/MediaComponents.d.ts +9 -1
  294. package/lib/typescript/components/styled/StyledInputComponents/MediaComponents.d.ts.map +1 -1
  295. package/lib/typescript/components/styled/StyledInputComponents/StyledInputComponents.d.ts.map +1 -1
  296. package/lib/typescript/components/styled/UnsupportedType.d.ts +1 -0
  297. package/lib/typescript/components/styled/UnsupportedType.d.ts.map +1 -1
  298. package/lib/typescript/components/styled/VideoMessage.d.ts.map +1 -1
  299. package/lib/typescript/context/xmppProvider.d.ts +1 -0
  300. package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
  301. package/lib/typescript/helpers/chatAutoEnterer.d.ts.map +1 -1
  302. package/lib/typescript/helpers/historyPreloadScheduler.d.ts.map +1 -1
  303. package/lib/typescript/helpers/insertMessageWithDelimiter.d.ts.map +1 -1
  304. package/lib/typescript/helpers/keyboardLayout.d.ts +9 -0
  305. package/lib/typescript/helpers/keyboardLayout.d.ts.map +1 -0
  306. package/lib/typescript/helpers/mediaDimensions.d.ts +9 -0
  307. package/lib/typescript/helpers/mediaDimensions.d.ts.map +1 -0
  308. package/lib/typescript/helpers/mimeToExtension.d.ts +8 -0
  309. package/lib/typescript/helpers/mimeToExtension.d.ts.map +1 -1
  310. package/lib/typescript/helpers/normalizeRoomJid.d.ts +2 -0
  311. package/lib/typescript/helpers/normalizeRoomJid.d.ts.map +1 -0
  312. package/lib/typescript/hooks/useChatRoomFocus.d.ts +42 -0
  313. package/lib/typescript/hooks/useChatRoomFocus.d.ts.map +1 -0
  314. package/lib/typescript/hooks/useHeapSender.d.ts.map +1 -1
  315. package/lib/typescript/hooks/useLogout.d.ts +23 -1
  316. package/lib/typescript/hooks/useLogout.d.ts.map +1 -1
  317. package/lib/typescript/hooks/usePushNotifications.d.ts.map +1 -1
  318. package/lib/typescript/hooks/useRoomState.d.ts +1 -0
  319. package/lib/typescript/hooks/useRoomState.d.ts.map +1 -1
  320. package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
  321. package/lib/typescript/hooks/useUnreadMessagesCounter.d.ts.map +1 -1
  322. package/lib/typescript/main.d.ts +1 -0
  323. package/lib/typescript/main.d.ts.map +1 -1
  324. package/lib/typescript/networking/api-requests/auth.api.d.ts +3 -0
  325. package/lib/typescript/networking/api-requests/auth.api.d.ts.map +1 -1
  326. package/lib/typescript/networking/api-requests/rooms.api.d.ts.map +1 -1
  327. package/lib/typescript/networking/apiClient.d.ts.map +1 -1
  328. package/lib/typescript/networking/outboundQueue.d.ts +57 -0
  329. package/lib/typescript/networking/outboundQueue.d.ts.map +1 -0
  330. package/lib/typescript/networking/xmpp/createTimeoutPromise.xmpp.d.ts.map +1 -1
  331. package/lib/typescript/networking/xmpp/flushLastViewedToPrivateStore.d.ts +1 -1
  332. package/lib/typescript/networking/xmpp/flushLastViewedToPrivateStore.d.ts.map +1 -1
  333. package/lib/typescript/networking/xmpp/getRoomsPaged.xmpp.d.ts +1 -1
  334. package/lib/typescript/networking/xmpp/getRoomsPaged.xmpp.d.ts.map +1 -1
  335. package/lib/typescript/networking/xmpp/presenceInRoom.xmpp.d.ts.map +1 -1
  336. package/lib/typescript/networking/xmpp/sendTextMessage.xmpp.d.ts.map +1 -1
  337. package/lib/typescript/networking/xmppClient.d.ts +33 -4
  338. package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
  339. package/lib/typescript/roomStore/Middleware/unreadMidlleware.d.ts.map +1 -1
  340. package/lib/typescript/roomStore/roomsSlice.d.ts +8 -1
  341. package/lib/typescript/roomStore/roomsSlice.d.ts.map +1 -1
  342. package/lib/typescript/types/models/media.model.d.ts +1 -0
  343. package/lib/typescript/types/models/media.model.d.ts.map +1 -1
  344. package/lib/typescript/types/models/xmpp.model.d.ts +1 -1
  345. package/lib/typescript/types/models/xmpp.model.d.ts.map +1 -1
  346. package/lib/typescript/types/types.d.ts +20 -0
  347. package/lib/typescript/types/types.d.ts.map +1 -1
  348. package/lib/typescript/utils/installPromiseRejectionTracker.d.ts +20 -0
  349. package/lib/typescript/utils/installPromiseRejectionTracker.d.ts.map +1 -0
  350. package/lib/typescript/utils/resendMessage.d.ts.map +1 -1
  351. package/package.json +23 -4
  352. package/src/assets/icons.tsx +23 -14
  353. package/src/components/ContextMenu/ContextMenuComponents.tsx +10 -5
  354. package/src/components/MainComponents/ChatHeader.tsx +46 -15
  355. package/src/components/MainComponents/ChatRoom.tsx +95 -66
  356. package/src/components/MainComponents/ChatWrapper.tsx +127 -42
  357. package/src/components/MainComponents/MediaMessage.tsx +14 -8
  358. package/src/components/MainComponents/MessageContainer.tsx +4 -2
  359. package/src/components/MainComponents/MessageList.tsx +175 -28
  360. package/src/components/MainComponents/ReduxWrapper.tsx +19 -9
  361. package/src/components/MainComponents/SystemMessage.tsx +38 -26
  362. package/src/components/MessageBubble/InteractionsOverlay.tsx +121 -0
  363. package/src/components/MessageBubble/Message.tsx +133 -63
  364. package/src/components/MessageBubble/MessageInteractions.tsx +146 -58
  365. package/src/components/Modals/AttachSheet/AttachSheet.tsx +146 -39
  366. package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +25 -3
  367. package/src/components/Modals/FilePreviewModal/DocumentViewer.tsx +93 -0
  368. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +161 -26
  369. package/src/components/Modals/FilePreviewModal/PdfView.tsx +32 -4
  370. package/src/components/Modals/Modal/Modal.tsx +4 -1
  371. package/src/components/Modals/ModalHeaderComponent.tsx +8 -1
  372. package/src/components/Modals/ModalWrapper/ModalWrapper.tsx +70 -58
  373. package/src/components/Modals/UserProfileModal/UserProfileModal.tsx +19 -15
  374. package/src/components/Modals/styledModalComponents.tsx +17 -13
  375. package/src/components/styled/MediaFilePreview.tsx +46 -26
  376. package/src/components/styled/MessageImage.tsx +58 -13
  377. package/src/components/styled/NewMessageLabel.tsx +8 -5
  378. package/src/components/styled/SendInput.tsx +64 -29
  379. package/src/components/styled/StyledComponents.ts +6 -3
  380. package/src/components/styled/StyledInputComponents/MediaComponents.tsx +15 -5
  381. package/src/components/styled/StyledInputComponents/StyledInputComponents.tsx +7 -2
  382. package/src/components/styled/UnsupportedType.tsx +7 -1
  383. package/src/components/styled/VideoMessage.tsx +76 -12
  384. package/src/context/MessageNotificationContext.tsx +8 -8
  385. package/src/context/xmppProvider.tsx +247 -30
  386. package/src/helpers/chatAutoEnterer.ts +2 -8
  387. package/src/helpers/historyPreloadScheduler.ts +4 -1
  388. package/src/helpers/insertMessageWithDelimiter.ts +22 -15
  389. package/src/helpers/keyboardLayout.ts +18 -0
  390. package/src/helpers/mediaDimensions.ts +47 -0
  391. package/src/helpers/mimeToExtension.ts +64 -0
  392. package/src/helpers/normalizeRoomJid.ts +6 -0
  393. package/src/hooks/useChatRoomFocus.ts +95 -0
  394. package/src/hooks/useHeapSender.tsx +2 -1
  395. package/src/hooks/useLogout.tsx +36 -13
  396. package/src/hooks/usePushNotifications.ts +21 -8
  397. package/src/hooks/useRoomState.tsx +4 -0
  398. package/src/hooks/useSendMessage.tsx +258 -13
  399. package/src/hooks/useUnreadMessagesCounter.ts +13 -16
  400. package/src/main.ts +1 -0
  401. package/src/networking/api-requests/auth.api.ts +55 -1
  402. package/src/networking/api-requests/rooms.api.ts +31 -7
  403. package/src/networking/apiClient.ts +2 -1
  404. package/src/networking/outboundQueue.ts +98 -0
  405. package/src/networking/xmpp/createTimeoutPromise.xmpp.ts +5 -1
  406. package/src/networking/xmpp/flushLastViewedToPrivateStore.ts +11 -13
  407. package/src/networking/xmpp/getRoomsPaged.xmpp.ts +27 -3
  408. package/src/networking/xmpp/presenceInRoom.xmpp.ts +24 -9
  409. package/src/networking/xmpp/sendTextMessage.xmpp.ts +2 -1
  410. package/src/networking/xmppClient.ts +317 -43
  411. package/src/roomStore/Middleware/unreadMidlleware.tsx +23 -5
  412. package/src/roomStore/roomsSlice.ts +177 -27
  413. package/src/types/models/media.model.ts +3 -0
  414. package/src/types/models/xmpp.model.ts +2 -1
  415. package/src/types/types.ts +31 -0
  416. package/src/utils/installPromiseRejectionTracker.ts +91 -0
  417. package/src/utils/resendMessage.ts +2 -1
@@ -0,0 +1,95 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import { useDispatch } from 'react-redux';
3
+ import {
4
+ clearVisibleRoom,
5
+ setCurrentRoom,
6
+ setLastViewedTimestamp,
7
+ setVisibleRoom,
8
+ } from '../roomStore/roomsSlice';
9
+ import { store } from '../roomStore';
10
+
11
+ interface UseChatRoomFocusOptions {
12
+ /** The room JID that the consumer's tab/screen is currently showing. */
13
+ roomJID: string | null | undefined;
14
+ /**
15
+ * Whether the chat tab/screen is currently focused. In tab-based
16
+ * navigators where `<ChatRoom>` never unmounts, the SDK can't tell
17
+ * "user is looking at this tab" from "user is on a different tab"
18
+ * — pass this from `useFocusEffect` (React Navigation) or a similar
19
+ * focus signal so the unread counter clears on focus and starts
20
+ * counting again on blur.
21
+ */
22
+ isFocused: boolean;
23
+ }
24
+
25
+ /**
26
+ * Public hook for consumers embedding `<ChatRoom>` inside a tab-based
27
+ * navigator. Mirrors the mount/unmount unread-tracking that ChatRoom
28
+ * does internally — but driven by an explicit focus signal so the
29
+ * unread counter behaves correctly when the chat tab is not focused
30
+ * but the component is still mounted.
31
+ *
32
+ * Without this hook, `useUnread()` will always return 0 for the chat
33
+ * room because the SDK assumes "mounted == active". With this hook,
34
+ * focus marks the room visible (clearing the badge) and blur stamps
35
+ * `lastViewedTimestamp = Date.now()` so future messages count as unread.
36
+ *
37
+ * Usage with React Navigation:
38
+ *
39
+ * ```tsx
40
+ * import { useIsFocused } from '@react-navigation/native';
41
+ *
42
+ * function ChatTab() {
43
+ * const isFocused = useIsFocused();
44
+ * useChatRoomFocus({ roomJID: 'general@conference.host', isFocused });
45
+ * return <Chat config={...} />;
46
+ * }
47
+ * ```
48
+ *
49
+ * Bug #18-adjacent / unread-tracking bug from sdk-bug-tracker.md.
50
+ */
51
+ export const useChatRoomFocus = ({
52
+ roomJID,
53
+ isFocused,
54
+ }: UseChatRoomFocusOptions) => {
55
+ const dispatch = useDispatch();
56
+ const prevRef = useRef<{ roomJID: string | null; isFocused: boolean }>({
57
+ roomJID: null,
58
+ isFocused: false,
59
+ });
60
+
61
+ const leaveRoom = (jid: string) => {
62
+ const timestamp = Date.now();
63
+ dispatch(setLastViewedTimestamp({ chatJID: jid, timestamp }));
64
+ dispatch(clearVisibleRoom());
65
+ const rooms = store.getState().rooms?.rooms;
66
+ (store.getState().chatSettingStore as any)?.client
67
+ ?.flushLastViewedToPrivateStoreStanza(rooms, { visibleRoomJID: jid })
68
+ .catch(() => {});
69
+ };
70
+
71
+ useEffect(() => {
72
+ const prev = prevRef.current;
73
+ if (prev.isFocused && prev.roomJID && (prev.roomJID !== roomJID || !isFocused)) {
74
+ leaveRoom(prev.roomJID);
75
+ }
76
+
77
+ if (roomJID && isFocused) {
78
+ dispatch(setCurrentRoom({ roomJID }));
79
+ dispatch(setVisibleRoom({ roomJID }));
80
+ }
81
+
82
+ prevRef.current = { roomJID: roomJID || null, isFocused };
83
+ }, [roomJID, isFocused, dispatch]);
84
+
85
+ useEffect(() => {
86
+ return () => {
87
+ const prev = prevRef.current;
88
+ if (prev.isFocused && prev.roomJID) {
89
+ leaveRoom(prev.roomJID);
90
+ }
91
+ };
92
+ }, []);
93
+ };
94
+
95
+ export default useChatRoomFocus;
@@ -38,7 +38,8 @@ export const useHeapSender = (client: XmppClient | null) => {
38
38
  !!msg.isReply,
39
39
  msg.showInChannel === 'true',
40
40
  msg.mainMessage || '',
41
- msg.langSource
41
+ msg.langSource,
42
+ msg.id
42
43
  );
43
44
  } else {
44
45
  await client.sendMessage(
@@ -49,7 +49,7 @@ const logoutService = {
49
49
  performLogout: async (): Promise<void> => {
50
50
  // 0. Flush lastViewedTimestamp to the server's private store BEFORE
51
51
  // we close XMPP. Only rooms with no outstanding unread (plus the
52
- // active room) get their entries updated — rooms with unread
52
+ // visible room) get their entries updated — rooms with unread
53
53
  // keep their old marker so the next login still surfaces those
54
54
  // messages as unread. Without this, an active-room view at
55
55
  // logout would still appear unread on next login because we
@@ -58,11 +58,11 @@ const logoutService = {
58
58
  const state = store.getState();
59
59
  const client = (state.chatSettingStore as any)?.client;
60
60
  const rooms = state.rooms?.rooms;
61
- const activeRoomJID = state.rooms?.activeRoomJID || null;
61
+ const visibleRoomJID = state.rooms?.visibleRoomJID || null;
62
62
  if (client?.flushLastViewedToPrivateStoreStanza) {
63
63
  await Promise.race([
64
64
  client.flushLastViewedToPrivateStoreStanza(rooms, {
65
- activeRoomJID,
65
+ visibleRoomJID,
66
66
  onlyIfNoUnread: true,
67
67
  }),
68
68
  new Promise((res) => setTimeout(res, 2000)),
@@ -134,18 +134,41 @@ const logoutService = {
134
134
  },
135
135
  };
136
136
 
137
- export const useLogout = () => {
138
- const dispatch = useDispatch();
137
+ /**
138
+ * Hook that returns an awaitable logout function.
139
+ *
140
+ * ```tsx
141
+ * const logout = useLogout();
142
+ * await logout();
143
+ * navigation.replace('SignIn'); // disk is provably clean here
144
+ * ```
145
+ *
146
+ * The returned function resolves AFTER all teardown work has landed:
147
+ * XMPP disconnect, redux reset, persisted slices wiped, stray
148
+ * AsyncStorage keys removed, REST cache cleared. It never rejects —
149
+ * any internal failure is logged via console.warn so a fire-and-forget
150
+ * `logout()` call (no `await`) still won't crash the host.
151
+ *
152
+ * Why awaitable: the persistence middleware debounces writes by 200ms,
153
+ * and the chat-slice `logout` reducer removes ETHORA_USER fire-and-
154
+ * forget. If the host navigated / re-mounted `<Chat>` immediately
155
+ * after a non-awaited call, the next bootstrap could occasionally
156
+ * read stale persisted state ("old chats reappear"). Awaiting the
157
+ * returned promise eliminates that race entirely.
158
+ */
159
+ export const useLogout = (): (() => Promise<void>) => {
160
+ // dispatch is kept for backward-compat with consumers who treat the
161
+ // hook's presence as a redux-context requirement; not used directly
162
+ // here because the work happens through the module-level service.
163
+ useDispatch();
139
164
 
140
- const handleLogout = useCallback(() => {
141
- // The hook fires-and-forgets — consumers that need to await the
142
- // teardown should call `logoutService.performLogout()` directly.
143
- logoutService.performLogout().catch((err) => {
144
- console.warn('useLogout: performLogout rejected', err);
145
- });
165
+ return useCallback(async () => {
166
+ try {
167
+ await logoutService.performLogout();
168
+ } catch (err) {
169
+ console.warn('useLogout: performLogout failed', err);
170
+ }
146
171
  }, []);
147
-
148
- return handleLogout;
149
172
  };
150
173
 
151
174
  export { logoutService };
@@ -51,7 +51,13 @@ export function usePushNotifications() {
51
51
  };
52
52
 
53
53
  useEffect(() => {
54
- initToken();
54
+ // initToken is async — `initToken()` without `.catch` leaves any
55
+ // rejection unhandled (showed as `Uncaught (in promise, id: X)` red
56
+ // screen in dev). The inner try/catch covers most paths but any
57
+ // synchronous throw before the try-block ran would still leak.
58
+ initToken().catch((err) =>
59
+ console.warn('[usePushNotifications] initToken rejected', err)
60
+ );
55
61
 
56
62
  const unsubscribeForeground = onForegroundMessage((message) => {
57
63
  setNotification(message);
@@ -78,13 +84,20 @@ export function usePushNotifications() {
78
84
  handleNotificationPress(message);
79
85
  });
80
86
 
81
- getInitialNotification().then((message) => {
82
- if (message) {
83
- console.log('[usePushNotifications] Initial notification:', JSON.stringify(message, null, 2));
84
- setNotification(message);
85
- handleNotificationPress(message);
86
- }
87
- });
87
+ getInitialNotification()
88
+ .then((message) => {
89
+ if (message) {
90
+ console.log('[usePushNotifications] Initial notification:', JSON.stringify(message, null, 2));
91
+ setNotification(message);
92
+ handleNotificationPress(message);
93
+ }
94
+ })
95
+ .catch((err) =>
96
+ console.warn(
97
+ '[usePushNotifications] getInitialNotification failed',
98
+ err
99
+ )
100
+ );
88
101
 
89
102
  return () => {
90
103
  unsubscribeForeground();
@@ -12,6 +12,9 @@ export const useRoomState = (roomJID?: string) => {
12
12
  const activeRoomJID = useSelector(
13
13
  (state: RootState) => state.rooms.activeRoomJID
14
14
  );
15
+ const visibleRoomJID = useSelector(
16
+ (state: RootState) => state.rooms.visibleRoomJID
17
+ );
15
18
  const editAction = useSelector((state: RootState) => state.rooms.editAction);
16
19
  const globalLoading = useSelector(
17
20
  (state: RootState) => state.rooms.isLoading
@@ -37,6 +40,7 @@ export const useRoomState = (roomJID?: string) => {
37
40
  room,
38
41
  roomsList,
39
42
  activeRoomJID,
43
+ visibleRoomJID,
40
44
  editAction,
41
45
  globalLoading,
42
46
  loadingText,
@@ -1,18 +1,30 @@
1
1
  import { useCallback } from 'react';
2
+ import { Platform } from 'react-native';
2
3
  import { v4 as uuidv4 } from 'uuid';
3
4
  import { useXmppClient } from '../context/xmppProvider';
4
- import { useDispatch, useSelector } from 'react-redux';
5
+ import { useDispatch, useSelector, useStore } from 'react-redux';
5
6
  import { addRoomMessage, setEditAction } from '../roomStore/roomsSlice';
6
7
  import {
7
8
  addMessageToHeap,
8
9
  clearMessageFailure,
9
10
  markMessageFailed,
10
11
  } from '../roomStore/roomHeapSlice';
11
- import { uploadFile } from '../networking/api-requests/auth.api';
12
- import { RootState } from '../roomStore';
12
+ import { uploadFileViaFetch } from '../networking/api-requests/auth.api';
13
+ import { enqueueOutboundSend } from '../networking/outboundQueue';
14
+ import { RootState, store } from '../roomStore';
15
+ import { getGlobalXmppClient } from '../utils/clientRegistry';
13
16
  import { useEventHandlers } from './useEventHandlers';
14
17
  import type { IConfig, IMessage } from '../types/types';
15
18
 
19
+ // Watchdog: if a message stays "pending" (no server echo) for this
20
+ // long after the optimistic dispatch, mark it failed so the bubble
21
+ // flips out of the "sending..." state. Covers the silent-failure
22
+ // case (e.g. device offline before the message ever made it to the
23
+ // XMPP socket) — bug #18. Tuned generously so a brief network blip
24
+ // followed by reconnect within 30s still resolves naturally via the
25
+ // server echo.
26
+ const PENDING_WATCHDOG_MS = 30_000;
27
+
16
28
  // Monotonic counter for stanza ids. Bumped on each send within a single
17
29
  // process. Two rapid sends in the same millisecond used to collide on
18
30
  // `send-text-message-${Date.now()}`; that collision made
@@ -23,9 +35,18 @@ let __sendIdSeq = 0;
23
35
  const nextStanzaId = (prefix: string): string =>
24
36
  `${prefix}-${Date.now()}-${(__sendIdSeq = (__sendIdSeq + 1) >>> 0)}`;
25
37
 
38
+ // Hard client-side cap on a single upload. The backend rejects oversized
39
+ // bodies with HTTP 413; SendInput blocks oversized files at pick time,
40
+ // and this mirrors that limit on every send path (including retries).
41
+ const MAX_MEDIA_BYTES = 50 * 1024 * 1024;
42
+
26
43
  export const useSendMessage = (_configOverride?: IConfig) => {
27
44
  const { client } = useXmppClient();
28
45
  const dispatch = useDispatch();
46
+ // useStore gives us imperative getState() access for the watchdog —
47
+ // peek at the message's current pending/failed state from inside a
48
+ // setTimeout callback without re-rendering on every redux change.
49
+ const reduxStore = useStore<RootState>();
29
50
 
30
51
  // Split selectors so each returns a primitive/stable reference — the
31
52
  // single-object selector above caused a "returned a different result
@@ -120,16 +141,85 @@ export const useSendMessage = (_configOverride?: IConfig) => {
120
141
  );
121
142
  }
122
143
 
144
+ // Silent-failure watchdog (bug #18). If no echo lands within
145
+ // PENDING_WATCHDOG_MS, flip the bubble to "failed → tap to
146
+ // retry". We check at fire-time whether the message is still
147
+ // pending and not already marked failed by the catch path.
148
+ const watchdogTimer = setTimeout(() => {
149
+ const state = reduxStore.getState();
150
+ const roomMsgs = state.rooms?.rooms?.[activeRoomJID]?.messages || [];
151
+ const stillPending = roomMsgs.find(
152
+ (m: any) => m.id === optimisticId && m.pending
153
+ );
154
+ const alreadyFailed = state.roomHeapSlice?.failedMessages?.[optimisticId];
155
+ if (stillPending && !alreadyFailed) {
156
+ console.warn(
157
+ `Send watchdog fired — message ${optimisticId} stuck pending > ${PENDING_WATCHDOG_MS}ms; marking failed`
158
+ );
159
+ dispatch(
160
+ markMessageFailed({
161
+ kind: 'text',
162
+ id: optimisticId,
163
+ roomJID: activeRoomJID,
164
+ body: message,
165
+ isReply,
166
+ isChecked,
167
+ mainMessage,
168
+ })
169
+ );
170
+ handleMessageFailed({
171
+ message,
172
+ roomJID: activeRoomJID,
173
+ error: new Error('Send timed out (no server echo)'),
174
+ messageType: 'text',
175
+ });
176
+ }
177
+ }, PENDING_WATCHDOG_MS);
178
+
123
179
  try {
124
- if (!client) {
125
- throw new Error('No XMPP client');
180
+
181
+ const effectiveClient: any =
182
+ client ||
183
+ [
184
+ getGlobalXmppClient(),
185
+ store.getState()?.chatSettingStore?.client,
186
+ ].find((c: any) => c && c.status === 'online');
187
+
188
+ if (!effectiveClient) {
189
+ const anyClient: any =
190
+ client ||
191
+ getGlobalXmppClient() ||
192
+ store.getState()?.chatSettingStore?.client;
193
+ try {
194
+ anyClient?.forceReconnect?.();
195
+ } catch {}
196
+ enqueueOutboundSend({
197
+ optimisticId,
198
+ roomJID: activeRoomJID,
199
+ enqueuedAt: Date.now(),
200
+ send: (c) =>
201
+ c.sendMessage(
202
+ activeRoomJID,
203
+ user.firstName,
204
+ user.lastName,
205
+ '',
206
+ user.walletAddress,
207
+ message,
208
+ '',
209
+ isReply || false,
210
+ isChecked || false,
211
+ mainMessage || '',
212
+ optimisticId
213
+ ),
214
+ });
215
+ return;
126
216
  }
127
217
  // Pass the optimistic id as the stanza id so the server echoes
128
218
  // it back with the same value — the reducer's dedupe lookup
129
219
  // matches and the bubble flips from pending → delivered in-place
130
220
  // (instead of rendering two copies, which is what happens when
131
221
  // ids don't match).
132
- client.sendMessage(
222
+ effectiveClient.sendMessage(
133
223
  activeRoomJID,
134
224
  user.firstName,
135
225
  user.lastName,
@@ -149,6 +239,7 @@ export const useSendMessage = (_configOverride?: IConfig) => {
149
239
  messageType: 'text',
150
240
  });
151
241
  } catch (error) {
242
+ clearTimeout(watchdogTimer);
152
243
  dispatch(
153
244
  markMessageFailed({
154
245
  kind: 'text',
@@ -168,7 +259,7 @@ export const useSendMessage = (_configOverride?: IConfig) => {
168
259
  });
169
260
  }
170
261
  },
171
- [editAction, client, user, dispatch, handleMessageSent, handleMessageEdited, handleMessageFailed]
262
+ [editAction, client, user, dispatch, handleMessageSent, handleMessageEdited, handleMessageFailed, reduxStore]
172
263
  );
173
264
 
174
265
  const sendMedia = useCallback(
@@ -183,6 +274,36 @@ export const useSendMessage = (_configOverride?: IConfig) => {
183
274
  ) => {
184
275
  client?.onCriticalSend?.(activeRoomJID);
185
276
 
277
+ // Defensive 50 MB guard on every send path (SendInput blocks it at
278
+ // pick time, but retries replay the saved payload straight here).
279
+ if (typeof data?.size === 'number' && data.size > MAX_MEDIA_BYTES) {
280
+ console.warn('media exceeds 50MB cap — not uploading', {
281
+ name: data?.name,
282
+ size: data?.size,
283
+ });
284
+ if (existingId) {
285
+ dispatch(
286
+ markMessageFailed({
287
+ kind: 'media',
288
+ id: existingId,
289
+ roomJID: activeRoomJID,
290
+ data,
291
+ type,
292
+ isReply,
293
+ isChecked,
294
+ mainMessage,
295
+ })
296
+ );
297
+ }
298
+ handleMessageFailed({
299
+ message: 'media',
300
+ roomJID: activeRoomJID,
301
+ error: new Error('File exceeds the 50 MB upload limit'),
302
+ messageType: 'media',
303
+ });
304
+ return;
305
+ }
306
+
186
307
  const id = existingId || `send-media-message:${uuidv4()}`;
187
308
  const optimisticTimestamp = Date.now();
188
309
  const optimisticDate = new Date(optimisticTimestamp).toISOString();
@@ -245,11 +366,94 @@ export const useSendMessage = (_configOverride?: IConfig) => {
245
366
  );
246
367
  }
247
368
 
248
- try {
249
- const mediaData = new FormData();
250
- mediaData.append('files', data);
369
+ // Silent-failure watchdog for media-send (bug #18). Same shape
370
+ // as the text path: if the optimistic bubble never gets the echo
371
+ // (e.g. upload completed but XMPP send is buffered offline), flip
372
+ // to failed → tap to retry.
373
+ const mediaWatchdog = setTimeout(() => {
374
+ const state = reduxStore.getState();
375
+ const roomMsgs = state.rooms?.rooms?.[activeRoomJID]?.messages || [];
376
+ const stillPending = roomMsgs.find(
377
+ (m: any) => m.id === id && m.pending
378
+ );
379
+ const alreadyFailed = state.roomHeapSlice?.failedMessages?.[id];
380
+ if (stillPending && !alreadyFailed) {
381
+ console.warn(
382
+ `Media-send watchdog fired — ${id} stuck pending > ${PENDING_WATCHDOG_MS}ms; marking failed`
383
+ );
384
+ dispatch(
385
+ markMessageFailed({
386
+ kind: 'media',
387
+ id,
388
+ roomJID: activeRoomJID,
389
+ data,
390
+ type,
391
+ isReply,
392
+ isChecked,
393
+ mainMessage,
394
+ })
395
+ );
396
+ handleMessageFailed({
397
+ message: 'media',
398
+ roomJID: activeRoomJID,
399
+ error: new Error('Media send timed out (no server echo)'),
400
+ messageType: 'media',
401
+ });
402
+ }
403
+ }, PENDING_WATCHDOG_MS);
251
404
 
252
- const response = await uploadFile(mediaData);
405
+ // Help RN's FormData polyfill build a proper multipart/file part
406
+ // (instead of serialising the JS object as JSON). On iOS the
407
+ // picker can return ph:// or assets-library:// URIs for some
408
+ // legacy library items — those can't be read as files. Coerce
409
+ // to a file:// scheme when possible so the body is a real blob.
410
+ const fileBlob = {
411
+ uri:
412
+ typeof data?.uri === 'string'
413
+ ? data.uri.replace(/^assets-library:\/\//, 'file://')
414
+ : data?.uri,
415
+ type:
416
+ data?.type ||
417
+ data?.mimeType ||
418
+ type ||
419
+ 'application/octet-stream',
420
+ name: data?.name || data?.fileName || `media_${Date.now()}`,
421
+ };
422
+
423
+ // Mirror the web client: a single multipart POST to /files/ with
424
+ // the file under the plural "files" field. We send it via fetch
425
+ // (not axios) because RN's fetch sets the multipart boundary on the
426
+ // Content-Type header correctly. The previous axios fallback omitted
427
+ // a valid boundary, so the server mis-parsed one file as many and
428
+ // rejected the request with HTTP 413 "TOO_MANY_FILES". The only
429
+ // retry is the documented singular-"file" fallback for deployments
430
+ // that 500 on the plural field (bug #10).
431
+ const tryUpload = async () => {
432
+ const fd = new FormData();
433
+ fd.append('files', fileBlob as any);
434
+ try {
435
+ return await uploadFileViaFetch(fd);
436
+ } catch (err: any) {
437
+ if (err?.response?.status === 500) {
438
+ console.warn(
439
+ 'upload "files" field returned 500 — retrying with "file" (singular)',
440
+ { name: fileBlob.name, type: fileBlob.type }
441
+ );
442
+ const fd2 = new FormData();
443
+ fd2.append('file', fileBlob as any);
444
+ return await uploadFileViaFetch(fd2);
445
+ }
446
+ throw err;
447
+ }
448
+ };
449
+
450
+ try {
451
+ const response = await tryUpload();
452
+ // Upload succeeded — the stanza is now in flight. If the
453
+ // server echo arrives within the watchdog window the bubble
454
+ // flips normally; if not, the watchdog still fires and marks
455
+ // it failed. We don't clear the timer on upload success because
456
+ // the XMPP send could still fail silently.
253
457
 
254
458
  for (const item of response.data.results) {
255
459
  const messagePayload = {
@@ -281,7 +485,34 @@ export const useSendMessage = (_configOverride?: IConfig) => {
281
485
  // Stanza id == placeholder id so the MUC echo's outer
282
486
  // <message id="..."> matches xmppId on the placeholder and
283
487
  // insertMessageWithDelimiter merges in place + flips pending.
284
- client?.sendMediaMessageStanza(activeRoomJID, messagePayload, id);
488
+ const effectiveMediaClient: any =
489
+ client ||
490
+ [
491
+ getGlobalXmppClient(),
492
+ store.getState()?.chatSettingStore?.client,
493
+ ].find((c: any) => c && c.status === 'online');
494
+ if (effectiveMediaClient) {
495
+ effectiveMediaClient.sendMediaMessageStanza(
496
+ activeRoomJID,
497
+ messagePayload,
498
+ id
499
+ );
500
+ } else {
501
+ const anyMediaClient: any =
502
+ client ||
503
+ getGlobalXmppClient() ||
504
+ store.getState()?.chatSettingStore?.client;
505
+ try {
506
+ anyMediaClient?.forceReconnect?.();
507
+ } catch {}
508
+ enqueueOutboundSend({
509
+ optimisticId: id,
510
+ roomJID: activeRoomJID,
511
+ enqueuedAt: Date.now(),
512
+ send: (c) =>
513
+ c.sendMediaMessageStanza(activeRoomJID, messagePayload, id),
514
+ });
515
+ }
285
516
  }
286
517
 
287
518
  await handleMessageSent({
@@ -300,16 +531,30 @@ export const useSendMessage = (_configOverride?: IConfig) => {
300
531
  },
301
532
  });
302
533
  } catch (error: any) {
534
+ // Upload (or stanza send) threw an explicit error. Clear the
535
+ // watchdog so it doesn't double-fire `markMessageFailed`.
536
+ clearTimeout(mediaWatchdog);
303
537
  // Surface the real server payload so we can see why the upload
304
538
  // was rejected (axios collapses the message to "Request failed
305
539
  // with status code N"; the actual reason lives in
306
540
  // `error.response.data`).
541
+ const uriStr = typeof data?.uri === 'string' ? data.uri : '';
542
+ const uriScheme = uriStr.match(/^([a-z]+):/i)?.[1] ?? 'unknown';
307
543
  console.error('upload failed', {
308
544
  status: error?.response?.status,
309
545
  statusText: error?.response?.statusText,
310
546
  serverBody: error?.response?.data,
311
547
  requestUrl: error?.config?.url,
312
548
  axiosMessage: error?.message,
549
+ axiosCode: error?.code,
550
+ axiosCauseMessage: error?.cause?.message,
551
+ uri: uriStr,
552
+ uriScheme,
553
+ mime: fileBlob.type,
554
+ fileSizeBytes: data?.size ?? null,
555
+ fileName: fileBlob.name,
556
+ platformOS: Platform.OS,
557
+ platformVersion: Platform.Version,
313
558
  });
314
559
  dispatch(
315
560
  markMessageFailed({
@@ -331,7 +576,7 @@ export const useSendMessage = (_configOverride?: IConfig) => {
331
576
  });
332
577
  }
333
578
  },
334
- [client, config, user, dispatch, handleMessageSent, handleMessageFailed]
579
+ [client, config, user, dispatch, handleMessageSent, handleMessageFailed, reduxStore]
335
580
  );
336
581
 
337
582
  // Retry a previously-failed send by replaying the saved payload from
@@ -1,6 +1,6 @@
1
1
  import { useSyncExternalStore } from 'react';
2
2
  import { IRoom } from '../types/types';
3
- import { RootState, store } from '../roomStore';
3
+ import { store } from '../roomStore';
4
4
 
5
5
  interface UnreadMessagesMap {
6
6
  [roomJid: string]: number;
@@ -13,21 +13,18 @@ interface UnreadMessagesStats {
13
13
  }
14
14
 
15
15
  export const useUnreadMessagesCounter = (): UnreadMessagesStats => {
16
- const subscribe = (callback: () => void) => {
17
- const unsubscribe = store.subscribe(() => {
18
- const state: RootState = store.getState();
19
- const rooms = state.rooms?.rooms;
20
-
21
- if (rooms) {
22
- Object.entries(rooms).forEach(([roomJid, room]: [string, IRoom]) => {
23
- if (room.unreadMessages !== undefined) {
24
- callback();
25
- }
26
- });
27
- }
28
- });
29
- return unsubscribe;
30
- };
16
+ // Notify React on EVERY store change and let useSyncExternalStore's
17
+ // snapshot comparison (Object.is on the `rooms` reference) decide
18
+ // whether to re-render. The previous implementation only fired the
19
+ // callback while iterating rooms that already had `unreadMessages`
20
+ // defined — so when `rooms` became empty (logout, or the last room
21
+ // removed) the loop body never ran, the callback never fired, and the
22
+ // hook never re-rendered: a consumer's badge stayed stuck at its last
23
+ // non-zero count after logout. (It's also cheaper — one callback per
24
+ // change instead of one per unread room. Redux Toolkit keeps the
25
+ // `rooms` slice reference stable across actions that don't touch it,
26
+ // so non-rooms dispatches still don't trigger a re-render.)
27
+ const subscribe = (callback: () => void) => store.subscribe(callback);
31
28
 
32
29
  const rooms = useSyncExternalStore(
33
30
  subscribe,
package/src/main.ts CHANGED
@@ -28,6 +28,7 @@ export { XmppProvider } from './context/xmppProvider';
28
28
 
29
29
  // Hooks that consumers usually need (badge counts, logout, push, etc.).
30
30
  export { useUnread } from './hooks/useUnreadMessagesCounter';
31
+ export { useChatRoomFocus } from './hooks/useChatRoomFocus';
31
32
  export { logoutService } from './hooks/useLogout';
32
33
  export { useQRCodeChat, handleQRChatId } from './hooks/useQRCodeChatHandler';
33
34
  export { resendMessage } from './utils/resendMessage';