@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
@@ -5,7 +5,7 @@ import { Platform } from 'react-native';
5
5
  import { v4 as uuidv4 } from 'uuid';
6
6
  import { useXmppClient } from '../context/xmppProvider';
7
7
  import { useDispatch, useSelector, useStore } from 'react-redux';
8
- import { addRoomMessage, setEditAction } from '../roomStore/roomsSlice';
8
+ import { addRoomMessage, setEditAction, setMediaUploadState, setMessageAttachments } from '../roomStore/roomsSlice';
9
9
  import { addMessageToHeap, clearMessageFailure, markMessageFailed } from '../roomStore/roomHeapSlice';
10
10
  import { uploadFileV2 } from '../networking/api-requests/auth.api';
11
11
  import { enqueueOutboundSend, removeOutboundSend } from '../networking/outboundQueue';
@@ -14,6 +14,13 @@ import { getGlobalXmppClient } from '../utils/clientRegistry';
14
14
  import { useEventHandlers } from './useEventHandlers';
15
15
  import { canEditMessage, canSendMediaToRoom, canSendToRoom } from '../e2ee';
16
16
  import { shouldEncrypt } from '../e2ee/send';
17
+ import { MAX_SEAL_BYTES } from '../e2ee/limits';
18
+ import { mapWithConcurrency } from '../helpers/mapWithConcurrency';
19
+ import { normalizeCaptionInput } from '../helpers/caption';
20
+ import { toUploadedAttachment } from '../helpers/attachments';
21
+ import { mediaFileCap, mediaSizeLimit } from '../helpers/mediaLimits';
22
+ import { hasMediaRetry, registerMediaRetry, retryMediaSend, unregisterMediaRetry } from '../helpers/mediaSendRetry';
23
+
17
24
  // Watchdog: if a message stays "pending" (no server echo) for this
18
25
  // long after the optimistic dispatch, mark it failed so the bubble
19
26
  // flips out of the "sending..." state. Covers the silent-failure
@@ -46,10 +53,48 @@ const MEDIA_PENDING_WATCHDOG_MS = 30_000;
46
53
  let __sendIdSeq = 0;
47
54
  const nextStanzaId = prefix => `${prefix}-${Date.now()}-${__sendIdSeq = __sendIdSeq + 1 >>> 0}`;
48
55
 
49
- // Hard client-side cap on a single upload. The backend rejects oversized
50
- // bodies with HTTP 413; SendInput blocks oversized files at pick time,
51
- // and this mirrors that limit on every send path (including retries).
52
- const MAX_MEDIA_BYTES = 50 * 1024 * 1024;
56
+ // Uploads in flight per send. A sealed (e2ee) file is encrypted on the device
57
+ // before it goes up, which is slow, so an e2ee send runs fewer at once.
58
+ const MEDIA_UPLOAD_CONCURRENCY = 4;
59
+ const MEDIA_E2EE_UPLOAD_CONCURRENCY = 2;
60
+
61
+ /** The local id of a tile until the upload gives it the server's `_id`. */
62
+ const localAttachmentId = (messageId, index) => `${messageId}:${index}`;
63
+
64
+ /** A tile without its local upload state (the file itself is unchanged). */
65
+ const withoutUploadState = attachment => {
66
+ const copy = {
67
+ ...attachment
68
+ };
69
+ delete copy.uploadStatus;
70
+ delete copy.uploadProgress;
71
+ return copy;
72
+ };
73
+
74
+ /** A picker uri as a plain file uri (iOS asset-library scheme rewritten). */
75
+ const normalizeFileUri = uri => typeof uri === 'string' ? uri.replace(/^assets-library:\/\//, 'file://') : '';
76
+
77
+ /** The file's own uri when it is on this device, for the sender's preview. */
78
+ const localPreviewUrl = uri => {
79
+ const normalized = normalizeFileUri(uri);
80
+ return /^(file|content):\/\//.test(normalized) ? normalized : undefined;
81
+ };
82
+
83
+ /** One file as a failed send keeps it: plain fields only, so it persists. */
84
+ const toFailedFile = (file, fallbackType) => ({
85
+ uri: typeof file?.uri === 'string' ? file.uri : undefined,
86
+ type: file?.type || file?.mimeType || fallbackType || undefined,
87
+ name: file?.name || file?.fileName || undefined,
88
+ ...(typeof file?.size === 'number' ? {
89
+ size: file.size
90
+ } : {}),
91
+ ...(typeof file?.duration === 'number' ? {
92
+ duration: file.duration
93
+ } : {}),
94
+ ...(typeof file?.waveForm === 'string' ? {
95
+ waveForm: file.waveForm
96
+ } : {})
97
+ });
53
98
  export const useSendMessage = _configOverride => {
54
99
  const {
55
100
  client
@@ -300,47 +345,91 @@ export const useSendMessage = _configOverride => {
300
345
  });
301
346
  }
302
347
  }, [editAction, client, user, langSource, dispatch, handleMessageSent, handleMessageEdited, handleMessageFailed, reduxStore]);
