@ethora/chat-component-rn 26.10.5 → 26.10.7
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.
- package/CHANGELOG.md +10 -0
- package/README.md +18 -0
- package/app.plugin.js +2 -0
- package/lib/commonjs/assets/icons.js +2 -2
- package/lib/commonjs/assets/icons.js.map +1 -1
- package/lib/commonjs/components/MainComponents/AlbumGrid.js +152 -0
- package/lib/commonjs/components/MainComponents/AlbumGrid.js.map +1 -0
- package/lib/commonjs/components/MainComponents/ChatRoom.js +2 -2
- package/lib/commonjs/components/MainComponents/ChatRoom.js.map +1 -1
- package/lib/commonjs/components/MainComponents/MediaMessage.js +199 -123
- package/lib/commonjs/components/MainComponents/MediaMessage.js.map +1 -1
- package/lib/commonjs/components/MainComponents/MediaUploadOverlay.js +109 -0
- package/lib/commonjs/components/MainComponents/MediaUploadOverlay.js.map +1 -0
- package/lib/commonjs/components/MessageBubble/MediaCaption.js +51 -0
- package/lib/commonjs/components/MessageBubble/MediaCaption.js.map +1 -0
- package/lib/commonjs/components/MessageBubble/Message.js +40 -1
- package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
- package/lib/commonjs/components/MessageBubble/MessageInteractions.js +2 -1
- package/lib/commonjs/components/MessageBubble/MessageInteractions.js.map +1 -1
- package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +75 -0
- package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
- package/lib/commonjs/components/RoomComponents/LastMessageItem.js +20 -4
- package/lib/commonjs/components/RoomComponents/LastMessageItem.js.map +1 -1
- package/lib/commonjs/components/RoomComponents/styled/LastAudioMessage.js +3 -2
- package/lib/commonjs/components/RoomComponents/styled/LastAudioMessage.js.map +1 -1
- package/lib/commonjs/components/RoomComponents/styled/LastMessageFile.js +3 -2
- package/lib/commonjs/components/RoomComponents/styled/LastMessageFile.js.map +1 -1
- package/lib/commonjs/components/RoomComponents/styled/LastMessagePhoto.js +3 -2
- package/lib/commonjs/components/RoomComponents/styled/LastMessagePhoto.js.map +1 -1
- package/lib/commonjs/components/RoomComponents/styled/LastMessageVideo.js +3 -2
- package/lib/commonjs/components/RoomComponents/styled/LastMessageVideo.js.map +1 -1
- package/lib/commonjs/components/Thread/ThreadWrapper.js +3 -2
- package/lib/commonjs/components/Thread/ThreadWrapper.js.map +1 -1
- package/lib/commonjs/components/styled/MediaFilePreview.js +60 -47
- package/lib/commonjs/components/styled/MediaFilePreview.js.map +1 -1
- package/lib/commonjs/components/styled/MessageImage.js +16 -6
- package/lib/commonjs/components/styled/MessageImage.js.map +1 -1
- package/lib/commonjs/components/styled/SendInput.js +99 -68
- package/lib/commonjs/components/styled/SendInput.js.map +1 -1
- package/lib/commonjs/components/styled/VideoMessage.js +24 -10
- package/lib/commonjs/components/styled/VideoMessage.js.map +1 -1
- package/lib/commonjs/context/xmppProvider.js +46 -0
- package/lib/commonjs/context/xmppProvider.js.map +1 -1
- package/lib/commonjs/e2ee/fileEnvelope.js +6 -0
- package/lib/commonjs/e2ee/fileEnvelope.js.map +1 -1
- package/lib/commonjs/e2ee/sealedBody.js +32 -2
- package/lib/commonjs/e2ee/sealedBody.js.map +1 -1
- package/lib/commonjs/helpers/albumLayout.js +150 -0
- package/lib/commonjs/helpers/albumLayout.js.map +1 -0
- package/lib/commonjs/helpers/attachments.js +255 -0
- package/lib/commonjs/helpers/attachments.js.map +1 -0
- package/lib/commonjs/helpers/caption.js +214 -0
- package/lib/commonjs/helpers/caption.js.map +1 -0
- package/lib/commonjs/helpers/createMainMessageForThread.js +5 -1
- package/lib/commonjs/helpers/createMainMessageForThread.js.map +1 -1
- package/lib/commonjs/helpers/createMessageFromXml.js +19 -5
- package/lib/commonjs/helpers/createMessageFromXml.js.map +1 -1
- package/lib/commonjs/helpers/fileKind.js +40 -0
- package/lib/commonjs/helpers/fileKind.js.map +1 -0
- package/lib/commonjs/helpers/getDataFromXml.js +6 -1
- package/lib/commonjs/helpers/getDataFromXml.js.map +1 -1
- package/lib/commonjs/helpers/mapWithConcurrency.js +41 -0
- package/lib/commonjs/helpers/mapWithConcurrency.js.map +1 -0
- package/lib/commonjs/helpers/mediaLimits.js +33 -0
- package/lib/commonjs/helpers/mediaLimits.js.map +1 -0
- package/lib/commonjs/helpers/mediaSendRetry.js +64 -0
- package/lib/commonjs/helpers/mediaSendRetry.js.map +1 -0
- package/lib/commonjs/helpers/mediaWireData.js +62 -0
- package/lib/commonjs/helpers/mediaWireData.js.map +1 -0
- package/lib/commonjs/helpers/mentionSpans.js +19 -0
- package/lib/commonjs/helpers/mentionSpans.js.map +1 -0
- package/lib/commonjs/helpers/mimeToExtension.js +45 -0
- package/lib/commonjs/helpers/mimeToExtension.js.map +1 -1
- package/lib/commonjs/helpers/readReceipts.js +173 -0
- package/lib/commonjs/helpers/readReceipts.js.map +1 -0
- package/lib/commonjs/helpers/repairFilenameEncoding.js +79 -0
- package/lib/commonjs/helpers/repairFilenameEncoding.js.map +1 -0
- package/lib/commonjs/helpers/safeJson.js +18 -0
- package/lib/commonjs/helpers/safeJson.js.map +1 -0
- package/lib/commonjs/helpers/secureStoreRuntime.js +10 -1
- package/lib/commonjs/helpers/secureStoreRuntime.js.map +1 -1
- package/lib/commonjs/hooks/useSendMessage.js +384 -193
- package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
- package/lib/commonjs/i18n/strings.js +96 -0
- package/lib/commonjs/i18n/strings.js.map +1 -1
- package/lib/commonjs/main.js +81 -0
- package/lib/commonjs/main.js.map +1 -1
- package/lib/commonjs/networking/api-requests/auth.api.js +62 -3
- package/lib/commonjs/networking/api-requests/auth.api.js.map +1 -1
- package/lib/commonjs/networking/api-requests/push.api.js +22 -7
- package/lib/commonjs/networking/api-requests/push.api.js.map +1 -1
- package/lib/commonjs/networking/stanzaHandlers.js +100 -3
- package/lib/commonjs/networking/stanzaHandlers.js.map +1 -1
- package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js +4 -2
- package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
- package/lib/commonjs/networking/xmpp/sendDisplayedMarker.xmpp.js +28 -0
- package/lib/commonjs/networking/xmpp/sendDisplayedMarker.xmpp.js.map +1 -0
- package/lib/commonjs/networking/xmpp/sendMediaMessage.xmpp.js +53 -2
- package/lib/commonjs/networking/xmpp/sendMediaMessage.xmpp.js.map +1 -1
- package/lib/commonjs/networking/xmpp/sendReceivedReceipt.xmpp.js +28 -0
- package/lib/commonjs/networking/xmpp/sendReceivedReceipt.xmpp.js.map +1 -0
- package/lib/commonjs/networking/xmppClient.js +64 -0
- package/lib/commonjs/networking/xmppClient.js.map +1 -1
- package/lib/commonjs/push/pushKeyStore.js +169 -0
- package/lib/commonjs/push/pushKeyStore.js.map +1 -0
- package/lib/commonjs/push/sealedBox.js +106 -0
- package/lib/commonjs/push/sealedBox.js.map +1 -0
- package/lib/commonjs/push/sealedPush.js +124 -0
- package/lib/commonjs/push/sealedPush.js.map +1 -0
- package/lib/commonjs/push/sealedPushTask.js +195 -0
- package/lib/commonjs/push/sealedPushTask.js.map +1 -0
- package/lib/commonjs/roomStore/persistence.js +78 -5
- package/lib/commonjs/roomStore/persistence.js.map +1 -1
- package/lib/commonjs/roomStore/roomHeapSlice.js +4 -0
- package/lib/commonjs/roomStore/roomHeapSlice.js.map +1 -1
- package/lib/commonjs/roomStore/roomsSlice.js +114 -1
- package/lib/commonjs/roomStore/roomsSlice.js.map +1 -1
- package/lib/commonjs/services/pushRegistration.js +12 -7
- package/lib/commonjs/services/pushRegistration.js.map +1 -1
- package/lib/commonjs/types/models/attachment.model.js +2 -0
- package/lib/commonjs/types/models/attachment.model.js.map +1 -0
- package/lib/commonjs/version.js +1 -1
- package/lib/module/assets/icons.js +1 -1
- package/lib/module/assets/icons.js.map +1 -1
- package/lib/module/components/MainComponents/AlbumGrid.js +146 -0
- package/lib/module/components/MainComponents/AlbumGrid.js.map +1 -0
- package/lib/module/components/MainComponents/ChatRoom.js +2 -2
- package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
- package/lib/module/components/MainComponents/MediaMessage.js +200 -124
- package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
- package/lib/module/components/MainComponents/MediaUploadOverlay.js +104 -0
- package/lib/module/components/MainComponents/MediaUploadOverlay.js.map +1 -0
- package/lib/module/components/MessageBubble/MediaCaption.js +46 -0
- package/lib/module/components/MessageBubble/MediaCaption.js.map +1 -0
- package/lib/module/components/MessageBubble/Message.js +44 -5
- package/lib/module/components/MessageBubble/Message.js.map +1 -1
- package/lib/module/components/MessageBubble/MessageInteractions.js +2 -1
- package/lib/module/components/MessageBubble/MessageInteractions.js.map +1 -1
- package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +75 -0
- package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
- package/lib/module/components/RoomComponents/LastMessageItem.js +20 -4
- package/lib/module/components/RoomComponents/LastMessageItem.js.map +1 -1
- package/lib/module/components/RoomComponents/styled/LastAudioMessage.js +3 -2
- package/lib/module/components/RoomComponents/styled/LastAudioMessage.js.map +1 -1
- package/lib/module/components/RoomComponents/styled/LastMessageFile.js +3 -2
- package/lib/module/components/RoomComponents/styled/LastMessageFile.js.map +1 -1
- package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js +3 -2
- package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js.map +1 -1
- package/lib/module/components/RoomComponents/styled/LastMessageVideo.js +3 -2
- package/lib/module/components/RoomComponents/styled/LastMessageVideo.js.map +1 -1
- package/lib/module/components/Thread/ThreadWrapper.js +3 -2
- package/lib/module/components/Thread/ThreadWrapper.js.map +1 -1
- package/lib/module/components/styled/MediaFilePreview.js +61 -48
- package/lib/module/components/styled/MediaFilePreview.js.map +1 -1
- package/lib/module/components/styled/MessageImage.js +16 -6
- package/lib/module/components/styled/MessageImage.js.map +1 -1
- package/lib/module/components/styled/SendInput.js +99 -68
- package/lib/module/components/styled/SendInput.js.map +1 -1
- package/lib/module/components/styled/VideoMessage.js +24 -10
- package/lib/module/components/styled/VideoMessage.js.map +1 -1
- package/lib/module/context/xmppProvider.js +46 -0
- package/lib/module/context/xmppProvider.js.map +1 -1
- package/lib/module/e2ee/fileEnvelope.js +1 -1
- package/lib/module/e2ee/fileEnvelope.js.map +1 -1
- package/lib/module/e2ee/sealedBody.js +32 -2
- package/lib/module/e2ee/sealedBody.js.map +1 -1
- package/lib/module/helpers/albumLayout.js +143 -0
- package/lib/module/helpers/albumLayout.js.map +1 -0
- package/lib/module/helpers/attachments.js +245 -0
- package/lib/module/helpers/attachments.js.map +1 -0
- package/lib/module/helpers/caption.js +207 -0
- package/lib/module/helpers/caption.js.map +1 -0
- package/lib/module/helpers/createMainMessageForThread.js +5 -1
- package/lib/module/helpers/createMainMessageForThread.js.map +1 -1
- package/lib/module/helpers/createMessageFromXml.js +20 -6
- package/lib/module/helpers/createMessageFromXml.js.map +1 -1
- package/lib/module/helpers/fileKind.js +34 -0
- package/lib/module/helpers/fileKind.js.map +1 -0
- package/lib/module/helpers/getDataFromXml.js +6 -1
- package/lib/module/helpers/getDataFromXml.js.map +1 -1
- package/lib/module/helpers/mapWithConcurrency.js +37 -0
- package/lib/module/helpers/mapWithConcurrency.js.map +1 -0
- package/lib/module/helpers/mediaLimits.js +27 -0
- package/lib/module/helpers/mediaLimits.js.map +1 -0
- package/lib/module/helpers/mediaSendRetry.js +55 -0
- package/lib/module/helpers/mediaSendRetry.js.map +1 -0
- package/lib/module/helpers/mediaWireData.js +58 -0
- package/lib/module/helpers/mediaWireData.js.map +1 -0
- package/lib/module/helpers/mentionSpans.js +14 -0
- package/lib/module/helpers/mentionSpans.js.map +1 -0
- package/lib/module/helpers/mimeToExtension.js +43 -0
- package/lib/module/helpers/mimeToExtension.js.map +1 -1
- package/lib/module/helpers/readReceipts.js +160 -0
- package/lib/module/helpers/readReceipts.js.map +1 -0
- package/lib/module/helpers/repairFilenameEncoding.js +74 -0
- package/lib/module/helpers/repairFilenameEncoding.js.map +1 -0
- package/lib/module/helpers/safeJson.js +13 -0
- package/lib/module/helpers/safeJson.js.map +1 -0
- package/lib/module/helpers/secureStoreRuntime.js +10 -1
- package/lib/module/helpers/secureStoreRuntime.js.map +1 -1
- package/lib/module/hooks/useSendMessage.js +387 -195
- package/lib/module/hooks/useSendMessage.js.map +1 -1
- package/lib/module/i18n/strings.js +96 -0
- package/lib/module/i18n/strings.js.map +1 -1
- package/lib/module/main.js +6 -0
- package/lib/module/main.js.map +1 -1
- package/lib/module/networking/api-requests/auth.api.js +62 -3
- package/lib/module/networking/api-requests/auth.api.js.map +1 -1
- package/lib/module/networking/api-requests/push.api.js +21 -6
- package/lib/module/networking/api-requests/push.api.js.map +1 -1
- package/lib/module/networking/stanzaHandlers.js +97 -4
- package/lib/module/networking/stanzaHandlers.js.map +1 -1
- package/lib/module/networking/xmpp/handleStanzas.xmpp.js +5 -3
- package/lib/module/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
- package/lib/module/networking/xmpp/sendDisplayedMarker.xmpp.js +25 -0
- package/lib/module/networking/xmpp/sendDisplayedMarker.xmpp.js.map +1 -0
- package/lib/module/networking/xmpp/sendMediaMessage.xmpp.js +53 -2
- package/lib/module/networking/xmpp/sendMediaMessage.xmpp.js.map +1 -1
- package/lib/module/networking/xmpp/sendReceivedReceipt.xmpp.js +25 -0
- package/lib/module/networking/xmpp/sendReceivedReceipt.xmpp.js.map +1 -0
- package/lib/module/networking/xmppClient.js +64 -0
- package/lib/module/networking/xmppClient.js.map +1 -1
- package/lib/module/push/pushKeyStore.js +159 -0
- package/lib/module/push/pushKeyStore.js.map +1 -0
- package/lib/module/push/sealedBox.js +97 -0
- package/lib/module/push/sealedBox.js.map +1 -0
- package/lib/module/push/sealedPush.js +117 -0
- package/lib/module/push/sealedPush.js.map +1 -0
- package/lib/module/push/sealedPushTask.js +187 -0
- package/lib/module/push/sealedPushTask.js.map +1 -0
- package/lib/module/roomStore/persistence.js +78 -5
- package/lib/module/roomStore/persistence.js.map +1 -1
- package/lib/module/roomStore/roomHeapSlice.js +4 -0
- package/lib/module/roomStore/roomHeapSlice.js.map +1 -1
- package/lib/module/roomStore/roomsSlice.js +109 -0
- package/lib/module/roomStore/roomsSlice.js.map +1 -1
- package/lib/module/services/pushRegistration.js +12 -7
- package/lib/module/services/pushRegistration.js.map +1 -1
- package/lib/module/types/models/attachment.model.js +2 -0
- package/lib/module/types/models/attachment.model.js.map +1 -0
- package/lib/module/version.js +1 -1
- package/lib/typescript/assets/icons.d.ts +6 -1
- package/lib/typescript/assets/icons.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/AlbumGrid.d.ts +21 -0
- package/lib/typescript/components/MainComponents/AlbumGrid.d.ts.map +1 -0
- package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/MediaUploadOverlay.d.ts +17 -0
- package/lib/typescript/components/MainComponents/MediaUploadOverlay.d.ts.map +1 -0
- package/lib/typescript/components/MessageBubble/MediaCaption.d.ts +12 -0
- package/lib/typescript/components/MessageBubble/MediaCaption.d.ts.map +1 -0
- package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
- package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts.map +1 -1
- package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
- package/lib/typescript/components/RoomComponents/LastMessageItem.d.ts.map +1 -1
- package/lib/typescript/components/RoomComponents/styled/LastAudioMessage.d.ts +2 -0
- package/lib/typescript/components/RoomComponents/styled/LastAudioMessage.d.ts.map +1 -1
- package/lib/typescript/components/RoomComponents/styled/LastMessageFile.d.ts +2 -0
- package/lib/typescript/components/RoomComponents/styled/LastMessageFile.d.ts.map +1 -1
- package/lib/typescript/components/RoomComponents/styled/LastMessagePhoto.d.ts +2 -0
- package/lib/typescript/components/RoomComponents/styled/LastMessagePhoto.d.ts.map +1 -1
- package/lib/typescript/components/RoomComponents/styled/LastMessageVideo.d.ts +2 -0
- package/lib/typescript/components/RoomComponents/styled/LastMessageVideo.d.ts.map +1 -1
- package/lib/typescript/components/Thread/ThreadWrapper.d.ts.map +1 -1
- package/lib/typescript/components/styled/MediaFilePreview.d.ts.map +1 -1
- package/lib/typescript/components/styled/MessageImage.d.ts +10 -0
- package/lib/typescript/components/styled/MessageImage.d.ts.map +1 -1
- package/lib/typescript/components/styled/SendInput.d.ts +7 -1
- package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
- package/lib/typescript/components/styled/VideoMessage.d.ts +7 -0
- package/lib/typescript/components/styled/VideoMessage.d.ts.map +1 -1
- package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
- package/lib/typescript/e2ee/fileEnvelope.d.ts +1 -1
- package/lib/typescript/e2ee/fileEnvelope.d.ts.map +1 -1
- package/lib/typescript/e2ee/sealedBody.d.ts +16 -0
- package/lib/typescript/e2ee/sealedBody.d.ts.map +1 -1
- package/lib/typescript/helpers/albumLayout.d.ts +66 -0
- package/lib/typescript/helpers/albumLayout.d.ts.map +1 -0
- package/lib/typescript/helpers/attachments.d.ts +55 -0
- package/lib/typescript/helpers/attachments.d.ts.map +1 -0
- package/lib/typescript/helpers/caption.d.ts +64 -0
- package/lib/typescript/helpers/caption.d.ts.map +1 -0
- package/lib/typescript/helpers/createMainMessageForThread.d.ts.map +1 -1
- package/lib/typescript/helpers/createMessageFromXml.d.ts.map +1 -1
- package/lib/typescript/helpers/fileKind.d.ts +20 -0
- package/lib/typescript/helpers/fileKind.d.ts.map +1 -0
- package/lib/typescript/helpers/getDataFromXml.d.ts.map +1 -1
- package/lib/typescript/helpers/mapWithConcurrency.d.ts +9 -0
- package/lib/typescript/helpers/mapWithConcurrency.d.ts.map +1 -0
- package/lib/typescript/helpers/mediaLimits.d.ts +13 -0
- package/lib/typescript/helpers/mediaLimits.d.ts.map +1 -0
- package/lib/typescript/helpers/mediaSendRetry.d.ts +26 -0
- package/lib/typescript/helpers/mediaSendRetry.d.ts.map +1 -0
- package/lib/typescript/helpers/mediaWireData.d.ts +16 -0
- package/lib/typescript/helpers/mediaWireData.d.ts.map +1 -0
- package/lib/typescript/helpers/mentionSpans.d.ts +8 -0
- package/lib/typescript/helpers/mentionSpans.d.ts.map +1 -0
- package/lib/typescript/helpers/mimeToExtension.d.ts +7 -0
- package/lib/typescript/helpers/mimeToExtension.d.ts.map +1 -1
- package/lib/typescript/helpers/readReceipts.d.ts +82 -0
- package/lib/typescript/helpers/readReceipts.d.ts.map +1 -0
- package/lib/typescript/helpers/repairFilenameEncoding.d.ts +12 -0
- package/lib/typescript/helpers/repairFilenameEncoding.d.ts.map +1 -0
- package/lib/typescript/helpers/safeJson.d.ts +2 -0
- package/lib/typescript/helpers/safeJson.d.ts.map +1 -0
- package/lib/typescript/helpers/secureStoreRuntime.d.ts +17 -3
- package/lib/typescript/helpers/secureStoreRuntime.d.ts.map +1 -1
- package/lib/typescript/hooks/useSendMessage.d.ts +2 -1
- package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
- package/lib/typescript/i18n/strings.d.ts.map +1 -1
- package/lib/typescript/main.d.ts +6 -0
- package/lib/typescript/main.d.ts.map +1 -1
- package/lib/typescript/networking/api-requests/auth.api.d.ts +2 -0
- package/lib/typescript/networking/api-requests/auth.api.d.ts.map +1 -1
- package/lib/typescript/networking/api-requests/push.api.d.ts +14 -2
- package/lib/typescript/networking/api-requests/push.api.d.ts.map +1 -1
- package/lib/typescript/networking/stanzaHandlers.d.ts +7 -3
- package/lib/typescript/networking/stanzaHandlers.d.ts.map +1 -1
- package/lib/typescript/networking/xmpp/handleStanzas.xmpp.d.ts.map +1 -1
- package/lib/typescript/networking/xmpp/sendDisplayedMarker.xmpp.d.ts +3 -0
- package/lib/typescript/networking/xmpp/sendDisplayedMarker.xmpp.d.ts.map +1 -0
- package/lib/typescript/networking/xmpp/sendMediaMessage.xmpp.d.ts +12 -0
- package/lib/typescript/networking/xmpp/sendMediaMessage.xmpp.d.ts.map +1 -1
- package/lib/typescript/networking/xmpp/sendReceivedReceipt.xmpp.d.ts +3 -0
- package/lib/typescript/networking/xmpp/sendReceivedReceipt.xmpp.d.ts.map +1 -0
- package/lib/typescript/networking/xmppClient.d.ts +15 -0
- package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
- package/lib/typescript/push/pushKeyStore.d.ts +35 -0
- package/lib/typescript/push/pushKeyStore.d.ts.map +1 -0
- package/lib/typescript/push/sealedBox.d.ts +24 -0
- package/lib/typescript/push/sealedBox.d.ts.map +1 -0
- package/lib/typescript/push/sealedPush.d.ts +61 -0
- package/lib/typescript/push/sealedPush.d.ts.map +1 -0
- package/lib/typescript/push/sealedPushTask.d.ts +33 -0
- package/lib/typescript/push/sealedPushTask.d.ts.map +1 -0
- package/lib/typescript/roomStore/persistence.d.ts.map +1 -1
- package/lib/typescript/roomStore/roomHeapSlice.d.ts +22 -1
- package/lib/typescript/roomStore/roomHeapSlice.d.ts.map +1 -1
- package/lib/typescript/roomStore/roomsSlice.d.ts +67 -3
- package/lib/typescript/roomStore/roomsSlice.d.ts.map +1 -1
- package/lib/typescript/services/pushRegistration.d.ts +8 -0
- package/lib/typescript/services/pushRegistration.d.ts.map +1 -1
- package/lib/typescript/types/models/attachment.model.d.ts +52 -0
- package/lib/typescript/types/models/attachment.model.d.ts.map +1 -0
- package/lib/typescript/types/models/message.model.d.ts +26 -0
- package/lib/typescript/types/models/message.model.d.ts.map +1 -1
- package/lib/typescript/types/models/modal.model.d.ts +7 -0
- package/lib/typescript/types/models/modal.model.d.ts.map +1 -1
- package/lib/typescript/types/models/room.model.d.ts +2 -0
- package/lib/typescript/types/models/room.model.d.ts.map +1 -1
- package/lib/typescript/types/types.d.ts +35 -1
- package/lib/typescript/types/types.d.ts.map +1 -1
- package/lib/typescript/version.d.ts +1 -1
- package/package.json +14 -1
- package/plugins/notification-service/Info.plist +31 -0
- package/plugins/notification-service/NotificationService.swift +195 -0
- package/plugins/notification-service/SealedBox.swift +355 -0
- package/plugins/notification-service/check/gen-vectors.mjs +24 -0
- package/plugins/notification-service/check/main.swift +37 -0
- package/plugins/notification-service/check/run.sh +12 -0
- package/plugins/notification-service/check/vectors.json +1 -0
- package/plugins/withNotificationServiceExtension.js +189 -0
- package/src/assets/icons.tsx +20 -2
- package/src/components/MainComponents/AlbumGrid.tsx +183 -0
- package/src/components/MainComponents/ChatRoom.tsx +4 -2
- package/src/components/MainComponents/MediaMessage.tsx +246 -130
- package/src/components/MainComponents/MediaUploadOverlay.tsx +102 -0
- package/src/components/MessageBubble/MediaCaption.tsx +45 -0
- package/src/components/MessageBubble/Message.tsx +71 -3
- package/src/components/MessageBubble/MessageInteractions.tsx +2 -1
- package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +64 -0
- package/src/components/RoomComponents/LastMessageItem.tsx +15 -4
- package/src/components/RoomComponents/styled/LastAudioMessage.tsx +6 -3
- package/src/components/RoomComponents/styled/LastMessageFile.tsx +6 -2
- package/src/components/RoomComponents/styled/LastMessagePhoto.tsx +6 -2
- package/src/components/RoomComponents/styled/LastMessageVideo.tsx +6 -2
- package/src/components/Thread/ThreadWrapper.tsx +5 -9
- package/src/components/styled/MediaFilePreview.tsx +49 -34
- package/src/components/styled/MessageImage.tsx +21 -5
- package/src/components/styled/SendInput.tsx +101 -87
- package/src/components/styled/VideoMessage.tsx +34 -9
- package/src/context/xmppProvider.tsx +32 -0
- package/src/e2ee/fileEnvelope.ts +1 -1
- package/src/e2ee/sealedBody.ts +44 -5
- package/src/helpers/albumLayout.ts +207 -0
- package/src/helpers/attachments.ts +316 -0
- package/src/helpers/caption.ts +283 -0
- package/src/helpers/createMainMessageForThread.ts +6 -1
- package/src/helpers/createMessageFromXml.ts +21 -5
- package/src/helpers/fileKind.ts +38 -0
- package/src/helpers/getDataFromXml.ts +5 -1
- package/src/helpers/mapWithConcurrency.ts +32 -0
- package/src/helpers/mediaLimits.ts +27 -0
- package/src/helpers/mediaSendRetry.ts +59 -0
- package/src/helpers/mediaWireData.ts +57 -0
- package/src/helpers/mentionSpans.ts +17 -0
- package/src/helpers/mimeToExtension.ts +51 -0
- package/src/helpers/readReceipts.ts +219 -0
- package/src/helpers/repairFilenameEncoding.ts +82 -0
- package/src/helpers/safeJson.ts +11 -0
- package/src/helpers/secureStoreRuntime.ts +21 -3
- package/src/hooks/useSendMessage.tsx +448 -249
- package/src/i18n/strings.ts +96 -0
- package/src/main.ts +30 -0
- package/src/networking/api-requests/auth.api.ts +60 -3
- package/src/networking/api-requests/push.api.ts +29 -4
- package/src/networking/stanzaHandlers.ts +91 -2
- package/src/networking/xmpp/handleStanzas.xmpp.ts +6 -2
- package/src/networking/xmpp/sendDisplayedMarker.xmpp.ts +31 -0
- package/src/networking/xmpp/sendMediaMessage.xmpp.ts +60 -3
- package/src/networking/xmpp/sendReceivedReceipt.xmpp.ts +31 -0
- package/src/networking/xmppClient.ts +48 -0
- package/src/push/pushKeyStore.ts +163 -0
- package/src/push/sealedBox.ts +104 -0
- package/src/push/sealedPush.ts +127 -0
- package/src/push/sealedPushTask.ts +175 -0
- package/src/roomStore/persistence.ts +94 -3
- package/src/roomStore/roomHeapSlice.ts +23 -1
- package/src/roomStore/roomsSlice.ts +112 -1
- package/src/services/pushRegistration.ts +25 -5
- package/src/types/models/attachment.model.ts +51 -0
- package/src/types/models/message.model.ts +29 -0
- package/src/types/models/modal.model.ts +7 -0
- package/src/types/models/room.model.ts +2 -0
- package/src/types/types.ts +41 -1
- package/src/version.ts +1 -1
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { IMentionSpan } from '../types/types';
|
|
2
|
+
import { safeJsonParse } from './safeJson';
|
|
3
|
+
import { validateMentionSpans } from './mentionSpans';
|
|
4
|
+
import { getAttachmentName, getMessageAttachments, isMediaMessage } from './attachments';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The text typed with a set of attachments, sent as part of the media
|
|
8
|
+
* message itself (never as a separate text stanza).
|
|
9
|
+
*
|
|
10
|
+
* `mentions` index into `text`, the same way a text message's mention spans
|
|
11
|
+
* index into its body.
|
|
12
|
+
*/
|
|
13
|
+
export interface CaptionInput {
|
|
14
|
+
text: string;
|
|
15
|
+
mentions?: IMentionSpan[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface NormalizedCaption {
|
|
19
|
+
caption: string;
|
|
20
|
+
mentions?: IMentionSpan[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const isSpanShape = (value: unknown): value is IMentionSpan => {
|
|
24
|
+
if (!value || typeof value !== 'object') return false;
|
|
25
|
+
const span = value as Record<string, unknown>;
|
|
26
|
+
return (
|
|
27
|
+
typeof span.jid === 'string' &&
|
|
28
|
+
typeof span.name === 'string' &&
|
|
29
|
+
typeof span.offset === 'number' &&
|
|
30
|
+
Number.isInteger(span.offset) &&
|
|
31
|
+
typeof span.length === 'number' &&
|
|
32
|
+
Number.isInteger(span.length)
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Mention spans for a caption, checked against the caption text: a span that
|
|
38
|
+
* no longer reads "@Name" at its offset is dropped. Undefined when nothing is
|
|
39
|
+
* left, so a caption without mentions carries no empty attribute.
|
|
40
|
+
*/
|
|
41
|
+
export const normalizeCaptionMentions = (
|
|
42
|
+
value: unknown,
|
|
43
|
+
caption: string
|
|
44
|
+
): IMentionSpan[] | undefined => {
|
|
45
|
+
const parsed =
|
|
46
|
+
typeof value === 'string' ? safeJsonParse<unknown>(value, []) : value;
|
|
47
|
+
if (!Array.isArray(parsed)) return undefined;
|
|
48
|
+
const spans = validateMentionSpans(caption, parsed.filter(isSpanShape));
|
|
49
|
+
return spans.length > 0 ? spans : undefined;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Turns what the composer typed into the caption that travels with the
|
|
54
|
+
* media. Undefined for blank text, which means "no caption".
|
|
55
|
+
*/
|
|
56
|
+
export const normalizeCaptionInput = (
|
|
57
|
+
input?: CaptionInput | null
|
|
58
|
+
): NormalizedCaption | undefined => {
|
|
59
|
+
const text = typeof input?.text === 'string' ? input.text : '';
|
|
60
|
+
if (!/\S/.test(text)) return undefined;
|
|
61
|
+
const mentions = normalizeCaptionMentions(input?.mentions, text);
|
|
62
|
+
return mentions ? { caption: text, mentions } : { caption: text };
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** What a media message's text is, and the @-mention spans that index into it. */
|
|
66
|
+
export interface ResolvedCaption {
|
|
67
|
+
text: string;
|
|
68
|
+
mentions?: IMentionSpan[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface CaptionOptions {
|
|
72
|
+
/**
|
|
73
|
+
* The caller already knows this is a media message (a thin `lastMessage`
|
|
74
|
+
* that only says so by its id). Otherwise it is read off the message.
|
|
75
|
+
*/
|
|
76
|
+
isMedia?: boolean;
|
|
77
|
+
/** The message is sealed: only a caption that came out of the envelope counts. */
|
|
78
|
+
sealed?: boolean;
|
|
79
|
+
/** Extra names of the sender, which a body may merely echo. */
|
|
80
|
+
senderNames?: string[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Bodies that are not something a person typed: the media placeholder every
|
|
85
|
+
* SDK sends, and the labels apps generate for a file with nothing to say.
|
|
86
|
+
*/
|
|
87
|
+
const PLACEHOLDER_BODIES = new Set([
|
|
88
|
+
'media',
|
|
89
|
+
'attachment',
|
|
90
|
+
'attachments',
|
|
91
|
+
'photo',
|
|
92
|
+
'photos',
|
|
93
|
+
'image',
|
|
94
|
+
'images',
|
|
95
|
+
'picture',
|
|
96
|
+
'video',
|
|
97
|
+
'audio',
|
|
98
|
+
'voice',
|
|
99
|
+
'voice message',
|
|
100
|
+
'voice note',
|
|
101
|
+
'file',
|
|
102
|
+
'files',
|
|
103
|
+
'document',
|
|
104
|
+
'blob',
|
|
105
|
+
'undefined',
|
|
106
|
+
'null',
|
|
107
|
+
'[object object]',
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
/** `<data>` attributes older or foreign clients stamp the file's text on. */
|
|
111
|
+
const CAPTION_ALIASES = ['captionText', 'text', 'message', 'description', 'title'] as const;
|
|
112
|
+
|
|
113
|
+
const norm = (value: unknown): string =>
|
|
114
|
+
typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
115
|
+
|
|
116
|
+
const str = (value: unknown): string =>
|
|
117
|
+
typeof value === 'string' ? value : typeof value === 'number' ? String(value) : '';
|
|
118
|
+
|
|
119
|
+
const tail = (url: string): string => {
|
|
120
|
+
const path = url.split(/[?#]/)[0];
|
|
121
|
+
return path.slice(path.lastIndexOf('/') + 1);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const safeDecode = (value: string): string => {
|
|
125
|
+
try {
|
|
126
|
+
return decodeURIComponent(value);
|
|
127
|
+
} catch {
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/** Every string a media message's `body` is allowed to merely repeat. */
|
|
133
|
+
const echoCandidates = (
|
|
134
|
+
message: Record<string, any>,
|
|
135
|
+
options: CaptionOptions
|
|
136
|
+
): Set<string> => {
|
|
137
|
+
const out = new Set<string>();
|
|
138
|
+
const add = (value: unknown) => {
|
|
139
|
+
const v = norm(value);
|
|
140
|
+
if (v) out.add(v);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// The file itself: its names and its URLs, as sent and as stored.
|
|
144
|
+
const attachments = getMessageAttachments(message as never);
|
|
145
|
+
for (const attachment of attachments) {
|
|
146
|
+
add(getAttachmentName(attachment));
|
|
147
|
+
add(attachment.originalName);
|
|
148
|
+
add(attachment.fileName);
|
|
149
|
+
for (const url of [attachment.location, attachment.locationPreview]) {
|
|
150
|
+
if (!url) continue;
|
|
151
|
+
add(url);
|
|
152
|
+
add(safeDecode(url));
|
|
153
|
+
add(tail(url));
|
|
154
|
+
add(safeDecode(tail(url)));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
add(message.originalName);
|
|
158
|
+
add(message.fileName);
|
|
159
|
+
add(message.location);
|
|
160
|
+
add(message.locationPreview);
|
|
161
|
+
|
|
162
|
+
// The sender: some clients put their own name, e-mail or username in the
|
|
163
|
+
// body next to the file.
|
|
164
|
+
const user = (message.user ?? {}) as Record<string, unknown>;
|
|
165
|
+
const first = str(message.senderFirstName) || str(message.firstName) || str(user.firstName);
|
|
166
|
+
const last = str(message.senderLastName) || str(message.lastName) || str(user.lastName);
|
|
167
|
+
add(user.name);
|
|
168
|
+
add(`${first} ${last}`);
|
|
169
|
+
add(first && !last ? first : '');
|
|
170
|
+
add(user.email);
|
|
171
|
+
add(user.username);
|
|
172
|
+
add(message.email);
|
|
173
|
+
add(message.userName);
|
|
174
|
+
add(message.username);
|
|
175
|
+
add(message.senderName);
|
|
176
|
+
const idLocal = str(user.id).split('@')[0];
|
|
177
|
+
add(idLocal);
|
|
178
|
+
add(str(user.id).split('_').pop());
|
|
179
|
+
add(str(message.senderJID).split('/')[0].split('@')[0]);
|
|
180
|
+
for (const name of options.senderNames ?? []) add(name);
|
|
181
|
+
return out;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/** "etest etest" is echoed as "etest": every word of the name is the body. */
|
|
185
|
+
const isRepeatedName = (name: string, text: string): boolean =>
|
|
186
|
+
Boolean(name && text) && name.split(/\s+/).every((part) => part === text);
|
|
187
|
+
|
|
188
|
+
const isRealCaptionText = (
|
|
189
|
+
text: string,
|
|
190
|
+
message: Record<string, any>,
|
|
191
|
+
options: CaptionOptions
|
|
192
|
+
): boolean => {
|
|
193
|
+
const t = text.trim();
|
|
194
|
+
if (!t) return false;
|
|
195
|
+
const n = t.toLowerCase();
|
|
196
|
+
if (PLACEHOLDER_BODIES.has(n)) return false;
|
|
197
|
+
// The key envelope a sealed message carries in its body is never text.
|
|
198
|
+
if (/^\{\s*"v"\s*:\s*\d+\s*,\s*"keys"/.test(t)) return false;
|
|
199
|
+
const candidates = echoCandidates(message, options);
|
|
200
|
+
if (candidates.has(n)) return false;
|
|
201
|
+
for (const name of candidates) if (isRepeatedName(name, n)) return false;
|
|
202
|
+
return true;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const asRecord = (message: unknown): Record<string, any> | undefined =>
|
|
206
|
+
message && typeof message === 'object' ? (message as Record<string, any>) : undefined;
|
|
207
|
+
|
|
208
|
+
const resolveSpans = (
|
|
209
|
+
message: Record<string, any>,
|
|
210
|
+
text: string,
|
|
211
|
+
keys: string[]
|
|
212
|
+
): IMentionSpan[] | undefined => {
|
|
213
|
+
for (const key of keys) {
|
|
214
|
+
const spans = normalizeCaptionMentions(message[key] ?? message.data?.[key], text);
|
|
215
|
+
if (spans) return spans;
|
|
216
|
+
}
|
|
217
|
+
return undefined;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* The text of a media message, from whichever format carried it. The one
|
|
222
|
+
* place that knows the formats, so every surface (bubble, room list preview,
|
|
223
|
+
* notification, reply quote, copy) agrees:
|
|
224
|
+
*
|
|
225
|
+
* 1. `caption` (+ `captionMentions`): what this SDK sends. The only source
|
|
226
|
+
* for a sealed message, whose caption comes out of the envelope.
|
|
227
|
+
* 2. The caption under another name on `<data>` (`captionText`, `text`,
|
|
228
|
+
* `message`, `description`, `title`) from other clients.
|
|
229
|
+
* 3. A real `body`: older clients and other SDKs put the text in the
|
|
230
|
+
* `<body>` of the media message itself. "media", the file's name or URL,
|
|
231
|
+
* the sender's own name or e-mail and generated labels are not text.
|
|
232
|
+
*
|
|
233
|
+
* Never derived for a sealed message, and never for a message with no file.
|
|
234
|
+
*/
|
|
235
|
+
export const resolveMessageCaption = (
|
|
236
|
+
message?: object | null,
|
|
237
|
+
options: CaptionOptions = {}
|
|
238
|
+
): ResolvedCaption | undefined => {
|
|
239
|
+
const source = asRecord(message);
|
|
240
|
+
if (!source || source.isDeleted) return undefined;
|
|
241
|
+
|
|
242
|
+
const explicit = source.caption;
|
|
243
|
+
if (typeof explicit === 'string' && /\S/.test(explicit)) {
|
|
244
|
+
const mentions = normalizeCaptionMentions(source.captionMentions, explicit);
|
|
245
|
+
return mentions ? { text: explicit, mentions } : { text: explicit };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const sealed =
|
|
249
|
+
options.sealed === true ||
|
|
250
|
+
source.clientEncrypted === 'true' ||
|
|
251
|
+
(Array.isArray(source.e2eeKeys) && source.e2eeKeys.length > 0);
|
|
252
|
+
if (sealed) return undefined;
|
|
253
|
+
|
|
254
|
+
const isMedia = options.isMedia ?? isMediaMessage(source as never);
|
|
255
|
+
if (!isMedia) return undefined;
|
|
256
|
+
|
|
257
|
+
for (const key of CAPTION_ALIASES) {
|
|
258
|
+
const value = str(source[key] ?? source.data?.[key]);
|
|
259
|
+
if (value && isRealCaptionText(value, source, options)) {
|
|
260
|
+
const mentions = resolveSpans(source, value, ['captionMentions']);
|
|
261
|
+
return mentions ? { text: value, mentions } : { text: value };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const body = str(source.body);
|
|
266
|
+
if (body && isRealCaptionText(body, source, options)) {
|
|
267
|
+
const mentions = resolveSpans(source, body, ['mentions']);
|
|
268
|
+
return mentions ? { text: body, mentions } : { text: body };
|
|
269
|
+
}
|
|
270
|
+
return undefined;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
/** The caption a message carries, or undefined when it has none. */
|
|
274
|
+
export const getMessageCaption = (
|
|
275
|
+
message?: object | null,
|
|
276
|
+
options?: CaptionOptions
|
|
277
|
+
): string | undefined => resolveMessageCaption(message, options)?.text;
|
|
278
|
+
|
|
279
|
+
/** The mention spans of `getMessageCaption`'s text. */
|
|
280
|
+
export const getMessageCaptionMentions = (
|
|
281
|
+
message?: object | null,
|
|
282
|
+
options?: CaptionOptions
|
|
283
|
+
): IMentionSpan[] | undefined => resolveMessageCaption(message, options)?.mentions;
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { IMessage } from '../types/types';
|
|
2
|
+
import { getMessageCaption } from './caption';
|
|
2
3
|
|
|
3
4
|
export const createMainMessageForThread = (message: IMessage): string => {
|
|
5
|
+
// A media message is quoted by its caption. A sealed one never is: the
|
|
6
|
+
// reply stanza travels in the clear, the caption does not.
|
|
7
|
+
const caption =
|
|
8
|
+
message.clientEncrypted === 'true' ? undefined : getMessageCaption(message);
|
|
4
9
|
const data = {
|
|
5
|
-
text: message.body,
|
|
10
|
+
text: caption ?? message.body,
|
|
6
11
|
id: message.id,
|
|
7
12
|
userName: message.user.name,
|
|
8
13
|
createdAt: message.date,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { IMessage } from '../types/types';
|
|
2
2
|
import { isE2eeRoom } from '../e2ee';
|
|
3
3
|
import { composeName } from './displayName';
|
|
4
|
-
import {
|
|
4
|
+
import { parseSealedMediaPayload } from '../e2ee/sealedBody';
|
|
5
|
+
import { normalizeMediaDataAttrs } from './mediaWireData';
|
|
5
6
|
|
|
6
7
|
interface IMessageWithNewData extends IMessage {
|
|
7
8
|
[x: string]: any;
|
|
@@ -66,7 +67,9 @@ export const createMessageFromXml = async (
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
const merged: any = {
|
|
69
|
-
|
|
70
|
+
// Idempotent: the wrapped path already ran it in getDataFromXml, the
|
|
71
|
+
// positional (MAM catch-up) path hands over the raw attrs.
|
|
72
|
+
...normalizeMediaDataAttrs(inner),
|
|
70
73
|
...outerExtras,
|
|
71
74
|
isDeleted: !!outerExtras?.deleted || !!(arg as any)?.isDeleted,
|
|
72
75
|
};
|
|
@@ -140,15 +143,28 @@ export const createMessageFromXml = async (
|
|
|
140
143
|
const unencrypted =
|
|
141
144
|
merged.omemoEncrypted !== 'true' && isE2eeRoom(String(merged.roomJid || ''));
|
|
142
145
|
|
|
143
|
-
|
|
146
|
+
// A sealed media body is `{v:1,keys,caption?,captionMentions?}`. The keys
|
|
147
|
+
// and the caption are lifted next to each other; the caption is the only
|
|
148
|
+
// one a sealed message may show (any open caption attribute was dropped
|
|
149
|
+
// by normalizeMediaDataAttrs).
|
|
150
|
+
const sealed =
|
|
144
151
|
merged.clientEncrypted === 'true'
|
|
145
|
-
?
|
|
152
|
+
? parseSealedMediaPayload(merged.body)
|
|
146
153
|
: undefined;
|
|
147
154
|
|
|
148
155
|
const message: IMessage = {
|
|
149
156
|
...merged,
|
|
150
157
|
...(unencrypted ? { unencrypted: true } : {}),
|
|
151
|
-
...(
|
|
158
|
+
...(sealed
|
|
159
|
+
? {
|
|
160
|
+
e2eeKeys: sealed.keys,
|
|
161
|
+
body: 'media',
|
|
162
|
+
...(sealed.caption ? { caption: sealed.caption } : {}),
|
|
163
|
+
...(sealed.captionMentions
|
|
164
|
+
? { captionMentions: sealed.captionMentions }
|
|
165
|
+
: {}),
|
|
166
|
+
}
|
|
167
|
+
: {}),
|
|
152
168
|
user: {
|
|
153
169
|
...(merged.user || {}),
|
|
154
170
|
id: merged?.user?.id || fromResource || senderLocal,
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { filenameFromUrl, isLikelyAudio } from './mimeToExtension';
|
|
2
|
+
|
|
3
|
+
export type FileKind = 'image' | 'video' | 'audio' | 'file';
|
|
4
|
+
|
|
5
|
+
interface FileKindInput {
|
|
6
|
+
mimetype?: string | null;
|
|
7
|
+
name?: string | null;
|
|
8
|
+
url?: string | null;
|
|
9
|
+
duration?: number | string | null;
|
|
10
|
+
waveForm?: string | null;
|
|
11
|
+
originalName?: string | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Which card a file gets in a bubble. Mirrors the branches MediaMessage has
|
|
16
|
+
* always used for a single file (image, video, audio, octet-stream as a voice
|
|
17
|
+
* note, then the voice-message heuristics) so an album, a card stack and a
|
|
18
|
+
* lone file all agree on what a given file is.
|
|
19
|
+
*/
|
|
20
|
+
export const getFileKind = (input: FileKindInput): FileKind => {
|
|
21
|
+
const mime = (input.mimetype || '').toLowerCase();
|
|
22
|
+
if (mime.startsWith('image/')) {return 'image';}
|
|
23
|
+
if (mime.startsWith('video/')) {return 'video';}
|
|
24
|
+
if (mime.startsWith('audio/')) {return 'audio';}
|
|
25
|
+
if (mime.includes('application/octet-stream')) {return 'audio';}
|
|
26
|
+
const name = input.name || filenameFromUrl(input.url);
|
|
27
|
+
return isLikelyAudio(mime, name, input.url, {
|
|
28
|
+
duration: input.duration,
|
|
29
|
+
waveForm: input.waveForm,
|
|
30
|
+
originalName: input.originalName,
|
|
31
|
+
})
|
|
32
|
+
? 'audio'
|
|
33
|
+
: 'file';
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** True for the kinds that sit in the picture/video album. */
|
|
37
|
+
export const isAlbumKind = (kind: FileKind): boolean =>
|
|
38
|
+
kind === 'image' || kind === 'video';
|
|
@@ -2,6 +2,7 @@ import { Element } from 'ltx';
|
|
|
2
2
|
import { Iso639_1Codes, IUser } from '../types/types';
|
|
3
3
|
import { transformArrayToObject } from './transformTranslatations';
|
|
4
4
|
import { normalizeXmppUsername } from './xmppUsername';
|
|
5
|
+
import { normalizeMediaDataAttrs } from './mediaWireData';
|
|
5
6
|
|
|
6
7
|
const getLocalpartFromJid = (jid?: string): string | undefined => {
|
|
7
8
|
const value = String(jid || '').trim();
|
|
@@ -122,7 +123,10 @@ export const getDataFromXml = async (stanza: Element): Promise<DataXml | undefin
|
|
|
122
123
|
photoURL,
|
|
123
124
|
} as any;
|
|
124
125
|
|
|
125
|
-
|
|
126
|
+
// `attachments` and `caption` ride on <data> as strings; decode them here
|
|
127
|
+
// so every consumer sees the typed values (sealed captions are lifted from
|
|
128
|
+
// the encrypted body later, see createMessageFromXml).
|
|
129
|
+
const dataAttrs = normalizeMediaDataAttrs(data?.attrs || {});
|
|
126
130
|
|
|
127
131
|
return {
|
|
128
132
|
data: dataAttrs,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs `worker` over `items` with at most `limit` calls in flight, and
|
|
3
|
+
* resolves with the results in INPUT order, whatever order the calls finish
|
|
4
|
+
* in. Workers that throw are reported as rejected entries instead of
|
|
5
|
+
* aborting the rest of the batch, so one failed upload never stops its
|
|
6
|
+
* siblings.
|
|
7
|
+
*/
|
|
8
|
+
export async function mapWithConcurrency<T, R>(
|
|
9
|
+
items: readonly T[],
|
|
10
|
+
limit: number,
|
|
11
|
+
worker: (item: T, index: number) => Promise<R>
|
|
12
|
+
): Promise<PromiseSettledResult<R>[]> {
|
|
13
|
+
const results: PromiseSettledResult<R>[] = new Array(items.length);
|
|
14
|
+
const width = Math.max(1, Math.min(Math.floor(limit) || 1, items.length));
|
|
15
|
+
let next = 0;
|
|
16
|
+
|
|
17
|
+
const lane = async (): Promise<void> => {
|
|
18
|
+
while (next < items.length) {
|
|
19
|
+
const index = next;
|
|
20
|
+
next += 1;
|
|
21
|
+
try {
|
|
22
|
+
const value = await worker(items[index]!, index);
|
|
23
|
+
results[index] = { status: 'fulfilled', value };
|
|
24
|
+
} catch (reason) {
|
|
25
|
+
results[index] = { status: 'rejected', reason };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
await Promise.all(Array.from({ length: width }, () => lane()));
|
|
31
|
+
return results;
|
|
32
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { IConfig } from '../types/types';
|
|
2
|
+
|
|
3
|
+
/** Hard client-side cap on a single upload. Mirrors the backend's body limit. */
|
|
4
|
+
export const MAX_MEDIA_BYTES = 50 * 1024 * 1024;
|
|
5
|
+
|
|
6
|
+
/** The most files one message may carry (also the parser-side ceiling). */
|
|
7
|
+
export const MAX_MEDIA_FILES = 10;
|
|
8
|
+
|
|
9
|
+
/** Files per message the host allows: `attachments.maxFiles`, clamped to 1-10. */
|
|
10
|
+
export const mediaFileCap = (config?: Pick<IConfig, 'attachments'> | null): number => {
|
|
11
|
+
const configured = Number(config?.attachments?.maxFiles);
|
|
12
|
+
if (!Number.isFinite(configured)) {return MAX_MEDIA_FILES;}
|
|
13
|
+
return Math.min(MAX_MEDIA_FILES, Math.max(1, Math.floor(configured)));
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Bytes allowed per file: the 50 MB upload cap, lowered by
|
|
18
|
+
* `attachments.maxSizeMB` when the host sets a smaller one.
|
|
19
|
+
*/
|
|
20
|
+
export const mediaSizeLimit = (
|
|
21
|
+
config?: Pick<IConfig, 'attachments'> | null,
|
|
22
|
+
override?: number
|
|
23
|
+
): number => {
|
|
24
|
+
const base = typeof override === 'number' && override > 0 ? Math.min(override, MAX_MEDIA_BYTES) : MAX_MEDIA_BYTES;
|
|
25
|
+
const mb = Number(config?.attachments?.maxSizeMB);
|
|
26
|
+
return Number.isFinite(mb) && mb > 0 ? Math.min(base, mb * 1024 * 1024) : base;
|
|
27
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry hooks for media messages whose uploads did not all land.
|
|
3
|
+
*
|
|
4
|
+
* The send closure lives inside useSendMessage, but the bubble that shows
|
|
5
|
+
* "Retry" on a tile is somewhere else in the tree and has no access to it.
|
|
6
|
+
* The send registers its retry runner under the message id while the
|
|
7
|
+
* message is unsent, and the bubble looks it up here. The runner is removed
|
|
8
|
+
* as soon as the message is delivered.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type MediaRetryRunner = (indices?: number[]) => Promise<boolean>;
|
|
12
|
+
|
|
13
|
+
const runners = new Map<string, MediaRetryRunner>();
|
|
14
|
+
|
|
15
|
+
export const registerMediaRetry = (messageId: string, runner: MediaRetryRunner): void => {
|
|
16
|
+
runners.set(messageId, runner);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const unregisterMediaRetry = (messageId: string): void => {
|
|
20
|
+
runners.delete(messageId);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const hasMediaRetry = (messageId: string): boolean => runners.has(messageId);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Re-runs the uploads still missing for `messageId` (only `index` / `indices`
|
|
27
|
+
* when given), then sends the message once every file is in. Resolves to
|
|
28
|
+
* false when nothing is registered or the attempt did not deliver.
|
|
29
|
+
*/
|
|
30
|
+
export const retryMediaSend = async (
|
|
31
|
+
messageId: string,
|
|
32
|
+
index?: number | number[]
|
|
33
|
+
): Promise<boolean> => {
|
|
34
|
+
const runner = runners.get(messageId);
|
|
35
|
+
if (!runner) {return false;}
|
|
36
|
+
const indices = index === undefined ? undefined : Array.isArray(index) ? index : [index];
|
|
37
|
+
return runner(indices);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const pendingRetryIndices = new Map<string, Set<number>>();
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Asks for one file of `messageId` to be retried. Requests made in the same
|
|
44
|
+
* tick are merged into one run: the runner takes a single run at a time, so
|
|
45
|
+
* a separate call per tile would drop every retry after the first.
|
|
46
|
+
*/
|
|
47
|
+
export const requestMediaRetry = (messageId: string, index: number): void => {
|
|
48
|
+
const batch = pendingRetryIndices.get(messageId);
|
|
49
|
+
if (batch) {
|
|
50
|
+
batch.add(index);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const next = new Set<number>([index]);
|
|
54
|
+
pendingRetryIndices.set(messageId, next);
|
|
55
|
+
queueMicrotask(() => {
|
|
56
|
+
pendingRetryIndices.delete(messageId);
|
|
57
|
+
retryMediaSend(messageId, [...next]).catch(() => false);
|
|
58
|
+
});
|
|
59
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { parseAttachments } from './attachments';
|
|
2
|
+
import { getMessageCaption, normalizeCaptionMentions } from './caption';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Normalises the media parts of a message's flat `<data>` attributes:
|
|
6
|
+
*
|
|
7
|
+
* - `attachments` (a JSON string on the wire) becomes the typed array, or is
|
|
8
|
+
* dropped when it decodes to nothing so the flat legacy fields win;
|
|
9
|
+
* - `caption` is kept only when it holds text, with `captionMentions` (JSON
|
|
10
|
+
* on the wire) checked against it;
|
|
11
|
+
* - in a sealed message (`clientEncrypted`) the caption attributes are
|
|
12
|
+
* always dropped: the only caption such a message may show is the one
|
|
13
|
+
* inside its encrypted body, which the caller lifts afterwards.
|
|
14
|
+
*
|
|
15
|
+
* Safe to apply twice, so the wrapped and the positional parsers can both
|
|
16
|
+
* call it.
|
|
17
|
+
*/
|
|
18
|
+
export const normalizeMediaDataAttrs = (
|
|
19
|
+
attrs: Record<string, any>
|
|
20
|
+
): Record<string, any> => {
|
|
21
|
+
const out: Record<string, any> = { ...attrs };
|
|
22
|
+
|
|
23
|
+
if ('attachments' in out) {
|
|
24
|
+
const attachments = parseAttachments(out.attachments);
|
|
25
|
+
if (attachments.length > 0) {
|
|
26
|
+
out.attachments = attachments;
|
|
27
|
+
} else {
|
|
28
|
+
delete out.attachments;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (out.clientEncrypted === 'true') {
|
|
33
|
+
delete out.caption;
|
|
34
|
+
delete out.captionMentions;
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if ('caption' in out) {
|
|
39
|
+
const caption = getMessageCaption({ caption: out.caption });
|
|
40
|
+
if (caption) {
|
|
41
|
+
out.caption = caption;
|
|
42
|
+
const spans = normalizeCaptionMentions(out.captionMentions, caption);
|
|
43
|
+
if (spans) {
|
|
44
|
+
out.captionMentions = spans;
|
|
45
|
+
} else {
|
|
46
|
+
delete out.captionMentions;
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
delete out.caption;
|
|
50
|
+
delete out.captionMentions;
|
|
51
|
+
}
|
|
52
|
+
} else if ('captionMentions' in out) {
|
|
53
|
+
delete out.captionMentions;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return out;
|
|
57
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { IMentionSpan } from '../types/models/message.model';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Keeps only the spans that still read "@Name" at their offset in `text`.
|
|
5
|
+
* RN does not render mentions; a caption's spans are stored (and checked) so
|
|
6
|
+
* they survive a round trip to a client that does.
|
|
7
|
+
*/
|
|
8
|
+
export const validateMentionSpans = (
|
|
9
|
+
text: string,
|
|
10
|
+
spans: IMentionSpan[]
|
|
11
|
+
): IMentionSpan[] =>
|
|
12
|
+
spans.filter((span) => {
|
|
13
|
+
if (span.offset < 0 || span.offset + span.length > text.length) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return text.slice(span.offset, span.offset + span.length) === `@${span.name}`;
|
|
17
|
+
});
|
|
@@ -64,6 +64,57 @@ const MIME_TO_EXT: Record<string, string> = {
|
|
|
64
64
|
|
|
65
65
|
const VALID_EXT_RE = /\.[a-z0-9]{1,8}$/i;
|
|
66
66
|
|
|
67
|
+
// Extensions that stand for a picture, video, audio or PDF a bubble can
|
|
68
|
+
// show, read back from the table above (first entry per extension wins).
|
|
69
|
+
// `.webm` and `.svg` are left out on purpose: a recorded voice note is often
|
|
70
|
+
// a `.webm` and must stay generic (audio), and SVG is not a bubble picture.
|
|
71
|
+
const INFERRABLE_EXT_TO_MIME: Record<string, string> = {};
|
|
72
|
+
for (const [mime, ext] of Object.entries(MIME_TO_EXT)) {
|
|
73
|
+
const inferrable =
|
|
74
|
+
mime === 'application/pdf' ||
|
|
75
|
+
mime.startsWith('image/') ||
|
|
76
|
+
mime.startsWith('video/') ||
|
|
77
|
+
mime.startsWith('audio/');
|
|
78
|
+
const bare = ext.slice(1);
|
|
79
|
+
if (inferrable && bare !== 'webm' && bare !== 'svg' && !INFERRABLE_EXT_TO_MIME[bare]) {
|
|
80
|
+
INFERRABLE_EXT_TO_MIME[bare] = mime;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Spellings the table above has no entry for.
|
|
84
|
+
Object.assign(INFERRABLE_EXT_TO_MIME, {
|
|
85
|
+
jpeg: 'image/jpeg',
|
|
86
|
+
jpe: 'image/jpeg',
|
|
87
|
+
tif: 'image/tiff',
|
|
88
|
+
avif: 'image/avif',
|
|
89
|
+
avi: 'video/x-msvideo',
|
|
90
|
+
oga: 'audio/ogg',
|
|
91
|
+
opus: 'audio/ogg',
|
|
92
|
+
amr: 'audio/amr',
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
/** A mimetype that says nothing: empty, or the storage's "could not sniff". */
|
|
96
|
+
export function isGenericMime(mime: string | undefined | null): boolean {
|
|
97
|
+
const normalized = (mime || '').toLowerCase().split(';')[0]!.trim();
|
|
98
|
+
return (
|
|
99
|
+
!normalized ||
|
|
100
|
+
normalized === 'application/octet-stream' ||
|
|
101
|
+
normalized === 'binary/octet-stream'
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The mimetype a file name's extension stands for, for the pictures, videos,
|
|
107
|
+
* audio and PDFs a bubble can show. Undefined for anything else.
|
|
108
|
+
*/
|
|
109
|
+
export function inferMimeFromName(
|
|
110
|
+
name: string | undefined | null
|
|
111
|
+
): string | undefined {
|
|
112
|
+
const base = String(name || '').split(/[?#]/)[0]!;
|
|
113
|
+
const dot = base.lastIndexOf('.');
|
|
114
|
+
if (dot < 1) {return undefined;}
|
|
115
|
+
return INFERRABLE_EXT_TO_MIME[base.slice(dot + 1).toLowerCase()];
|
|
116
|
+
}
|
|
117
|
+
|
|
67
118
|
// Audio file extensions we recognise across the SDK. Used for the
|
|
68
119
|
// "looks like audio even when the mimetype is application/octet-stream"
|
|
69
120
|
// heuristic in MediaMessage / FilePreviewModal. Voice-message senders
|