@ethora/chat-component-rn 26.10.4 → 26.10.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (451) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +15 -0
  3. package/app.plugin.js +2 -0
  4. package/lib/commonjs/assets/icons.js +2 -2
  5. package/lib/commonjs/assets/icons.js.map +1 -1
  6. package/lib/commonjs/components/MainComponents/AlbumGrid.js +152 -0
  7. package/lib/commonjs/components/MainComponents/AlbumGrid.js.map +1 -0
  8. package/lib/commonjs/components/MainComponents/ChatRoom.js +2 -2
  9. package/lib/commonjs/components/MainComponents/ChatRoom.js.map +1 -1
  10. package/lib/commonjs/components/MainComponents/MediaMessage.js +199 -123
  11. package/lib/commonjs/components/MainComponents/MediaMessage.js.map +1 -1
  12. package/lib/commonjs/components/MainComponents/MediaUploadOverlay.js +109 -0
  13. package/lib/commonjs/components/MainComponents/MediaUploadOverlay.js.map +1 -0
  14. package/lib/commonjs/components/MessageBubble/MediaCaption.js +51 -0
  15. package/lib/commonjs/components/MessageBubble/MediaCaption.js.map +1 -0
  16. package/lib/commonjs/components/MessageBubble/Message.js +40 -1
  17. package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
  18. package/lib/commonjs/components/MessageBubble/MessageInteractions.js +2 -1
  19. package/lib/commonjs/components/MessageBubble/MessageInteractions.js.map +1 -1
  20. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +75 -0
  21. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  22. package/lib/commonjs/components/RoomComponents/LastMessageItem.js +20 -4
  23. package/lib/commonjs/components/RoomComponents/LastMessageItem.js.map +1 -1
  24. package/lib/commonjs/components/RoomComponents/styled/LastAudioMessage.js +3 -2
  25. package/lib/commonjs/components/RoomComponents/styled/LastAudioMessage.js.map +1 -1
  26. package/lib/commonjs/components/RoomComponents/styled/LastMessageFile.js +3 -2
  27. package/lib/commonjs/components/RoomComponents/styled/LastMessageFile.js.map +1 -1
  28. package/lib/commonjs/components/RoomComponents/styled/LastMessagePhoto.js +3 -2
  29. package/lib/commonjs/components/RoomComponents/styled/LastMessagePhoto.js.map +1 -1
  30. package/lib/commonjs/components/RoomComponents/styled/LastMessageVideo.js +3 -2
  31. package/lib/commonjs/components/RoomComponents/styled/LastMessageVideo.js.map +1 -1
  32. package/lib/commonjs/components/Thread/ThreadWrapper.js +3 -2
  33. package/lib/commonjs/components/Thread/ThreadWrapper.js.map +1 -1
  34. package/lib/commonjs/components/VideoCalls/ActiveCallBar.js +61 -16
  35. package/lib/commonjs/components/VideoCalls/ActiveCallBar.js.map +1 -1
  36. package/lib/commonjs/components/VideoCalls/CallIcons.js +41 -16
  37. package/lib/commonjs/components/VideoCalls/CallIcons.js.map +1 -1
  38. package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js +183 -9
  39. package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js.map +1 -1
  40. package/lib/commonjs/components/VideoCalls/VideoCallSession.js +40 -15
  41. package/lib/commonjs/components/VideoCalls/VideoCallSession.js.map +1 -1
  42. package/lib/commonjs/components/VideoCalls/callSessionController.js +49 -26
  43. package/lib/commonjs/components/VideoCalls/callSessionController.js.map +1 -1
  44. package/lib/commonjs/components/VideoCalls/callUiStore.js +33 -4
  45. package/lib/commonjs/components/VideoCalls/callUiStore.js.map +1 -1
  46. package/lib/commonjs/components/VideoCalls/livekitRuntime.js +9 -1
  47. package/lib/commonjs/components/VideoCalls/livekitRuntime.js.map +1 -1
  48. package/lib/commonjs/components/VideoCalls/useCallKeep.js +197 -28
  49. package/lib/commonjs/components/VideoCalls/useCallKeep.js.map +1 -1
  50. package/lib/commonjs/components/VideoCalls/useOngoingCallNotification.js +88 -0
  51. package/lib/commonjs/components/VideoCalls/useOngoingCallNotification.js.map +1 -0
  52. package/lib/commonjs/components/VideoCalls/usePipTile.js +232 -0
  53. package/lib/commonjs/components/VideoCalls/usePipTile.js.map +1 -0
  54. package/lib/commonjs/components/styled/MediaFilePreview.js +60 -47
  55. package/lib/commonjs/components/styled/MediaFilePreview.js.map +1 -1
  56. package/lib/commonjs/components/styled/MessageImage.js +16 -6
  57. package/lib/commonjs/components/styled/MessageImage.js.map +1 -1
  58. package/lib/commonjs/components/styled/SendInput.js +99 -68
  59. package/lib/commonjs/components/styled/SendInput.js.map +1 -1
  60. package/lib/commonjs/components/styled/VideoMessage.js +24 -10
  61. package/lib/commonjs/components/styled/VideoMessage.js.map +1 -1
  62. package/lib/commonjs/context/xmppProvider.js +46 -0
  63. package/lib/commonjs/context/xmppProvider.js.map +1 -1
  64. package/lib/commonjs/e2ee/fileEnvelope.js +6 -0
  65. package/lib/commonjs/e2ee/fileEnvelope.js.map +1 -1
  66. package/lib/commonjs/e2ee/sealedBody.js +32 -2
  67. package/lib/commonjs/e2ee/sealedBody.js.map +1 -1
  68. package/lib/commonjs/helpers/albumLayout.js +150 -0
  69. package/lib/commonjs/helpers/albumLayout.js.map +1 -0
  70. package/lib/commonjs/helpers/attachments.js +255 -0
  71. package/lib/commonjs/helpers/attachments.js.map +1 -0
  72. package/lib/commonjs/helpers/caption.js +214 -0
  73. package/lib/commonjs/helpers/caption.js.map +1 -0
  74. package/lib/commonjs/helpers/createMainMessageForThread.js +5 -1
  75. package/lib/commonjs/helpers/createMainMessageForThread.js.map +1 -1
  76. package/lib/commonjs/helpers/createMessageFromXml.js +19 -5
  77. package/lib/commonjs/helpers/createMessageFromXml.js.map +1 -1
  78. package/lib/commonjs/helpers/fileKind.js +40 -0
  79. package/lib/commonjs/helpers/fileKind.js.map +1 -0
  80. package/lib/commonjs/helpers/getDataFromXml.js +6 -1
  81. package/lib/commonjs/helpers/getDataFromXml.js.map +1 -1
  82. package/lib/commonjs/helpers/mapWithConcurrency.js +41 -0
  83. package/lib/commonjs/helpers/mapWithConcurrency.js.map +1 -0
  84. package/lib/commonjs/helpers/mediaLimits.js +33 -0
  85. package/lib/commonjs/helpers/mediaLimits.js.map +1 -0
  86. package/lib/commonjs/helpers/mediaSendRetry.js +64 -0
  87. package/lib/commonjs/helpers/mediaSendRetry.js.map +1 -0
  88. package/lib/commonjs/helpers/mediaWireData.js +62 -0
  89. package/lib/commonjs/helpers/mediaWireData.js.map +1 -0
  90. package/lib/commonjs/helpers/mentionSpans.js +19 -0
  91. package/lib/commonjs/helpers/mentionSpans.js.map +1 -0
  92. package/lib/commonjs/helpers/mimeToExtension.js +45 -0
  93. package/lib/commonjs/helpers/mimeToExtension.js.map +1 -1
  94. package/lib/commonjs/helpers/readReceipts.js +173 -0
  95. package/lib/commonjs/helpers/readReceipts.js.map +1 -0
  96. package/lib/commonjs/helpers/repairFilenameEncoding.js +79 -0
  97. package/lib/commonjs/helpers/repairFilenameEncoding.js.map +1 -0
  98. package/lib/commonjs/helpers/safeJson.js +18 -0
  99. package/lib/commonjs/helpers/safeJson.js.map +1 -0
  100. package/lib/commonjs/helpers/updateMessagesTillLast.js +3 -1
  101. package/lib/commonjs/helpers/updateMessagesTillLast.js.map +1 -1
  102. package/lib/commonjs/hooks/useSendMessage.js +384 -193
  103. package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
  104. package/lib/commonjs/i18n/strings.js +144 -0
  105. package/lib/commonjs/i18n/strings.js.map +1 -1
  106. package/lib/commonjs/main.js.map +1 -1
  107. package/lib/commonjs/networking/api-requests/auth.api.js +62 -3
  108. package/lib/commonjs/networking/api-requests/auth.api.js.map +1 -1
  109. package/lib/commonjs/networking/stanzaHandlers.js +100 -3
  110. package/lib/commonjs/networking/stanzaHandlers.js.map +1 -1
  111. package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js +4 -2
  112. package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
  113. package/lib/commonjs/networking/xmpp/sendDisplayedMarker.xmpp.js +28 -0
  114. package/lib/commonjs/networking/xmpp/sendDisplayedMarker.xmpp.js.map +1 -0
  115. package/lib/commonjs/networking/xmpp/sendMediaMessage.xmpp.js +53 -2
  116. package/lib/commonjs/networking/xmpp/sendMediaMessage.xmpp.js.map +1 -1
  117. package/lib/commonjs/networking/xmpp/sendReceivedReceipt.xmpp.js +28 -0
  118. package/lib/commonjs/networking/xmpp/sendReceivedReceipt.xmpp.js.map +1 -0
  119. package/lib/commonjs/networking/xmppClient.js +64 -0
  120. package/lib/commonjs/networking/xmppClient.js.map +1 -1
  121. package/lib/commonjs/roomStore/persistence.js +78 -5
  122. package/lib/commonjs/roomStore/persistence.js.map +1 -1
  123. package/lib/commonjs/roomStore/roomHeapSlice.js +4 -0
  124. package/lib/commonjs/roomStore/roomHeapSlice.js.map +1 -1
  125. package/lib/commonjs/roomStore/roomsSlice.js +114 -1
  126. package/lib/commonjs/roomStore/roomsSlice.js.map +1 -1
  127. package/lib/commonjs/types/models/attachment.model.js +2 -0
  128. package/lib/commonjs/types/models/attachment.model.js.map +1 -0
  129. package/lib/commonjs/version.js +1 -1
  130. package/lib/module/assets/icons.js +1 -1
  131. package/lib/module/assets/icons.js.map +1 -1
  132. package/lib/module/components/MainComponents/AlbumGrid.js +146 -0
  133. package/lib/module/components/MainComponents/AlbumGrid.js.map +1 -0
  134. package/lib/module/components/MainComponents/ChatRoom.js +2 -2
  135. package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
  136. package/lib/module/components/MainComponents/MediaMessage.js +200 -124
  137. package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
  138. package/lib/module/components/MainComponents/MediaUploadOverlay.js +104 -0
  139. package/lib/module/components/MainComponents/MediaUploadOverlay.js.map +1 -0
  140. package/lib/module/components/MessageBubble/MediaCaption.js +46 -0
  141. package/lib/module/components/MessageBubble/MediaCaption.js.map +1 -0
  142. package/lib/module/components/MessageBubble/Message.js +44 -5
  143. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  144. package/lib/module/components/MessageBubble/MessageInteractions.js +2 -1
  145. package/lib/module/components/MessageBubble/MessageInteractions.js.map +1 -1
  146. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +75 -0
  147. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  148. package/lib/module/components/RoomComponents/LastMessageItem.js +20 -4
  149. package/lib/module/components/RoomComponents/LastMessageItem.js.map +1 -1
  150. package/lib/module/components/RoomComponents/styled/LastAudioMessage.js +3 -2
  151. package/lib/module/components/RoomComponents/styled/LastAudioMessage.js.map +1 -1
  152. package/lib/module/components/RoomComponents/styled/LastMessageFile.js +3 -2
  153. package/lib/module/components/RoomComponents/styled/LastMessageFile.js.map +1 -1
  154. package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js +3 -2
  155. package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js.map +1 -1
  156. package/lib/module/components/RoomComponents/styled/LastMessageVideo.js +3 -2
  157. package/lib/module/components/RoomComponents/styled/LastMessageVideo.js.map +1 -1
  158. package/lib/module/components/Thread/ThreadWrapper.js +3 -2
  159. package/lib/module/components/Thread/ThreadWrapper.js.map +1 -1
  160. package/lib/module/components/VideoCalls/ActiveCallBar.js +63 -18
  161. package/lib/module/components/VideoCalls/ActiveCallBar.js.map +1 -1
  162. package/lib/module/components/VideoCalls/CallIcons.js +38 -15
  163. package/lib/module/components/VideoCalls/CallIcons.js.map +1 -1
  164. package/lib/module/components/VideoCalls/VideoCallOverlay.js +185 -11
  165. package/lib/module/components/VideoCalls/VideoCallOverlay.js.map +1 -1
  166. package/lib/module/components/VideoCalls/VideoCallSession.js +41 -16
  167. package/lib/module/components/VideoCalls/VideoCallSession.js.map +1 -1
  168. package/lib/module/components/VideoCalls/callSessionController.js +46 -24
  169. package/lib/module/components/VideoCalls/callSessionController.js.map +1 -1
  170. package/lib/module/components/VideoCalls/callUiStore.js +30 -3
  171. package/lib/module/components/VideoCalls/callUiStore.js.map +1 -1
  172. package/lib/module/components/VideoCalls/livekitRuntime.js +9 -1
  173. package/lib/module/components/VideoCalls/livekitRuntime.js.map +1 -1
  174. package/lib/module/components/VideoCalls/useCallKeep.js +198 -29
  175. package/lib/module/components/VideoCalls/useCallKeep.js.map +1 -1
  176. package/lib/module/components/VideoCalls/useOngoingCallNotification.js +84 -0
  177. package/lib/module/components/VideoCalls/useOngoingCallNotification.js.map +1 -0
  178. package/lib/module/components/VideoCalls/usePipTile.js +228 -0
  179. package/lib/module/components/VideoCalls/usePipTile.js.map +1 -0
  180. package/lib/module/components/styled/MediaFilePreview.js +61 -48
  181. package/lib/module/components/styled/MediaFilePreview.js.map +1 -1
  182. package/lib/module/components/styled/MessageImage.js +16 -6
  183. package/lib/module/components/styled/MessageImage.js.map +1 -1
  184. package/lib/module/components/styled/SendInput.js +99 -68
  185. package/lib/module/components/styled/SendInput.js.map +1 -1
  186. package/lib/module/components/styled/VideoMessage.js +24 -10
  187. package/lib/module/components/styled/VideoMessage.js.map +1 -1
  188. package/lib/module/context/xmppProvider.js +46 -0
  189. package/lib/module/context/xmppProvider.js.map +1 -1
  190. package/lib/module/e2ee/fileEnvelope.js +1 -1
  191. package/lib/module/e2ee/fileEnvelope.js.map +1 -1
  192. package/lib/module/e2ee/sealedBody.js +32 -2
  193. package/lib/module/e2ee/sealedBody.js.map +1 -1
  194. package/lib/module/helpers/albumLayout.js +143 -0
  195. package/lib/module/helpers/albumLayout.js.map +1 -0
  196. package/lib/module/helpers/attachments.js +245 -0
  197. package/lib/module/helpers/attachments.js.map +1 -0
  198. package/lib/module/helpers/caption.js +207 -0
  199. package/lib/module/helpers/caption.js.map +1 -0
  200. package/lib/module/helpers/createMainMessageForThread.js +5 -1
  201. package/lib/module/helpers/createMainMessageForThread.js.map +1 -1
  202. package/lib/module/helpers/createMessageFromXml.js +20 -6
  203. package/lib/module/helpers/createMessageFromXml.js.map +1 -1
  204. package/lib/module/helpers/fileKind.js +34 -0
  205. package/lib/module/helpers/fileKind.js.map +1 -0
  206. package/lib/module/helpers/getDataFromXml.js +6 -1
  207. package/lib/module/helpers/getDataFromXml.js.map +1 -1
  208. package/lib/module/helpers/mapWithConcurrency.js +37 -0
  209. package/lib/module/helpers/mapWithConcurrency.js.map +1 -0
  210. package/lib/module/helpers/mediaLimits.js +27 -0
  211. package/lib/module/helpers/mediaLimits.js.map +1 -0
  212. package/lib/module/helpers/mediaSendRetry.js +55 -0
  213. package/lib/module/helpers/mediaSendRetry.js.map +1 -0
  214. package/lib/module/helpers/mediaWireData.js +58 -0
  215. package/lib/module/helpers/mediaWireData.js.map +1 -0
  216. package/lib/module/helpers/mentionSpans.js +14 -0
  217. package/lib/module/helpers/mentionSpans.js.map +1 -0
  218. package/lib/module/helpers/mimeToExtension.js +43 -0
  219. package/lib/module/helpers/mimeToExtension.js.map +1 -1
  220. package/lib/module/helpers/readReceipts.js +160 -0
  221. package/lib/module/helpers/readReceipts.js.map +1 -0
  222. package/lib/module/helpers/repairFilenameEncoding.js +74 -0
  223. package/lib/module/helpers/repairFilenameEncoding.js.map +1 -0
  224. package/lib/module/helpers/safeJson.js +13 -0
  225. package/lib/module/helpers/safeJson.js.map +1 -0
  226. package/lib/module/helpers/updateMessagesTillLast.js +3 -1
  227. package/lib/module/helpers/updateMessagesTillLast.js.map +1 -1
  228. package/lib/module/hooks/useSendMessage.js +387 -195
  229. package/lib/module/hooks/useSendMessage.js.map +1 -1
  230. package/lib/module/i18n/strings.js +144 -0
  231. package/lib/module/i18n/strings.js.map +1 -1
  232. package/lib/module/main.js.map +1 -1
  233. package/lib/module/networking/api-requests/auth.api.js +62 -3
  234. package/lib/module/networking/api-requests/auth.api.js.map +1 -1
  235. package/lib/module/networking/stanzaHandlers.js +97 -4
  236. package/lib/module/networking/stanzaHandlers.js.map +1 -1
  237. package/lib/module/networking/xmpp/handleStanzas.xmpp.js +5 -3
  238. package/lib/module/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
  239. package/lib/module/networking/xmpp/sendDisplayedMarker.xmpp.js +25 -0
  240. package/lib/module/networking/xmpp/sendDisplayedMarker.xmpp.js.map +1 -0
  241. package/lib/module/networking/xmpp/sendMediaMessage.xmpp.js +53 -2
  242. package/lib/module/networking/xmpp/sendMediaMessage.xmpp.js.map +1 -1
  243. package/lib/module/networking/xmpp/sendReceivedReceipt.xmpp.js +25 -0
  244. package/lib/module/networking/xmpp/sendReceivedReceipt.xmpp.js.map +1 -0
  245. package/lib/module/networking/xmppClient.js +64 -0
  246. package/lib/module/networking/xmppClient.js.map +1 -1
  247. package/lib/module/roomStore/persistence.js +78 -5
  248. package/lib/module/roomStore/persistence.js.map +1 -1
  249. package/lib/module/roomStore/roomHeapSlice.js +4 -0
  250. package/lib/module/roomStore/roomHeapSlice.js.map +1 -1
  251. package/lib/module/roomStore/roomsSlice.js +109 -0
  252. package/lib/module/roomStore/roomsSlice.js.map +1 -1
  253. package/lib/module/types/models/attachment.model.js +2 -0
  254. package/lib/module/types/models/attachment.model.js.map +1 -0
  255. package/lib/module/version.js +1 -1
  256. package/lib/typescript/assets/icons.d.ts +6 -1
  257. package/lib/typescript/assets/icons.d.ts.map +1 -1
  258. package/lib/typescript/components/MainComponents/AlbumGrid.d.ts +21 -0
  259. package/lib/typescript/components/MainComponents/AlbumGrid.d.ts.map +1 -0
  260. package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
  261. package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
  262. package/lib/typescript/components/MainComponents/MediaUploadOverlay.d.ts +17 -0
  263. package/lib/typescript/components/MainComponents/MediaUploadOverlay.d.ts.map +1 -0
  264. package/lib/typescript/components/MessageBubble/MediaCaption.d.ts +12 -0
  265. package/lib/typescript/components/MessageBubble/MediaCaption.d.ts.map +1 -0
  266. package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
  267. package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts.map +1 -1
  268. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
  269. package/lib/typescript/components/RoomComponents/LastMessageItem.d.ts.map +1 -1
  270. package/lib/typescript/components/RoomComponents/styled/LastAudioMessage.d.ts +2 -0
  271. package/lib/typescript/components/RoomComponents/styled/LastAudioMessage.d.ts.map +1 -1
  272. package/lib/typescript/components/RoomComponents/styled/LastMessageFile.d.ts +2 -0
  273. package/lib/typescript/components/RoomComponents/styled/LastMessageFile.d.ts.map +1 -1
  274. package/lib/typescript/components/RoomComponents/styled/LastMessagePhoto.d.ts +2 -0
  275. package/lib/typescript/components/RoomComponents/styled/LastMessagePhoto.d.ts.map +1 -1
  276. package/lib/typescript/components/RoomComponents/styled/LastMessageVideo.d.ts +2 -0
  277. package/lib/typescript/components/RoomComponents/styled/LastMessageVideo.d.ts.map +1 -1
  278. package/lib/typescript/components/Thread/ThreadWrapper.d.ts.map +1 -1
  279. package/lib/typescript/components/VideoCalls/ActiveCallBar.d.ts +9 -3
  280. package/lib/typescript/components/VideoCalls/ActiveCallBar.d.ts.map +1 -1
  281. package/lib/typescript/components/VideoCalls/CallIcons.d.ts +9 -0
  282. package/lib/typescript/components/VideoCalls/CallIcons.d.ts.map +1 -1
  283. package/lib/typescript/components/VideoCalls/VideoCallOverlay.d.ts.map +1 -1
  284. package/lib/typescript/components/VideoCalls/VideoCallSession.d.ts.map +1 -1
  285. package/lib/typescript/components/VideoCalls/callSessionController.d.ts +7 -0
  286. package/lib/typescript/components/VideoCalls/callSessionController.d.ts.map +1 -1
  287. package/lib/typescript/components/VideoCalls/callUiStore.d.ts +13 -0
  288. package/lib/typescript/components/VideoCalls/callUiStore.d.ts.map +1 -1
  289. package/lib/typescript/components/VideoCalls/livekitRuntime.d.ts +5 -0
  290. package/lib/typescript/components/VideoCalls/livekitRuntime.d.ts.map +1 -1
  291. package/lib/typescript/components/VideoCalls/useCallKeep.d.ts +12 -0
  292. package/lib/typescript/components/VideoCalls/useCallKeep.d.ts.map +1 -1
  293. package/lib/typescript/components/VideoCalls/useOngoingCallNotification.d.ts +10 -0
  294. package/lib/typescript/components/VideoCalls/useOngoingCallNotification.d.ts.map +1 -0
  295. package/lib/typescript/components/VideoCalls/usePipTile.d.ts +46 -0
  296. package/lib/typescript/components/VideoCalls/usePipTile.d.ts.map +1 -0
  297. package/lib/typescript/components/styled/MediaFilePreview.d.ts.map +1 -1
  298. package/lib/typescript/components/styled/MessageImage.d.ts +10 -0
  299. package/lib/typescript/components/styled/MessageImage.d.ts.map +1 -1
  300. package/lib/typescript/components/styled/SendInput.d.ts +7 -1
  301. package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
  302. package/lib/typescript/components/styled/VideoMessage.d.ts +7 -0
  303. package/lib/typescript/components/styled/VideoMessage.d.ts.map +1 -1
  304. package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
  305. package/lib/typescript/e2ee/fileEnvelope.d.ts +1 -1
  306. package/lib/typescript/e2ee/fileEnvelope.d.ts.map +1 -1
  307. package/lib/typescript/e2ee/sealedBody.d.ts +16 -0
  308. package/lib/typescript/e2ee/sealedBody.d.ts.map +1 -1
  309. package/lib/typescript/helpers/albumLayout.d.ts +66 -0
  310. package/lib/typescript/helpers/albumLayout.d.ts.map +1 -0
  311. package/lib/typescript/helpers/attachments.d.ts +55 -0
  312. package/lib/typescript/helpers/attachments.d.ts.map +1 -0
  313. package/lib/typescript/helpers/caption.d.ts +64 -0
  314. package/lib/typescript/helpers/caption.d.ts.map +1 -0
  315. package/lib/typescript/helpers/createMainMessageForThread.d.ts.map +1 -1
  316. package/lib/typescript/helpers/createMessageFromXml.d.ts.map +1 -1
  317. package/lib/typescript/helpers/fileKind.d.ts +20 -0
  318. package/lib/typescript/helpers/fileKind.d.ts.map +1 -0
  319. package/lib/typescript/helpers/getDataFromXml.d.ts.map +1 -1
  320. package/lib/typescript/helpers/mapWithConcurrency.d.ts +9 -0
  321. package/lib/typescript/helpers/mapWithConcurrency.d.ts.map +1 -0
  322. package/lib/typescript/helpers/mediaLimits.d.ts +13 -0
  323. package/lib/typescript/helpers/mediaLimits.d.ts.map +1 -0
  324. package/lib/typescript/helpers/mediaSendRetry.d.ts +26 -0
  325. package/lib/typescript/helpers/mediaSendRetry.d.ts.map +1 -0
  326. package/lib/typescript/helpers/mediaWireData.d.ts +16 -0
  327. package/lib/typescript/helpers/mediaWireData.d.ts.map +1 -0
  328. package/lib/typescript/helpers/mentionSpans.d.ts +8 -0
  329. package/lib/typescript/helpers/mentionSpans.d.ts.map +1 -0
  330. package/lib/typescript/helpers/mimeToExtension.d.ts +7 -0
  331. package/lib/typescript/helpers/mimeToExtension.d.ts.map +1 -1
  332. package/lib/typescript/helpers/readReceipts.d.ts +82 -0
  333. package/lib/typescript/helpers/readReceipts.d.ts.map +1 -0
  334. package/lib/typescript/helpers/repairFilenameEncoding.d.ts +12 -0
  335. package/lib/typescript/helpers/repairFilenameEncoding.d.ts.map +1 -0
  336. package/lib/typescript/helpers/safeJson.d.ts +2 -0
  337. package/lib/typescript/helpers/safeJson.d.ts.map +1 -0
  338. package/lib/typescript/helpers/updateMessagesTillLast.d.ts.map +1 -1
  339. package/lib/typescript/hooks/useSendMessage.d.ts +2 -1
  340. package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
  341. package/lib/typescript/i18n/strings.d.ts.map +1 -1
  342. package/lib/typescript/main.d.ts +1 -1
  343. package/lib/typescript/main.d.ts.map +1 -1
  344. package/lib/typescript/networking/api-requests/auth.api.d.ts +2 -0
  345. package/lib/typescript/networking/api-requests/auth.api.d.ts.map +1 -1
  346. package/lib/typescript/networking/stanzaHandlers.d.ts +7 -3
  347. package/lib/typescript/networking/stanzaHandlers.d.ts.map +1 -1
  348. package/lib/typescript/networking/xmpp/handleStanzas.xmpp.d.ts.map +1 -1
  349. package/lib/typescript/networking/xmpp/sendDisplayedMarker.xmpp.d.ts +3 -0
  350. package/lib/typescript/networking/xmpp/sendDisplayedMarker.xmpp.d.ts.map +1 -0
  351. package/lib/typescript/networking/xmpp/sendMediaMessage.xmpp.d.ts +12 -0
  352. package/lib/typescript/networking/xmpp/sendMediaMessage.xmpp.d.ts.map +1 -1
  353. package/lib/typescript/networking/xmpp/sendReceivedReceipt.xmpp.d.ts +3 -0
  354. package/lib/typescript/networking/xmpp/sendReceivedReceipt.xmpp.d.ts.map +1 -0
  355. package/lib/typescript/networking/xmppClient.d.ts +15 -0
  356. package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
  357. package/lib/typescript/roomStore/persistence.d.ts.map +1 -1
  358. package/lib/typescript/roomStore/roomHeapSlice.d.ts +22 -1
  359. package/lib/typescript/roomStore/roomHeapSlice.d.ts.map +1 -1
  360. package/lib/typescript/roomStore/roomsSlice.d.ts +67 -3
  361. package/lib/typescript/roomStore/roomsSlice.d.ts.map +1 -1
  362. package/lib/typescript/types/models/attachment.model.d.ts +52 -0
  363. package/lib/typescript/types/models/attachment.model.d.ts.map +1 -0
  364. package/lib/typescript/types/models/message.model.d.ts +26 -0
  365. package/lib/typescript/types/models/message.model.d.ts.map +1 -1
  366. package/lib/typescript/types/models/modal.model.d.ts +7 -0
  367. package/lib/typescript/types/models/modal.model.d.ts.map +1 -1
  368. package/lib/typescript/types/models/room.model.d.ts +2 -0
  369. package/lib/typescript/types/models/room.model.d.ts.map +1 -1
  370. package/lib/typescript/types/types.d.ts +69 -1
  371. package/lib/typescript/types/types.d.ts.map +1 -1
  372. package/lib/typescript/version.d.ts +1 -1
  373. package/package.json +4 -1
  374. package/plugins/notification-service/Info.plist +31 -0
  375. package/plugins/notification-service/NotificationService.swift +82 -0
  376. package/plugins/withNotificationServiceExtension.js +137 -0
  377. package/src/assets/icons.tsx +20 -2
  378. package/src/components/MainComponents/AlbumGrid.tsx +183 -0
  379. package/src/components/MainComponents/ChatRoom.tsx +4 -2
  380. package/src/components/MainComponents/MediaMessage.tsx +246 -130
  381. package/src/components/MainComponents/MediaUploadOverlay.tsx +102 -0
  382. package/src/components/MessageBubble/MediaCaption.tsx +45 -0
  383. package/src/components/MessageBubble/Message.tsx +71 -3
  384. package/src/components/MessageBubble/MessageInteractions.tsx +2 -1
  385. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +64 -0
  386. package/src/components/RoomComponents/LastMessageItem.tsx +15 -4
  387. package/src/components/RoomComponents/styled/LastAudioMessage.tsx +6 -3
  388. package/src/components/RoomComponents/styled/LastMessageFile.tsx +6 -2
  389. package/src/components/RoomComponents/styled/LastMessagePhoto.tsx +6 -2
  390. package/src/components/RoomComponents/styled/LastMessageVideo.tsx +6 -2
  391. package/src/components/Thread/ThreadWrapper.tsx +5 -9
  392. package/src/components/VideoCalls/ActiveCallBar.tsx +56 -10
  393. package/src/components/VideoCalls/CallIcons.tsx +36 -12
  394. package/src/components/VideoCalls/VideoCallOverlay.tsx +205 -8
  395. package/src/components/VideoCalls/VideoCallSession.tsx +45 -13
  396. package/src/components/VideoCalls/callSessionController.ts +51 -20
  397. package/src/components/VideoCalls/callUiStore.ts +33 -3
  398. package/src/components/VideoCalls/livekitRuntime.ts +13 -0
  399. package/src/components/VideoCalls/useCallKeep.ts +204 -33
  400. package/src/components/VideoCalls/useOngoingCallNotification.ts +113 -0
  401. package/src/components/VideoCalls/usePipTile.ts +214 -0
  402. package/src/components/styled/MediaFilePreview.tsx +49 -34
  403. package/src/components/styled/MessageImage.tsx +21 -5
  404. package/src/components/styled/SendInput.tsx +101 -87
  405. package/src/components/styled/VideoMessage.tsx +34 -9
  406. package/src/context/xmppProvider.tsx +32 -0
  407. package/src/e2ee/fileEnvelope.ts +1 -1
  408. package/src/e2ee/sealedBody.ts +44 -5
  409. package/src/helpers/albumLayout.ts +207 -0
  410. package/src/helpers/attachments.ts +316 -0
  411. package/src/helpers/caption.ts +283 -0
  412. package/src/helpers/createMainMessageForThread.ts +6 -1
  413. package/src/helpers/createMessageFromXml.ts +21 -5
  414. package/src/helpers/fileKind.ts +38 -0
  415. package/src/helpers/getDataFromXml.ts +5 -1
  416. package/src/helpers/mapWithConcurrency.ts +32 -0
  417. package/src/helpers/mediaLimits.ts +27 -0
  418. package/src/helpers/mediaSendRetry.ts +59 -0
  419. package/src/helpers/mediaWireData.ts +57 -0
  420. package/src/helpers/mentionSpans.ts +17 -0
  421. package/src/helpers/mimeToExtension.ts +51 -0
  422. package/src/helpers/readReceipts.ts +219 -0
  423. package/src/helpers/repairFilenameEncoding.ts +82 -0
  424. package/src/helpers/safeJson.ts +11 -0
  425. package/src/helpers/updateMessagesTillLast.tsx +2 -1
  426. package/src/hooks/useSendMessage.tsx +448 -249
  427. package/src/i18n/strings.ts +144 -0
  428. package/src/main.ts +3 -0
  429. package/src/networking/api-requests/auth.api.ts +60 -3
  430. package/src/networking/stanzaHandlers.ts +91 -2
  431. package/src/networking/xmpp/handleStanzas.xmpp.ts +6 -2
  432. package/src/networking/xmpp/sendDisplayedMarker.xmpp.ts +31 -0
  433. package/src/networking/xmpp/sendMediaMessage.xmpp.ts +60 -3
  434. package/src/networking/xmpp/sendReceivedReceipt.xmpp.ts +31 -0
  435. package/src/networking/xmppClient.ts +48 -0
  436. package/src/roomStore/persistence.ts +94 -3
  437. package/src/roomStore/roomHeapSlice.ts +23 -1
  438. package/src/roomStore/roomsSlice.ts +112 -1
  439. package/src/types/models/attachment.model.ts +51 -0
  440. package/src/types/models/message.model.ts +29 -0
  441. package/src/types/models/modal.model.ts +7 -0
  442. package/src/types/models/room.model.ts +2 -0
  443. package/src/types/types.ts +83 -1
  444. package/src/version.ts +1 -1
  445. package/lib/commonjs/components/VideoCalls/useDraggable.js +0 -59
  446. package/lib/commonjs/components/VideoCalls/useDraggable.js.map +0 -1
  447. package/lib/module/components/VideoCalls/useDraggable.js +0 -55
  448. package/lib/module/components/VideoCalls/useDraggable.js.map +0 -1
  449. package/lib/typescript/components/VideoCalls/useDraggable.d.ts +0 -19
  450. package/lib/typescript/components/VideoCalls/useDraggable.d.ts.map +0 -1
  451. package/src/components/VideoCalls/useDraggable.ts +0 -46