303
- const sendMedia = useCallback(async (data, type, activeRoomJID, isReply = false, isChecked = false, mainMessage = '', existingId) => {
348
+ const sendMedia = useCallback(async (data, type, activeRoomJID, isReply = false, isChecked = false, mainMessage = '', existingId, caption) => {
304
349
  if (!canSendMediaToRoom(activeRoomJID)) {
305
350
  console.warn('media not sent: room is end-to-end encrypted');
306
- return;
351
+ return false;
307
352
  }
308
353
  client?.onCriticalSend?.(activeRoomJID);
309
354
 
310
- // Defensive 50 MB guard on every send path (SendInput blocks it at
355
+ // One send = one message, whatever the caller handed us: a single file,
356
+ // the composer's array, or the saved list on a retry. Extra files past
357
+ // the cap are dropped here as well as in the composer.
358
+ const files = (Array.isArray(data) ? data : [data]).filter(file => !!file && typeof file === 'object').slice(0, mediaFileCap(config));
359
+ if (files.length === 0) {
360
+ return false;
361
+ }
362
+ const first = files[0];
363
+ const isE2ee = shouldEncrypt(activeRoomJID);
364
+ const sizeLimit = mediaSizeLimit(config, isE2ee ? MAX_SEAL_BYTES : undefined);
365
+ const captionFields = normalizeCaptionInput(caption);
366
+ const id = existingId || `send-media-message:${uuidv4()}`;
367
+
368
+ // Everything a retry needs to replay this send, in selection order.
369
+ const failedPayload = () => ({
370
+ kind: 'media',
371
+ id,
372
+ roomJID: activeRoomJID,
373
+ data: toFailedFile(first, type),
374
+ type,
375
+ isReply,
376
+ isChecked,
377
+ mainMessage,
378
+ files: files.map(file => toFailedFile(file, type)),
379
+ ...(captionFields ? {
380
+ caption: captionFields.caption,
381
+ ...(captionFields.mentions ? {
382
+ captionMentions: captionFields.mentions
383
+ } : {})
384
+ } : {})
385
+ });
386
+
387
+ // Defensive size guard on every send path (the composer blocks it at
311
388
  // pick time, but retries replay the saved payload straight here).
312
- if (typeof data?.size === 'number' && data.size > MAX_MEDIA_BYTES) {
313
- console.warn('media exceeds 50MB cap — not uploading', {
314
- name: data?.name,
315
- size: data?.size
389
+ const oversize = files.find(file => typeof file?.size === 'number' && file.size > sizeLimit);
390
+ if (oversize) {
391
+ console.warn('media exceeds the size cap - not uploading', {
392
+ name: oversize?.name,
393
+ size: oversize?.size
316
394
  });
395
+ const error = new Error(`File exceeds the ${Math.round(sizeLimit / (1024 * 1024))} MB upload limit`);
317
396
  if (existingId) {
318
- dispatch(markMessageFailed({
319
- kind: 'media',
320
- id: existingId,
321
- roomJID: activeRoomJID,
322
- data,
323
- type,
324
- isReply,
325
- isChecked,
326
- mainMessage
327
- }));
397
+ dispatch(markMessageFailed(failedPayload()));
328
398
  }
329
399
  handleMessageFailed({
330
400
  message: 'media',
331
401
  roomJID: activeRoomJID,
332
- error: new Error('File exceeds the 50 MB upload limit'),
402
+ error,
333
403
  messageType: 'media'
334
404
  });
335
- return;
405
+ return false;
336
406
  }
337
- const id = existingId || `send-media-message:${uuidv4()}`;
338
407
  const optimisticTimestamp = Date.now();
339
408
  const optimisticDate = new Date(optimisticTimestamp).toISOString();
340
409
  const selfId = user?.xmppUsername || user?.walletAddress || '';
341
410
  const displayName = `${user?.firstName || ''} ${user?.lastName || ''}`.trim() || selfId;
342
- const fileSizeStr = data?.size != null ? String(data.size) : '';
343
- const normalizedFileUri = typeof data?.uri === 'string' ? data.uri.replace(/^assets-library:\/\//, 'file://') : '';
411
+ const normalizedFileUri = normalizeFileUri(first?.uri);
412
+ const fileSizeStr = first?.size != null ? String(first.size) : '';
413
+
414
+ // One tile per file, in selection order. A tile's id is local until the
415
+ // upload returns the server's `_id`; the tile's upload state is keyed on it.
416
+ const tiles = files.map((file, index) => {
417
+ const localUrl = localPreviewUrl(file?.uri);
418
+ return {
419
+ attachmentId: localAttachmentId(id, index),
420
+ location: '',
421
+ mimetype: file?.type || file?.mimeType || type,
422
+ originalName: file?.name,
423
+ fileName: file?.name,
424
+ size: file?.size != null ? String(file.size) : undefined,
425
+ duration: file?.duration != null ? String(file.duration) : undefined,
426
+ ...(localUrl ? {
427
+ localUrl
428
+ } : {}),
429
+ uploadStatus: 'uploading',
430
+ uploadProgress: 0
431
+ };
432
+ });
344
433
  if (!config?.disableSentLogic && !existingId) {
345
434
  dispatch(addRoomMessage({
346
435
  roomJID: activeRoomJID,
@@ -360,17 +449,22 @@ export const useSendMessage = _configOverride => {
360
449
  xmppId: id,
361
450
  xmppFrom: `${activeRoomJID}/${selfId}`,
362
451
  isMediafile: 'true',
363
- fileName: data?.name,
452
+ fileName: first?.name,
364
453
  location: normalizedFileUri,
365
454
  locationPreview: normalizedFileUri,
366
455
  mimetype: type,
367
- originalName: data?.name,
456
+ originalName: first?.name,
368
457
  size: fileSizeStr,
369
- duration: data?.duration,
370
- waveForm: data?.waveForm,
458
+ duration: first?.duration,
459
+ waveForm: first?.waveForm,
460
+ attachments: tiles,
371
461
  isReply: isReply ? 'true' : 'false',
372
462
  showInChannel: `${isChecked}`,
373
- mainMessage
463
+ mainMessage,
464
+ ...(captionFields ? {
465
+ caption: captionFields.caption,
466
+ captionMentions: captionFields.mentions
467
+ } : {})
374
468
  }
375
469
  }));
376
470
  dispatch(addMessageToHeap({
@@ -391,31 +485,25 @@ export const useSendMessage = _configOverride => {
391
485
  }));
392
486
  }
393
487
 
394
- // Silent-failure watchdog for media-send (bug #18). Same shape
395
- // as the text path: if the optimistic bubble never gets the echo
396
- // (e.g. upload completed but XMPP send is buffered offline), flip
397
- // to failed → tap to retry.
398
- const mediaWatchdog = setTimeout(() => {
488
+ // Silent-failure watchdog for media-send (bug #18). Re-armed on every
489
+ // retry, so a retried send gets its own window. While files are still
490
+ // going up it is not a failure: it waits one more window instead.
491
+ const armWatchdog = () => setTimeout(() => {
492
+ if (running) {
493
+ mediaWatchdog = armWatchdog();
494
+ return;
495
+ }
399
496
  const state = reduxStore.getState();
400
497
  const roomMsgs = state.rooms?.rooms?.[activeRoomJID]?.messages || [];
401
498
  const stillPending = roomMsgs.find(m => m.id === id && m.pending);
402
499
  const alreadyFailed = state.roomHeapSlice?.failedMessages?.[id];
403
500
  if (stillPending && !alreadyFailed) {
404
- console.warn(`Media-send watchdog fired — ${id} stuck pending > ${PENDING_WATCHDOG_MS}ms; marking failed`);
501
+ console.warn(`Media-send watchdog fired - ${id} stuck pending > ${MEDIA_PENDING_WATCHDOG_MS}ms; marking failed`);
405
502
  // See the text-send watchdog above: drop the buffered replay so
406
503
  // a later onOnline flush can't silently resend behind the
407
504
  // "failed / tap to retry" bubble.
408
505
  removeOutboundSend(id);
409
- dispatch(markMessageFailed({
410
- kind: 'media',
411
- id,
412
- roomJID: activeRoomJID,
413
- data,
414
- type,
415
- isReply,
416
- isChecked,
417
- mainMessage
418
- }));
506
+ dispatch(markMessageFailed(failedPayload()));
419
507
  handleMessageFailed({
420
508
  message: 'media',
421
509
  roomJID: activeRoomJID,
@@ -424,138 +512,220 @@ export const useSendMessage = _configOverride => {
424
512
  });
425
513
  }
426
514
  }, MEDIA_PENDING_WATCHDOG_MS);
427
-
428
- // Help RN's FormData polyfill build a proper multipart/file part
429
- // (instead of serialising the JS object as JSON). On iOS the
430
- // picker can return ph:// or assets-library:// URIs for some
431
- // legacy library items — those can't be read as files. Coerce
432
- // to a file:// scheme when possible so the body is a real blob.
433
- const fileBlob = {
434
- uri: normalizedFileUri || data?.uri,
435
- type: data?.type || data?.mimeType || type || 'application/octet-stream',
436
- name: data?.name || data?.fileName || `media_${Date.now()}`
515
+ const uploaded = files.map(() => undefined);
516
+ const rawResults = files.map(() => undefined);
517
+ const sealedUploads = files.map(() => undefined);
518
+ // Per-file key material, kept apart from the sealed upload: the upload
519
+ // is disposed once the message is assembled, but a send that fails after
520
+ // that point is retried and must still carry every key.
521
+ const keyMaterials = files.map(() => undefined);
522
+ let delivered = false;
523
+ let running = false;
524
+ const concurrency = isE2ee ? MEDIA_E2EE_UPLOAD_CONCURRENCY : MEDIA_UPLOAD_CONCURRENCY;
525
+ let mediaWatchdog = armWatchdog();
526
+ const publishTile = (index, patch) => {
527
+ const attachmentId = tiles[index]?.attachmentId;
528
+ if (config?.disableSentLogic || !attachmentId) {
529
+ return;
530
+ }
531
+ dispatch(setMediaUploadState({
532
+ roomJID: activeRoomJID,
533
+ messageId: id,
534
+ attachmentId,
535
+ ...patch
536
+ }));
437
537
  };
438
- let sealed;
439
- const tryUpload = async () => {
440
- if (shouldEncrypt(activeRoomJID) && !sealed) {
538
+ const missingIndices = () => uploaded.reduce((acc, item, index) => item ? acc : [...acc, index], []);
539
+ const uploadOne = async index => {
540
+ const file = files[index];
541
+ publishTile(index, {
542
+ uploadStatus: 'uploading',
543
+ uploadProgress: 0
544
+ });
545
+
546
+ // Helps RN's FormData polyfill build a proper multipart/file part
547
+ // (instead of serialising the JS object as JSON). On iOS the picker
548
+ // can return ph:// or assets-library:// URIs; coerce to file:// when
549
+ // possible so the body is a real blob.
550
+ const fileBlob = {
551
+ uri: normalizeFileUri(file?.uri) || file?.uri,
552
+ type: file?.type || file?.mimeType || type || 'application/octet-stream',
553
+ name: file?.name || file?.fileName || `media_${Date.now()}_${index}`
554
+ };
555
+
556
+ // In an e2ee room each file is sealed before it leaves the device:
557
+ // the server only ever sees opaque bytes under a random name. The
558
+ // per-file keys ride in the encrypted <body> (see sendMediaMessage).
559
+ if (isE2ee) {
560
+ sealedUploads[index]?.dispose();
561
+ sealedUploads[index] = undefined;
441
562
  const {
442
563
  sealPickedFile
443
564
  } = require('../e2ee/sealedFiles');
444
- sealed = await sealPickedFile({
565
+ sealedUploads[index] = await sealPickedFile({
445
566
  uri: fileBlob.uri,
446
567
  name: fileBlob.name,
447
568
  type: fileBlob.type,
448
- size: typeof data?.size === 'number' ? data.size : undefined
569
+ size: typeof file?.size === 'number' ? file.size : undefined
449
570
  });
450
571
  }
572
+ const sealed = sealedUploads[index];
573
+ if (sealed) {
574
+ keyMaterials[index] = sealed.keyMaterial;
575
+ }
451
576
  const part = sealed ? {
452
577
  uri: sealed.uri,
453
578
  type: 'application/octet-stream',
454
579
  name: sealed.name
455
580
  } : fileBlob;
456
581
  const options = {
457
- clientEncrypted: !!sealed
582
+ clientEncrypted: !!sealed,
583
+ onProgress: percent => publishTile(index, {
584
+ uploadProgress: percent
585
+ })
586
+ };
587
+ const post = field => {
588
+ const fd = new FormData();
589
+ fd.append(field, part);
590
+ return uploadFileV2(fd, activeRoomJID, options);
458
591
  };
459
- const fd = new FormData();
460
- fd.append('files', part);
592
+ let response;
461
593
  try {
462
- return await uploadFileV2(fd, activeRoomJID, options);
594
+ response = await post('files');
463
595
  } catch (err) {
464
- if (err?.response?.status === 500) {
465
- console.warn('upload "files" field returned 500 — retrying with "file" (singular)', {
466
- name: part.name,
467
- type: part.type
468
- });
469
- const fd2 = new FormData();
470
- fd2.append('file', part);
471
- return await uploadFileV2(fd2, activeRoomJID, options);
596
+ if (err?.response?.status !== 500) {
597
+ throw err;
472
598
  }
473
- throw err;
599
+ console.warn('upload "files" field returned 500 - retrying with "file" (singular)', {
600
+ name: part.name,
601
+ type: part.type
602
+ });
603
+ response = await post('file');
474
604
  }
605
+ const raw = Array.isArray(response?.data?.results) ? response.data.results[0] : undefined;
606
+ if (!raw) {
607
+ throw new Error('media_upload_empty_response');
608
+ }
609
+ uploaded[index] = toUploadedAttachment(raw);
610
+ rawResults[index] = raw;
611
+ // The bytes are up; the message waits for its siblings.
612
+ publishTile(index, {
613
+ uploadStatus: 'uploading',
614
+ uploadProgress: 100
615
+ });
475
616
  };
476
- try {
477
- const response = await tryUpload();
478
- // Upload succeeded — the stanza is now in flight. If the
479
- // server echo arrives within the watchdog window the bubble
480
- // flips normally; if not, the watchdog still fires and marks
481
- // it failed. We don't clear the timer on upload success because
482
- // the XMPP send could still fail silently.
483
-
484
- for (const item of response.data.results) {
485
- const messagePayload = {
486
- firstName: user.firstName,
487
- lastName: user.lastName,
488
- walletAddress: user.walletAddress,
489
- createdAt: item.createdAt,
490
- expiresAt: item.expiresAt,
491
- fileName: item.filename,
492
- isVisible: item?.isVisible,
493
- location: item.location,
494
- locationPreview: item.locationPreview,
495
- mimetype: item.mimetype,
496
- originalName: item?.originalname,
497
- ownerKey: item?.ownerKey,
498
- size: item.size,
499
- duration: item?.duration ?? data?.duration,
500
- waveForm: data?.waveForm,
501
- updatedAt: item?.updatedAt,
502
- userId: item?.userId,
503
- attachmentId: item?._id,
504
- wrappable: true,
505
- roomJid: activeRoomJID,
506
- showInChannel: isChecked,
507
- isReply,
508
- mainMessage,
509
- isPrivate: item?.isPrivate,
510
- __v: item.__v,
511
- e2eeKeys: sealed ? [sealed.keyMaterial] : undefined
512
- };
513
- if (sealed) {
617
+ const sendAssembled = async () => {
618
+ const raws = rawResults;
619
+ const attachments = uploaded;
620
+ const head = raws[0];
621
+ const messagePayload = {
622
+ firstName: user.firstName,
623
+ lastName: user.lastName,
624
+ walletAddress: user.walletAddress,
625
+ createdAt: head.createdAt,
626
+ expiresAt: head.expiresAt,
627
+ fileName: head.filename,
628
+ isVisible: head?.isVisible,
629
+ location: head.location,
630
+ locationPreview: head.locationPreview,
631
+ mimetype: head.mimetype,
632
+ originalName: head?.originalname,
633
+ ownerKey: head?.ownerKey,
634
+ size: head.size,
635
+ duration: head?.duration ?? first?.duration,
636
+ waveForm: first?.waveForm,
637
+ updatedAt: head?.updatedAt,
638
+ userId: head?.userId,
639
+ attachmentId: head?._id,
640
+ // Only when there is more than one file: sendMediaMessage stamps
641
+ // the list on <data> and leaves a single-file stanza untouched.
642
+ attachments: attachments.length > 1 ? attachments : undefined,
643
+ caption: captionFields?.caption,
644
+ captionMentions: captionFields?.mentions,
645
+ wrappable: true,
646
+ roomJid: activeRoomJID,
647
+ showInChannel: isChecked,
648
+ isReply,
649
+ mainMessage,
650
+ isPrivate: head?.isPrivate,
651
+ __v: head.__v,
652
+ // One key per sealed file, in selection order.
653
+ e2eeKeys: isE2ee ? keyMaterials : undefined
654
+ };
655
+
656
+ // The local copies of sealed files are remembered for the sender and
657
+ // the scratch ciphertext is dropped.
658
+ if (isE2ee) {
659
+ const {
660
+ rememberOpened
661
+ } = require('../e2ee/sealedFiles');
662
+ for (let index = 0; index < files.length; index++) {
663
+ const item = attachments[index];
514
664
  try {
515
- const {
516
- rememberOpened
517
- } = require('../e2ee/sealedFiles');
518
665
  await rememberOpened(item.location, {
519
- uri: fileBlob.uri,
520
- name: fileBlob.name,
521
- type: fileBlob.type,
522
- size: typeof data?.size === 'number' ? data.size : undefined
666
+ uri: normalizeFileUri(files[index]?.uri) || files[index]?.uri,
667
+ name: files[index]?.name,
668
+ type: files[index]?.type || files[index]?.mimeType || type,
669
+ size: typeof files[index]?.size === 'number' ? files[index].size : undefined
523
670
  });
524
671
  } catch (err) {
525
672
  console.warn('could not keep the sent attachment', err);
526
673
  }
527
- sealed.dispose();
674
+ sealedUploads[index]?.dispose();
675
+ sealedUploads[index] = undefined;
528
676
  }
529
- // Stanza id == placeholder id so the MUC echo's outer
530
- // <message id="..."> matches xmppId on the placeholder and
531
- // insertMessageWithDelimiter merges in place + flips pending.
677
+ }
678
+
679
+ // Replace the optimistic tiles with the uploaded files. A sealed
680
+ // message keeps its local tiles (the server copies are ciphertext) and
681
+ // only drops the upload state. `pending` is left alone: only the
682
+ // server echo confirms the message.
683
+ if (!config?.disableSentLogic) {
684
+ dispatch(setMessageAttachments({
685
+ roomJID: activeRoomJID,
686
+ messageId: id,
687
+ attachments: isE2ee ? tiles.map(withoutUploadState) : attachments.map((attachment, index) => {
688
+ const localUrl = tiles[index]?.localUrl;
689
+ return localUrl ? {
690
+ ...attachment,
691
+ localUrl
692
+ } : attachment;
693
+ })
694
+ }));
695
+ }
696
+ const effectiveMediaClient = [client, getGlobalXmppClient(), store.getState()?.chatSettingStore?.client].find(c => c && c.status === 'online');
697
+ if (effectiveMediaClient) {
698
+ effectiveMediaClient.setActiveRoomJid?.(activeRoomJID);
699
+ await Promise.resolve(effectiveMediaClient.prioritizeRoomPresence?.(activeRoomJID)).catch(() => false);
700
+ effectiveMediaClient.sendMediaMessageStanza(activeRoomJID, messagePayload, id);
701
+ try {
702
+ effectiveMediaClient.ensureStreamAlive?.();
703
+ } catch {}
704
+ } else {
532
705
  // Same online-only guard as the text path: a surviving-but-offline
533
- // client must NOT be used (the stanza would vanish into a dead
534
- // socket) — fall through to enqueue so the reconnect flush replays.
535
- const effectiveMediaClient = [client, getGlobalXmppClient(), store.getState()?.chatSettingStore?.client].find(c => c && c.status === 'online');
536
- if (effectiveMediaClient) {
537
- effectiveMediaClient.setActiveRoomJid?.(activeRoomJID);
538
- await Promise.resolve(effectiveMediaClient.prioritizeRoomPresence?.(activeRoomJID)).catch(() => false);
539
- effectiveMediaClient.sendMediaMessageStanza(activeRoomJID, messagePayload, id);
540
- try {
541
- effectiveMediaClient.ensureStreamAlive?.();
542
- } catch {}
543
- } else {
544
- const anyMediaClient = client || getGlobalXmppClient() || store.getState()?.chatSettingStore?.client;
545
- try {
546
- anyMediaClient?.forceReconnect?.();
547
- } catch {}
548
- enqueueOutboundSend({
549
- optimisticId: id,
550
- roomJID: activeRoomJID,
551
- enqueuedAt: Date.now(),
552
- // Match the media watchdog so a reconnect within the (longer)
553
- // media window still auto-replays the buffered stanza.
554
- ttlMs: MEDIA_PENDING_WATCHDOG_MS,
555
- send: c => c.sendMediaMessageStanza(activeRoomJID, messagePayload, id)
556
- });
557
- }
706
+ // client must NOT be used. Buffer the stanza for the reconnect flush.
707
+ const anyMediaClient = client || getGlobalXmppClient() || store.getState()?.chatSettingStore?.client;
708
+ try {
709
+ anyMediaClient?.forceReconnect?.();
710
+ } catch {}
711
+ enqueueOutboundSend({
712
+ optimisticId: id,
713
+ roomJID: activeRoomJID,
714
+ enqueuedAt: Date.now(),
715
+ // Match the media watchdog so a reconnect within the (longer)
716
+ // media window still auto-replays the buffered stanza.
717
+ ttlMs: MEDIA_PENDING_WATCHDOG_MS,
718
+ send: c => c.sendMediaMessageStanza(activeRoomJID, messagePayload, id)
719
+ });
558
720
  }
721
+
722
+ // The upload time is spent: the wait for the server echo gets a full
723
+ // watchdog window of its own, not what was left of the upload's.
724
+ clearTimeout(mediaWatchdog);
725
+ mediaWatchdog = armWatchdog();
726
+ delivered = true;
727
+ unregisterMediaRetry(id);
728
+ dispatch(clearMessageFailure(id));
559
729
  await handleMessageSent({
560
730
  message: 'media',
561
731
  roomJID: activeRoomJID,
@@ -568,59 +738,73 @@ export const useSendMessage = _configOverride => {
568
738
  fileData: data,
569
739
  fileType: type,
570
740
  messageId: id,
571
- uploadResults: response.data.results
741
+ uploadResults: raws
572
742
  }
573
743
  });
574
- } catch (error) {
575
- // Upload (or stanza send) threw an explicit error. Clear the
576
- // watchdog so it doesn't double-fire `markMessageFailed`.
577
- clearTimeout(mediaWatchdog);
578
- sealed?.dispose();
579
- // Surface the real server payload so we can see why the upload
580
- // was rejected (axios collapses the message to "Request failed
581
- // with status code N"; the actual reason lives in
582
- // `error.response.data`).
583
- const uriStr = typeof data?.uri === 'string' ? data.uri : '';
584
- const uriScheme = uriStr.match(/^([a-z]+):/i)?.[1] ?? 'unknown';
585
- console.error('upload failed', {
586
- status: error?.response?.status,
587
- statusText: error?.response?.statusText,
588
- serverBody: error?.response?.data,
589
- requestUrl: error?.config?.url,
590
- axiosMessage: error?.message,
591
- axiosCode: error?.code,
592
- axiosCauseMessage: error?.cause?.message,
593
- uri: uriStr,
594
- uriScheme,
595
- mime: fileBlob.type,
596
- fileSizeBytes: data?.size ?? null,
597
- fileName: fileBlob.name,
598
- platformOS: Platform.OS,
599
- platformVersion: Platform.Version
600
- });
601
- dispatch(markMessageFailed({
602
- kind: 'media',
603
- id,
604
- roomJID: activeRoomJID,
605
- data,
606
- type,
607
- isReply,
608
- isChecked,
609
- mainMessage
610
- }));
744
+ };
745
+ const failMessage = error => {
746
+ console.error('Media send failed:', error);
747
+ // Every file stays held: the bubble shows Failed, and Retry replays
748
+ // the files that did not make it (in-session) or the whole list.
749
+ dispatch(markMessageFailed(failedPayload()));
611
750
  handleMessageFailed({
612
751
  message: 'media',
613
752
  roomJID: activeRoomJID,
614
753
  error: error,
615
754
  messageType: 'media'
616
755
  });
617
- }
756
+ };
757
+
758
+ /**
759
+ * Uploads `indices` (a retry passes only the files that failed), then
760
+ * sends once nothing is missing. Any failure leaves the message failed
761
+ * with its tiles marked, ready for a retry.
762
+ */
763
+ const run = async indices => {
764
+ if (delivered || running) {
765
+ return false;
766
+ }
767
+ running = true;
768
+ clearTimeout(mediaWatchdog);
769
+ mediaWatchdog = armWatchdog();
770
+ try {
771
+ await mapWithConcurrency(indices, concurrency, async index => {
772
+ try {
773
+ await uploadOne(index);
774
+ } catch (error) {
775
+ console.error('upload failed', {
776
+ status: error?.response?.status,
777
+ serverBody: error?.response?.data,
778
+ axiosMessage: error?.message,
779
+ fileName: files[index]?.name,
780
+ fileSizeBytes: files[index]?.size ?? null,
781
+ platformOS: Platform.OS
782
+ });
783
+ publishTile(index, {
784
+ uploadStatus: 'failed',
785
+ uploadProgress: 0
786
+ });
787
+ }
788
+ });
789
+ if (missingIndices().length > 0) {
790
+ throw new Error('media_upload_failed');
791
+ }
792
+ await sendAssembled();
793
+ return true;
794
+ } catch (error) {
795
+ failMessage(error);
796
+ return false;
797
+ } finally {
798
+ running = false;
799
+ }
800
+ };
801
+ registerMediaRetry(id, indices => run(missingIndices().filter(position => indices === undefined || indices.includes(position))));
802
+ return run(files.map((_, index) => index));
618
803
  }, [client, config, user, dispatch, handleMessageSent, handleMessageFailed, reduxStore]);
619
804
 
620
- // Retry a previously-failed send by replaying the saved payload from
621
- // the heap. Clears the failure flag first so the bubble flips back to
622
- // "sending..."; if the retry fails again `markMessageFailed` re-arms
623
- // it. No-op when the id isn't in the failed map.
805
+ // Retry a previously-failed send. An in-session send re-runs only the files
806
+ // still missing (its runner is still registered); after a restart the
807
+ // saved payload is replayed as a whole. No-op when the id isn't failed.
624
808
  const retryMessage = useCallback(async failedId => {
625
809
  const payload = reduxStore.getState().roomHeapSlice?.failedMessages?.[failedId];
626
810
  if (!payload) {
@@ -649,9 +833,17 @@ export const useSendMessage = _configOverride => {
649
833
  // Re-use the failed message's id so the retry resends in place
650
834
  // instead of spawning a duplicate bubble.
651
835
  failedId);
652
- } else {
653
- await sendMedia(payload.data, payload.type, payload.roomJID, payload.isReply, payload.isChecked, payload.mainMessage, failedId);
836
+ return;
837
+ }
838
+ if (hasMediaRetry(failedId)) {
839
+ await retryMediaSend(failedId);
840
+ return;
654
841
  }
842
+ const list = Array.isArray(payload.files) && payload.files.length > 0 ? payload.files : payload.data;
843
+ await sendMedia(list, payload.type, payload.roomJID, payload.isReply, payload.isChecked, payload.mainMessage, failedId, payload.caption ? {
844
+ text: payload.caption,
845
+ mentions: payload.captionMentions
846
+ } : undefined);
655
847
  }, [reduxStore, dispatch, handleMessageRetry, sendMessage, sendMedia]);
656
848
 
657
849
  // ChatRoom/ThreadWrapper consume this as the "edit branch" of send.