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