@@ -3,21 +3,38 @@ import { Platform } from 'react-native';
3
3
  import { v4 as uuidv4 } from 'uuid';
4
4
  import { useXmppClient } from '../context/xmppProvider';
5
5
  import { useDispatch, useSelector, useStore } from 'react-redux';
6
- import { addRoomMessage, setEditAction } from '../roomStore/roomsSlice';
6
+ import {
7
+ addRoomMessage,
8
+ setEditAction,
9
+ setMediaUploadState,
10
+ setMessageAttachments,
11
+ } from '../roomStore/roomsSlice';
7
12
  import {
8
13
  addMessageToHeap,
9
14
  clearMessageFailure,
10
15
  markMessageFailed,
11
16
  } from '../roomStore/roomHeapSlice';
17
+ import type { FailedMediaFile, FailedMessagePayload } from '../roomStore/roomHeapSlice';
12
18
  import { uploadFileV2 } from '../networking/api-requests/auth.api';
13
19
  import { enqueueOutboundSend, removeOutboundSend } from '../networking/outboundQueue';
14
20
  import { RootState, store } from '../roomStore';
15
21
  import { getGlobalXmppClient } from '../utils/clientRegistry';
16
22
  import { useEventHandlers } from './useEventHandlers';
17
- import type { IConfig, IMessage } from '../types/types';
23
+ import type { IAttachment, IConfig, IMessage } from '../types/types';
18
24
  import { canEditMessage, canSendMediaToRoom, canSendToRoom } from '../e2ee';
19
25
  import { shouldEncrypt } from '../e2ee/send';
20
26
  import type { SealedUpload } from '../e2ee/sealedFiles';
27
+ import { MAX_SEAL_BYTES } from '../e2ee/limits';
28
+ import { mapWithConcurrency } from '../helpers/mapWithConcurrency';
29
+ import { normalizeCaptionInput, type CaptionInput } from '../helpers/caption';
30
+ import { toUploadedAttachment } from '../helpers/attachments';
31
+ import { mediaFileCap, mediaSizeLimit } from '../helpers/mediaLimits';
32
+ import {
33
+ hasMediaRetry,
34
+ registerMediaRetry,
35
+ retryMediaSend,
36
+ unregisterMediaRetry,
37
+ } from '../helpers/mediaSendRetry';
21
38
 
22
39
  // Watchdog: if a message stays "pending" (no server echo) for this
23
40
  // long after the optimistic dispatch, mark it failed so the bubble
@@ -52,10 +69,42 @@ let __sendIdSeq = 0;
52
69
  const nextStanzaId = (prefix: string): string =>
53
70
  `${prefix}-${Date.now()}-${(__sendIdSeq = (__sendIdSeq + 1) >>> 0)}`;
54
71
 
55
- // Hard client-side cap on a single upload. The backend rejects oversized
56
- // bodies with HTTP 413; SendInput blocks oversized files at pick time,
57
- // and this mirrors that limit on every send path (including retries).
58
- const MAX_MEDIA_BYTES = 50 * 1024 * 1024;
72
+ // Uploads in flight per send. A sealed (e2ee) file is encrypted on the device
73
+ // before it goes up, which is slow, so an e2ee send runs fewer at once.
74
+ const MEDIA_UPLOAD_CONCURRENCY = 4;
75
+ const MEDIA_E2EE_UPLOAD_CONCURRENCY = 2;
76
+
77
+ /** The local id of a tile until the upload gives it the server's `_id`. */
78
+ const localAttachmentId = (messageId: string, index: number): string =>
79
+ `${messageId}:${index}`;
80
+
81
+ /** A tile without its local upload state (the file itself is unchanged). */
82
+ const withoutUploadState = (attachment: IAttachment): IAttachment => {
83
+ const copy: IAttachment = { ...attachment };
84
+ delete copy.uploadStatus;
85
+ delete copy.uploadProgress;
86
+ return copy;
87
+ };
88
+
89
+ /** A picker uri as a plain file uri (iOS asset-library scheme rewritten). */
90
+ const normalizeFileUri = (uri: unknown): string =>
91
+ typeof uri === 'string' ? uri.replace(/^assets-library:\/\//, 'file://') : '';
92
+
93
+ /** The file's own uri when it is on this device, for the sender's preview. */
94
+ const localPreviewUrl = (uri: unknown): string | undefined => {
95
+ const normalized = normalizeFileUri(uri);
96
+ return /^(file|content):\/\//.test(normalized) ? normalized : undefined;
97
+ };
98
+
99
+ /** One file as a failed send keeps it: plain fields only, so it persists. */
100
+ const toFailedFile = (file: any, fallbackType: string): FailedMediaFile => ({
101
+ uri: typeof file?.uri === 'string' ? file.uri : undefined,
102
+ type: file?.type || file?.mimeType || fallbackType || undefined,
103
+ name: file?.name || file?.fileName || undefined,
104
+ ...(typeof file?.size === 'number' ? { size: file.size } : {}),
105
+ ...(typeof file?.duration === 'number' ? { duration: file.duration } : {}),
106
+ ...(typeof file?.waveForm === 'string' ? { waveForm: file.waveForm } : {}),
107
+ });
59
108
 
60
109
  export const useSendMessage = (_configOverride?: IConfig) => {
61
110
  const { client } = useXmppClient();
@@ -381,58 +430,100 @@ export const useSendMessage = (_configOverride?: IConfig) => {
381
430
  isReply: boolean = false,
382
431
  isChecked: boolean = false,
383
432
  mainMessage: string = '',
384
- existingId?: string
385
- ) => {
433
+ existingId?: string,
434
+ caption?: CaptionInput
435
+ ): Promise<boolean> => {
386
436
  if (!canSendMediaToRoom(activeRoomJID)) {
387
437
  console.warn('media not sent: room is end-to-end encrypted');
388
- return;
438
+ return false;
389
439
  }
390
440
 
391
441
  client?.onCriticalSend?.(activeRoomJID);
392
442
 
393
- // Defensive 50 MB guard on every send path (SendInput blocks it at
443
+ // One send = one message, whatever the caller handed us: a single file,
444
+ // the composer's array, or the saved list on a retry. Extra files past
445
+ // the cap are dropped here as well as in the composer.
446
+ const files: any[] = (Array.isArray(data) ? data : [data])
447
+ .filter((file) => !!file && typeof file === 'object')
448
+ .slice(0, mediaFileCap(config));
449
+ if (files.length === 0) {return false;}
450
+ const first = files[0];
451
+
452
+ const isE2ee = shouldEncrypt(activeRoomJID);
453
+ const sizeLimit = mediaSizeLimit(config, isE2ee ? MAX_SEAL_BYTES : undefined);
454
+ const captionFields = normalizeCaptionInput(caption);
455
+ const id = existingId || `send-media-message:${uuidv4()}`;
456
+
457
+ // Everything a retry needs to replay this send, in selection order.
458
+ const failedPayload = (): FailedMessagePayload => ({
459
+ kind: 'media',
460
+ id,
461
+ roomJID: activeRoomJID,
462
+ data: toFailedFile(first, type),
463
+ type,
464
+ isReply,
465
+ isChecked,
466
+ mainMessage,
467
+ files: files.map((file) => toFailedFile(file, type)),
468
+ ...(captionFields
469
+ ? {
470
+ caption: captionFields.caption,
471
+ ...(captionFields.mentions ? { captionMentions: captionFields.mentions } : {}),
472
+ }
473
+ : {}),
474
+ });
475
+
476
+ // Defensive size guard on every send path (the composer blocks it at
394
477
  // pick time, but retries replay the saved payload straight here).
395
- if (typeof data?.size === 'number' && data.size > MAX_MEDIA_BYTES) {
396
- console.warn('media exceeds 50MB cap — not uploading', {
397
- name: data?.name,
398
- size: data?.size,
478
+ const oversize = files.find(
479
+ (file) => typeof file?.size === 'number' && file.size > sizeLimit
480
+ );
481
+ if (oversize) {
482
+ console.warn('media exceeds the size cap - not uploading', {
483
+ name: oversize?.name,
484
+ size: oversize?.size,
399
485
  });
486
+ const error = new Error(
487
+ `File exceeds the ${Math.round(sizeLimit / (1024 * 1024))} MB upload limit`
488
+ );
400
489
  if (existingId) {
401
- dispatch(
402
- markMessageFailed({
403
- kind: 'media',
404
- id: existingId,
405
- roomJID: activeRoomJID,
406
- data,
407
- type,
408
- isReply,
409
- isChecked,
410
- mainMessage,
411
- })
412
- );
490
+ dispatch(markMessageFailed(failedPayload()));
413
491
  }
414
492
  handleMessageFailed({
415
493
  message: 'media',
416
494
  roomJID: activeRoomJID,
417
- error: new Error('File exceeds the 50 MB upload limit'),
495
+ error,
418
496
  messageType: 'media',
419
497
  });
420
- return;
498
+ return false;
421
499
  }
422
500
 
423
- const id = existingId || `send-media-message:${uuidv4()}`;
424
501
  const optimisticTimestamp = Date.now();
425
502
  const optimisticDate = new Date(optimisticTimestamp).toISOString();
426
503
  const selfId =
427
504
  (user as any)?.xmppUsername || (user as any)?.walletAddress || '';
428
505
  const displayName =
429
506
  `${user?.firstName || ''} ${user?.lastName || ''}`.trim() || selfId;
430
- const fileSizeStr = data?.size != null ? String(data.size) : '';
507
+ const normalizedFileUri = normalizeFileUri(first?.uri);
508
+ const fileSizeStr = first?.size != null ? String(first.size) : '';
431
509
 
432
- const normalizedFileUri =
433
- typeof data?.uri === 'string'
434
- ? data.uri.replace(/^assets-library:\/\//, 'file://')
435
- : '';
510
+ // One tile per file, in selection order. A tile's id is local until the
511
+ // upload returns the server's `_id`; the tile's upload state is keyed on it.
512
+ const tiles: IAttachment[] = files.map((file, index) => {
513
+ const localUrl = localPreviewUrl(file?.uri);
514
+ return {
515
+ attachmentId: localAttachmentId(id, index),
516
+ location: '',
517
+ mimetype: file?.type || file?.mimeType || type,
518
+ originalName: file?.name,
519
+ fileName: file?.name,
520
+ size: file?.size != null ? String(file.size) : undefined,
521
+ duration: file?.duration != null ? String(file.duration) : undefined,
522
+ ...(localUrl ? { localUrl } : {}),
523
+ uploadStatus: 'uploading',
524
+ uploadProgress: 0,
525
+ };
526
+ });
436
527
 
437
528
  if (!config?.disableSentLogic && !existingId) {
438
529
  dispatch(
@@ -454,17 +545,24 @@ export const useSendMessage = (_configOverride?: IConfig) => {
454
545
  xmppId: id,
455
546
  xmppFrom: `${activeRoomJID}/${selfId}`,
456
547
  isMediafile: 'true',
457
- fileName: data?.name,
548
+ fileName: first?.name,
458
549
  location: normalizedFileUri,
459
550
  locationPreview: normalizedFileUri,
460
551
  mimetype: type,
461
- originalName: data?.name,
552
+ originalName: first?.name,
462
553
  size: fileSizeStr,
463
- duration: data?.duration,
464
- waveForm: data?.waveForm,
554
+ duration: first?.duration,
555
+ waveForm: first?.waveForm,
556
+ attachments: tiles,
465
557
  isReply: isReply ? 'true' : 'false',
466
558
  showInChannel: `${isChecked}`,
467
559
  mainMessage,
560
+ ...(captionFields
561
+ ? {
562
+ caption: captionFields.caption,
563
+ captionMentions: captionFields.mentions,
564
+ }
565
+ : {}),
468
566
  } as any as IMessage,
469
567
  })
470
568
  );
@@ -488,193 +586,267 @@ export const useSendMessage = (_configOverride?: IConfig) => {
488
586
  );
489
587
  }
490
588
 
491
- // Silent-failure watchdog for media-send (bug #18). Same shape
492
- // as the text path: if the optimistic bubble never gets the echo
493
- // (e.g. upload completed but XMPP send is buffered offline), flip
494
- // to failed → tap to retry.
495
- const mediaWatchdog = setTimeout(() => {
496
- const state = reduxStore.getState();
497
- const roomMsgs = state.rooms?.rooms?.[activeRoomJID]?.messages || [];
498
- const stillPending = roomMsgs.find(
499
- (m: any) => m.id === id && m.pending
500
- );
501
- const alreadyFailed = state.roomHeapSlice?.failedMessages?.[id];
502
- if (stillPending && !alreadyFailed) {
503
- console.warn(
504
- `Media-send watchdog fired — ${id} stuck pending > ${PENDING_WATCHDOG_MS}ms; marking failed`
589
+ // Silent-failure watchdog for media-send (bug #18). Re-armed on every
590
+ // retry, so a retried send gets its own window. While files are still
591
+ // going up it is not a failure: it waits one more window instead.
592
+ const armWatchdog = (): ReturnType<typeof setTimeout> =>
593
+ setTimeout(() => {
594
+ if (running) {
595
+ mediaWatchdog = armWatchdog();
596
+ return;
597
+ }
598
+ const state = reduxStore.getState();
599
+ const roomMsgs = state.rooms?.rooms?.[activeRoomJID]?.messages || [];
600
+ const stillPending = roomMsgs.find(
601
+ (m: any) => m.id === id && m.pending
505
602
  );
506
- // See the text-send watchdog above: drop the buffered replay so
507
- // a later onOnline flush can't silently resend behind the
508
- // "failed / tap to retry" bubble.
509
- removeOutboundSend(id);
510
- dispatch(
511
- markMessageFailed({
512
- kind: 'media',
513
- id,
603
+ const alreadyFailed = state.roomHeapSlice?.failedMessages?.[id];
604
+ if (stillPending && !alreadyFailed) {
605
+ console.warn(
606
+ `Media-send watchdog fired - ${id} stuck pending > ${MEDIA_PENDING_WATCHDOG_MS}ms; marking failed`
607
+ );
608
+ // See the text-send watchdog above: drop the buffered replay so
609
+ // a later onOnline flush can't silently resend behind the
610
+ // "failed / tap to retry" bubble.
611
+ removeOutboundSend(id);
612
+ dispatch(markMessageFailed(failedPayload()));
613
+ handleMessageFailed({
614
+ message: 'media',
514
615
  roomJID: activeRoomJID,
515
- data,
516
- type,
517
- isReply,
518
- isChecked,
519
- mainMessage,
520
- })
521
- );
522
- handleMessageFailed({
523
- message: 'media',
616
+ error: new Error('Media send timed out (no server echo)'),
617
+ messageType: 'media',
618
+ });
619
+ }
620
+ }, MEDIA_PENDING_WATCHDOG_MS);
621
+ const uploaded: Array<IAttachment | undefined> = files.map(() => undefined);
622
+ const rawResults: Array<any | undefined> = files.map(() => undefined);
623
+ const sealedUploads: Array<SealedUpload | undefined> = files.map(() => undefined);
624
+ // Per-file key material, kept apart from the sealed upload: the upload
625
+ // is disposed once the message is assembled, but a send that fails after
626
+ // that point is retried and must still carry every key.
627
+ const keyMaterials: Array<string | undefined> = files.map(() => undefined);
628
+ let delivered = false;
629
+ let running = false;
630
+ const concurrency = isE2ee ? MEDIA_E2EE_UPLOAD_CONCURRENCY : MEDIA_UPLOAD_CONCURRENCY;
631
+ let mediaWatchdog = armWatchdog();
632
+
633
+ const publishTile = (
634
+ index: number,
635
+ patch: { uploadStatus?: 'uploading' | 'failed' | null; uploadProgress?: number }
636
+ ) => {
637
+ const attachmentId = tiles[index]?.attachmentId;
638
+ if (config?.disableSentLogic || !attachmentId) {return;}
639
+ dispatch(
640
+ setMediaUploadState({
524
641
  roomJID: activeRoomJID,
525
- error: new Error('Media send timed out (no server echo)'),
526
- messageType: 'media',
527
- });
528
- }
529
- }, MEDIA_PENDING_WATCHDOG_MS);
530
-
531
- // Help RN's FormData polyfill build a proper multipart/file part
532
- // (instead of serialising the JS object as JSON). On iOS the
533
- // picker can return ph:// or assets-library:// URIs for some
534
- // legacy library items — those can't be read as files. Coerce
535
- // to a file:// scheme when possible so the body is a real blob.
536
- const fileBlob = {
537
- uri: normalizedFileUri || data?.uri,
538
- type:
539
- data?.type ||
540
- data?.mimeType ||
541
- type ||
542
- 'application/octet-stream',
543
- name: data?.name || data?.fileName || `media_${Date.now()}`,
642
+ messageId: id,
643
+ attachmentId,
644
+ ...patch,
645
+ })
646
+ );
544
647
  };
545
648
 
546
- let sealed: SealedUpload | undefined;
649
+ const missingIndices = () =>
650
+ uploaded.reduce<number[]>(
651
+ (acc, item, index) => (item ? acc : [...acc, index]),
652
+ []
653
+ );
547
654
 
548
- const tryUpload = async () => {
549
- if (shouldEncrypt(activeRoomJID) && !sealed) {
655
+ const uploadOne = async (index: number): Promise<void> => {
656
+ const file = files[index];
657
+ publishTile(index, { uploadStatus: 'uploading', uploadProgress: 0 });
658
+
659
+ // Helps RN's FormData polyfill build a proper multipart/file part
660
+ // (instead of serialising the JS object as JSON). On iOS the picker
661
+ // can return ph:// or assets-library:// URIs; coerce to file:// when
662
+ // possible so the body is a real blob.
663
+ const fileBlob = {
664
+ uri: normalizeFileUri(file?.uri) || file?.uri,
665
+ type: file?.type || file?.mimeType || type || 'application/octet-stream',
666
+ name: file?.name || file?.fileName || `media_${Date.now()}_${index}`,
667
+ };
668
+
669
+ // In an e2ee room each file is sealed before it leaves the device:
670
+ // the server only ever sees opaque bytes under a random name. The
671
+ // per-file keys ride in the encrypted <body> (see sendMediaMessage).
672
+ if (isE2ee) {
673
+ sealedUploads[index]?.dispose();
674
+ sealedUploads[index] = undefined;
550
675
  const { sealPickedFile } =
551
676
  require('../e2ee/sealedFiles') as typeof import('../e2ee/sealedFiles');
552
- sealed = await sealPickedFile({
677
+ sealedUploads[index] = await sealPickedFile({
553
678
  uri: fileBlob.uri,
554
679
  name: fileBlob.name,
555
680
  type: fileBlob.type,
556
- size: typeof data?.size === 'number' ? data.size : undefined,
681
+ size: typeof file?.size === 'number' ? file.size : undefined,
557
682
  });
558
683
  }
684
+ const sealed = sealedUploads[index];
685
+ if (sealed) {keyMaterials[index] = sealed.keyMaterial;}
559
686
  const part = sealed
560
687
  ? { uri: sealed.uri, type: 'application/octet-stream', name: sealed.name }
561
688
  : fileBlob;
562
- const options = { clientEncrypted: !!sealed };
563
- const fd = new FormData();
564
- fd.append('files', part as any);
689
+ const options = {
690
+ clientEncrypted: !!sealed,
691
+ onProgress: (percent: number) =>
692
+ publishTile(index, { uploadProgress: percent }),
693
+ };
694
+ const post = (field: 'files' | 'file') => {
695
+ const fd = new FormData();
696
+ fd.append(field, part as any);
697
+ return uploadFileV2(fd, activeRoomJID, options);
698
+ };
699
+
700
+ let response: { data: any };
565
701
  try {
566
- return await uploadFileV2(fd, activeRoomJID, options);
702
+ response = await post('files');
567
703
  } catch (err: any) {
568
- if (err?.response?.status === 500) {
569
- console.warn(
570
- 'upload "files" field returned 500 — retrying with "file" (singular)',
571
- { name: part.name, type: part.type }
572
- );
573
- const fd2 = new FormData();
574
- fd2.append('file', part as any);
575
- return await uploadFileV2(fd2, activeRoomJID, options);
576
- }
577
- throw err;
704
+ if (err?.response?.status !== 500) {throw err;}
705
+ console.warn(
706
+ 'upload "files" field returned 500 - retrying with "file" (singular)',
707
+ { name: part.name, type: part.type }
708
+ );
709
+ response = await post('file');
578
710
  }
711
+
712
+ const raw = Array.isArray(response?.data?.results)
713
+ ? response.data.results[0]
714
+ : undefined;
715
+ if (!raw) {throw new Error('media_upload_empty_response');}
716
+
717
+ uploaded[index] = toUploadedAttachment(raw);
718
+ rawResults[index] = raw;
719
+ // The bytes are up; the message waits for its siblings.
720
+ publishTile(index, { uploadStatus: 'uploading', uploadProgress: 100 });
579
721
  };
580
722
 
581
- try {
582
- const response = await tryUpload();
583
- // Upload succeeded — the stanza is now in flight. If the
584
- // server echo arrives within the watchdog window the bubble
585
- // flips normally; if not, the watchdog still fires and marks
586
- // it failed. We don't clear the timer on upload success because
587
- // the XMPP send could still fail silently.
588
-
589
- for (const item of response.data.results) {
590
- const messagePayload = {
591
- firstName: user.firstName,
592
- lastName: user.lastName,
593
- walletAddress: user.walletAddress,
594
- createdAt: item.createdAt,
595
- expiresAt: item.expiresAt,
596
- fileName: item.filename,
597
- isVisible: item?.isVisible,
598
- location: item.location,
599
- locationPreview: item.locationPreview,
600
- mimetype: item.mimetype,
601
- originalName: item?.originalname,
602
- ownerKey: item?.ownerKey,
603
- size: item.size,
604
- duration: item?.duration ?? data?.duration,
605
- waveForm: data?.waveForm,
606
- updatedAt: item?.updatedAt,
607
- userId: item?.userId,
608
- attachmentId: item?._id,
609
- wrappable: true,
610
- roomJid: activeRoomJID,
611
- showInChannel: isChecked,
612
- isReply,
613
- mainMessage,
614
- isPrivate: item?.isPrivate,
615
- __v: item.__v,
616
- e2eeKeys: sealed ? [sealed.keyMaterial] : undefined,
617
- };
618
- if (sealed) {
723
+ const sendAssembled = async (): Promise<void> => {
724
+ const raws = rawResults as any[];
725
+ const attachments = uploaded as IAttachment[];
726
+ const head = raws[0];
727
+
728
+ const messagePayload = {
729
+ firstName: user.firstName,
730
+ lastName: user.lastName,
731
+ walletAddress: user.walletAddress,
732
+ createdAt: head.createdAt,
733
+ expiresAt: head.expiresAt,
734
+ fileName: head.filename,
735
+ isVisible: head?.isVisible,
736
+ location: head.location,
737
+ locationPreview: head.locationPreview,
738
+ mimetype: head.mimetype,
739
+ originalName: head?.originalname,
740
+ ownerKey: head?.ownerKey,
741
+ size: head.size,
742
+ duration: head?.duration ?? first?.duration,
743
+ waveForm: first?.waveForm,
744
+ updatedAt: head?.updatedAt,
745
+ userId: head?.userId,
746
+ attachmentId: head?._id,
747
+ // Only when there is more than one file: sendMediaMessage stamps
748
+ // the list on <data> and leaves a single-file stanza untouched.
749
+ attachments: attachments.length > 1 ? attachments : undefined,
750
+ caption: captionFields?.caption,
751
+ captionMentions: captionFields?.mentions,
752
+ wrappable: true,
753
+ roomJid: activeRoomJID,
754
+ showInChannel: isChecked,
755
+ isReply,
756
+ mainMessage,
757
+ isPrivate: head?.isPrivate,
758
+ __v: head.__v,
759
+ // One key per sealed file, in selection order.
760
+ e2eeKeys: isE2ee
761
+ ? (keyMaterials as string[])
762
+ : undefined,
763
+ };
764
+
765
+ // The local copies of sealed files are remembered for the sender and
766
+ // the scratch ciphertext is dropped.
767
+ if (isE2ee) {
768
+ const { rememberOpened } =
769
+ require('../e2ee/sealedFiles') as typeof import('../e2ee/sealedFiles');
770
+ for (let index = 0; index < files.length; index++) {
771
+ const item = attachments[index];
619
772
  try {
620
- const { rememberOpened } =
621
- require('../e2ee/sealedFiles') as typeof import('../e2ee/sealedFiles');
622
773
  await rememberOpened(item.location, {
623
- uri: fileBlob.uri,
624
- name: fileBlob.name,
625
- type: fileBlob.type,
626
- size: typeof data?.size === 'number' ? data.size : undefined,
774
+ uri: normalizeFileUri(files[index]?.uri) || files[index]?.uri,
775
+ name: files[index]?.name,
776
+ type: files[index]?.type || files[index]?.mimeType || type,
777
+ size: typeof files[index]?.size === 'number' ? files[index].size : undefined,
627
778
  });
628
779
  } catch (err) {
629
780
  console.warn('could not keep the sent attachment', err);
630
781
  }
631
- sealed.dispose();
782
+ sealedUploads[index]?.dispose();
783
+ sealedUploads[index] = undefined;
632
784
  }
633
- // Stanza id == placeholder id so the MUC echo's outer
634
- // <message id="..."> matches xmppId on the placeholder and
635
- // insertMessageWithDelimiter merges in place + flips pending.
636
- // Same online-only guard as the text path: a surviving-but-offline
637
- // client must NOT be used (the stanza would vanish into a dead
638
- // socket) — fall through to enqueue so the reconnect flush replays.
639
- const effectiveMediaClient: any = [
640
- client,
641
- getGlobalXmppClient(),
642
- store.getState()?.chatSettingStore?.client,
643
- ].find((c: any) => c && c.status === 'online');
644
- if (effectiveMediaClient) {
645
- effectiveMediaClient.setActiveRoomJid?.(activeRoomJID);
646
- await Promise.resolve(
647
- effectiveMediaClient.prioritizeRoomPresence?.(activeRoomJID)
648
- ).catch(() => false);
649
- effectiveMediaClient.sendMediaMessageStanza(
650
- activeRoomJID,
651
- messagePayload,
652
- id
653
- );
654
- try {
655
- effectiveMediaClient.ensureStreamAlive?.();
656
- } catch {}
657
- } else {
658
- const anyMediaClient: any =
659
- client ||
660
- getGlobalXmppClient() ||
661
- store.getState()?.chatSettingStore?.client;
662
- try {
663
- anyMediaClient?.forceReconnect?.();
664
- } catch {}
665
- enqueueOutboundSend({
666
- optimisticId: id,
785
+ }
786
+
787
+ // Replace the optimistic tiles with the uploaded files. A sealed
788
+ // message keeps its local tiles (the server copies are ciphertext) and
789
+ // only drops the upload state. `pending` is left alone: only the
790
+ // server echo confirms the message.
791
+ if (!config?.disableSentLogic) {
792
+ dispatch(
793
+ setMessageAttachments({
667
794
  roomJID: activeRoomJID,
668
- enqueuedAt: Date.now(),
669
- // Match the media watchdog so a reconnect within the (longer)
670
- // media window still auto-replays the buffered stanza.
671
- ttlMs: MEDIA_PENDING_WATCHDOG_MS,
672
- send: (c) =>
673
- c.sendMediaMessageStanza(activeRoomJID, messagePayload, id),
674
- });
675
- }
795
+ messageId: id,
796
+ attachments: isE2ee
797
+ ? tiles.map(withoutUploadState)
798
+ : attachments.map((attachment, index) => {
799
+ const localUrl = tiles[index]?.localUrl;
800
+ return localUrl ? { ...attachment, localUrl } : attachment;
801
+ }),
802
+ })
803
+ );
804
+ }
805
+
806
+ const effectiveMediaClient: any = [
807
+ client,
808
+ getGlobalXmppClient(),
809
+ store.getState()?.chatSettingStore?.client,
810
+ ].find((c: any) => c && c.status === 'online');
811
+ if (effectiveMediaClient) {
812
+ effectiveMediaClient.setActiveRoomJid?.(activeRoomJID);
813
+ await Promise.resolve(
814
+ effectiveMediaClient.prioritizeRoomPresence?.(activeRoomJID)
815
+ ).catch(() => false);
816
+ effectiveMediaClient.sendMediaMessageStanza(activeRoomJID, messagePayload, id);
817
+ try {
818
+ effectiveMediaClient.ensureStreamAlive?.();
819
+ } catch {}
820
+ } else {
821
+ // Same online-only guard as the text path: a surviving-but-offline
822
+ // client must NOT be used. Buffer the stanza for the reconnect flush.
823
+ const anyMediaClient: any =
824
+ client ||
825
+ getGlobalXmppClient() ||
826
+ store.getState()?.chatSettingStore?.client;
827
+ try {
828
+ anyMediaClient?.forceReconnect?.();
829
+ } catch {}
830
+ enqueueOutboundSend({
831
+ optimisticId: id,
832
+ roomJID: activeRoomJID,
833
+ enqueuedAt: Date.now(),
834
+ // Match the media watchdog so a reconnect within the (longer)
835
+ // media window still auto-replays the buffered stanza.
836
+ ttlMs: MEDIA_PENDING_WATCHDOG_MS,
837
+ send: (c) => c.sendMediaMessageStanza(activeRoomJID, messagePayload, id),
838
+ });
676
839
  }
677
840
 
841
+ // The upload time is spent: the wait for the server echo gets a full
842
+ // watchdog window of its own, not what was left of the upload's.
843
+ clearTimeout(mediaWatchdog);
844
+ mediaWatchdog = armWatchdog();
845
+
846
+ delivered = true;
847
+ unregisterMediaRetry(id);
848
+ dispatch(clearMessageFailure(id));
849
+
678
850
  await handleMessageSent({
679
851
  message: 'media',
680
852
  roomJID: activeRoomJID,
@@ -687,63 +859,79 @@ export const useSendMessage = (_configOverride?: IConfig) => {
687
859
  fileData: data,
688
860
  fileType: type,
689
861
  messageId: id,
690
- uploadResults: response.data.results,
862
+ uploadResults: raws,
691
863
  },
692
864
  });
693
- } catch (error: any) {
694
- // Upload (or stanza send) threw an explicit error. Clear the
695
- // watchdog so it doesn't double-fire `markMessageFailed`.
696
- clearTimeout(mediaWatchdog);
697
- sealed?.dispose();
698
- // Surface the real server payload so we can see why the upload
699
- // was rejected (axios collapses the message to "Request failed
700
- // with status code N"; the actual reason lives in
701
- // `error.response.data`).
702
- const uriStr = typeof data?.uri === 'string' ? data.uri : '';
703
- const uriScheme = uriStr.match(/^([a-z]+):/i)?.[1] ?? 'unknown';
704
- console.error('upload failed', {
705
- status: error?.response?.status,
706
- statusText: error?.response?.statusText,
707
- serverBody: error?.response?.data,
708
- requestUrl: error?.config?.url,
709
- axiosMessage: error?.message,
710
- axiosCode: error?.code,
711
- axiosCauseMessage: error?.cause?.message,
712
- uri: uriStr,
713
- uriScheme,
714
- mime: fileBlob.type,
715
- fileSizeBytes: data?.size ?? null,
716
- fileName: fileBlob.name,
717
- platformOS: Platform.OS,
718
- platformVersion: Platform.Version,
719
- });
720
- dispatch(
721
- markMessageFailed({
722
- kind: 'media',
723
- id,
724
- roomJID: activeRoomJID,
725
- data,
726
- type,
727
- isReply,
728
- isChecked,
729
- mainMessage,
730
- })
731
- );
865
+ };
866
+
867
+ const failMessage = (error: unknown) => {
868
+ console.error('Media send failed:', error);
869
+ // Every file stays held: the bubble shows Failed, and Retry replays
870
+ // the files that did not make it (in-session) or the whole list.
871
+ dispatch(markMessageFailed(failedPayload()));
732
872
  handleMessageFailed({
733
873
  message: 'media',
734
874
  roomJID: activeRoomJID,
735
875
  error: error as Error,
736
876
  messageType: 'media',
737
877
  });
738
- }
878
+ };
879
+
880
+ /**
881
+ * Uploads `indices` (a retry passes only the files that failed), then
882
+ * sends once nothing is missing. Any failure leaves the message failed
883
+ * with its tiles marked, ready for a retry.
884
+ */
885
+ const run = async (indices: number[]): Promise<boolean> => {
886
+ if (delivered || running) {return false;}
887
+ running = true;
888
+ clearTimeout(mediaWatchdog);
889
+ mediaWatchdog = armWatchdog();
890
+ try {
891
+ await mapWithConcurrency(indices, concurrency, async (index) => {
892
+ try {
893
+ await uploadOne(index);
894
+ } catch (error: any) {
895
+ console.error('upload failed', {
896
+ status: error?.response?.status,
897
+ serverBody: error?.response?.data,
898
+ axiosMessage: error?.message,
899
+ fileName: files[index]?.name,
900
+ fileSizeBytes: files[index]?.size ?? null,
901
+ platformOS: Platform.OS,
902
+ });
903
+ publishTile(index, { uploadStatus: 'failed', uploadProgress: 0 });
904
+ }
905
+ });
906
+ if (missingIndices().length > 0) {
907
+ throw new Error('media_upload_failed');
908
+ }
909
+ await sendAssembled();
910
+ return true;
911
+ } catch (error) {
912
+ failMessage(error);
913
+ return false;
914
+ } finally {
915
+ running = false;
916
+ }
917
+ };
918
+
919
+ registerMediaRetry(id, (indices?: number[]) =>
920
+ run(
921
+ missingIndices().filter(
922
+ (position) => indices === undefined || indices.includes(position)
923
+ )
924
+ )
925
+ );
926
+
927
+ return run(files.map((_, index) => index));
739
928
  },
740
929
  [client, config, user, dispatch, handleMessageSent, handleMessageFailed, reduxStore]
741
930
  );
742
931
 
743
- // Retry a previously-failed send by replaying the saved payload from
744
- // the heap. Clears the failure flag first so the bubble flips back to
745
- // "sending..."; if the retry fails again `markMessageFailed` re-arms
746
- // it. No-op when the id isn't in the failed map.
932
+ // Retry a previously-failed send. An in-session send re-runs only the files
933
+ // still missing (its runner is still registered); after a restart the
934
+ // saved payload is replayed as a whole. No-op when the id isn't failed.
747
935
  const retryMessage = useCallback(
748
936
  async (failedId: string) => {
749
937
  const payload =
@@ -783,17 +971,28 @@ export const useSendMessage = (_configOverride?: IConfig) => {
783
971
  // instead of spawning a duplicate bubble.
784
972
  failedId
785
973
  );
786
- } else {
787
- await sendMedia(
788
- payload.data,
789
- payload.type,
790
- payload.roomJID,
791
- payload.isReply,
792
- payload.isChecked,
793
- payload.mainMessage,
794
- failedId
795
- );
974
+ return;
975
+ }
976
+ if (hasMediaRetry(failedId)) {
977
+ await retryMediaSend(failedId);
978
+ return;
796
979
  }
980
+ const list =
981
+ Array.isArray(payload.files) && payload.files.length > 0
982
+ ? payload.files
983
+ : payload.data;
984
+ await sendMedia(
985
+ list,
986
+ payload.type,
987
+ payload.roomJID,
988
+ payload.isReply,
989
+ payload.isChecked,
990
+ payload.mainMessage,
991
+ failedId,
992
+ payload.caption
993
+ ? { text: payload.caption, mentions: payload.captionMentions }
994
+ : undefined
995
+ );
797
996
  },
798
997
  [reduxStore, dispatch, handleMessageRetry, sendMessage, sendMedia]
799
998
  );