@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,175 @@
|
|
|
1
|
+
import { AppState, Platform } from 'react-native';
|
|
2
|
+
import { pushLog } from '../utils/devLogger';
|
|
3
|
+
import { loadPushSecretKey } from './pushKeyStore';
|
|
4
|
+
import { openSealedPush, sealedPushEnvelopeOf, type OpenSealedPushResult } from './sealedPush';
|
|
5
|
+
|
|
6
|
+
// Android half of encrypted pushes. A sealed push is data-only, so the OS
|
|
7
|
+
// shows nothing by itself: expo-notifications hands it to a background
|
|
8
|
+
// task (expo-task-manager, foreground, background and killed app alike)
|
|
9
|
+
// and this task opens the box and posts the notification. iOS does the
|
|
10
|
+
// same in the notification service extension; here `registerSealedPushTask`
|
|
11
|
+
// is a no-op.
|
|
12
|
+
//
|
|
13
|
+
// Both packages are optional peers, required lazily: a host that does not
|
|
14
|
+
// want encrypted pushes never pays for them.
|
|
15
|
+
|
|
16
|
+
export const SEALED_PUSH_TASK = 'ETHORA_SEALED_PUSH';
|
|
17
|
+
|
|
18
|
+
export interface SealedPushTaskOptions {
|
|
19
|
+
/** Android notification channel; the OS default channel when omitted. */
|
|
20
|
+
channelId?: string;
|
|
21
|
+
/** Shown when the box does not open (no key, another key, damaged). Default "New message". */
|
|
22
|
+
placeholderTitle?: string;
|
|
23
|
+
placeholderBody?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const PLACEHOLDER_TITLE = 'New message';
|
|
27
|
+
|
|
28
|
+
let registered = false;
|
|
29
|
+
|
|
30
|
+
const loadTaskManager = (): any => {
|
|
31
|
+
try {
|
|
32
|
+
return require('expo-task-manager');
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const loadNotifications = (): any => {
|
|
39
|
+
try {
|
|
40
|
+
return require('expo-notifications');
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The FCM data map out of the task payload. expo-notifications passes the
|
|
48
|
+
* message as `{ notification, data }`; the data keys are on `data`
|
|
49
|
+
* itself, `data.dataString` carries the same as a JSON string on some
|
|
50
|
+
* versions, and a Firebase `RemoteMessage` keeps them under `data.data`.
|
|
51
|
+
*/
|
|
52
|
+
export const sealedPushDataOf = (payload: unknown): Record<string, unknown> | null => {
|
|
53
|
+
const root = payload && typeof payload === 'object' ? (payload as Record<string, unknown>) : null;
|
|
54
|
+
if (!root) {return null;}
|
|
55
|
+
const candidates: unknown[] = [root.data, root];
|
|
56
|
+
const data = root.data && typeof root.data === 'object' ? (root.data as Record<string, unknown>) : null;
|
|
57
|
+
if (data) {
|
|
58
|
+
if (typeof data.dataString === 'string') {
|
|
59
|
+
try {
|
|
60
|
+
candidates.unshift(JSON.parse(data.dataString));
|
|
61
|
+
} catch {
|
|
62
|
+
// not JSON — fall through to the map itself
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
candidates.push(data.data);
|
|
66
|
+
}
|
|
67
|
+
for (const candidate of candidates) {
|
|
68
|
+
if (sealedPushEnvelopeOf(candidate as Record<string, unknown>)) {
|
|
69
|
+
return candidate as Record<string, unknown>;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const notificationContent = (result: OpenSealedPushResult, options: SealedPushTaskOptions) => {
|
|
76
|
+
const { envelope } = result;
|
|
77
|
+
// What the tap handler (`handlePushPayload` through the host's response
|
|
78
|
+
// listener) needs: the same ids a plain push carries on `data`.
|
|
79
|
+
const data = {
|
|
80
|
+
jid: envelope.jid,
|
|
81
|
+
msgID: envelope.msgID,
|
|
82
|
+
userJid: envelope.userJid,
|
|
83
|
+
sealed: true,
|
|
84
|
+
};
|
|
85
|
+
if (!result.ok) {
|
|
86
|
+
return {
|
|
87
|
+
title: options.placeholderTitle || PLACEHOLDER_TITLE,
|
|
88
|
+
...(options.placeholderBody ? { body: options.placeholderBody } : {}),
|
|
89
|
+
data,
|
|
90
|
+
sound: 'default',
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const { content } = result;
|
|
94
|
+
return {
|
|
95
|
+
title: content.title || content.sender || content.chat || PLACEHOLDER_TITLE,
|
|
96
|
+
body: content.body,
|
|
97
|
+
data: { ...data, chatJid: content.chatJid, senderJid: content.senderJid },
|
|
98
|
+
sound: 'default',
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Open a sealed push and show it. One notification per chat: the room JID
|
|
104
|
+
* is the identifier, so a newer message replaces the older one the way the
|
|
105
|
+
* service's collapse key does for plain pushes.
|
|
106
|
+
*/
|
|
107
|
+
export async function presentSealedPush(
|
|
108
|
+
data: Record<string, unknown>,
|
|
109
|
+
options: SealedPushTaskOptions = {}
|
|
110
|
+
): Promise<OpenSealedPushResult | null> {
|
|
111
|
+
const envelope = sealedPushEnvelopeOf(data);
|
|
112
|
+
if (!envelope) {return null;}
|
|
113
|
+
const notifications = loadNotifications();
|
|
114
|
+
if (!notifications?.scheduleNotificationAsync) {return null;}
|
|
115
|
+
|
|
116
|
+
const result = openSealedPush(envelope, await loadPushSecretKey());
|
|
117
|
+
// console, not only pushLog: with the app killed this runs headless and
|
|
118
|
+
// `adb logcat -s ReactNativeJS` is the only place to see it.
|
|
119
|
+
if (result.ok) {
|
|
120
|
+
console.log(`[push] sealed push kid=${envelope.kid} opened for ${envelope.jid}`);
|
|
121
|
+
} else {
|
|
122
|
+
console.warn(`[push] sealed push kid=${envelope.kid} not opened (${result.reason}), showing the placeholder`);
|
|
123
|
+
pushLog('warn', `push: sealed push not opened (${result.reason}), showing the placeholder`);
|
|
124
|
+
}
|
|
125
|
+
await notifications.scheduleNotificationAsync({
|
|
126
|
+
identifier: envelope.jid || envelope.msgID || undefined,
|
|
127
|
+
content: notificationContent(result, options),
|
|
128
|
+
trigger: options.channelId ? { channelId: options.channelId } : null,
|
|
129
|
+
});
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Define and register the background task that renders sealed pushes on
|
|
135
|
+
* Android. Call it at module scope of a file the app loads first (the
|
|
136
|
+
* entry file, or the root layout with expo-router): a killed app is
|
|
137
|
+
* started headless to run it, and the task must exist by then. No-op on
|
|
138
|
+
* iOS and when expo-task-manager / expo-notifications are not installed.
|
|
139
|
+
*/
|
|
140
|
+
export function registerSealedPushTask(options: SealedPushTaskOptions = {}): boolean {
|
|
141
|
+
if (Platform.OS !== 'android' || registered) {return registered;}
|
|
142
|
+
const taskManager = loadTaskManager();
|
|
143
|
+
const notifications = loadNotifications();
|
|
144
|
+
if (!taskManager?.defineTask || !notifications?.registerTaskAsync) {
|
|
145
|
+
pushLog(
|
|
146
|
+
'warn',
|
|
147
|
+
'push: expo-task-manager and expo-notifications are needed to show encrypted pushes on Android'
|
|
148
|
+
);
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
taskManager.defineTask(SEALED_PUSH_TASK, async ({ data, error }: { data: unknown; error: unknown }) => {
|
|
152
|
+
if (error) {return;}
|
|
153
|
+
const map = sealedPushDataOf(data);
|
|
154
|
+
if (!map) {return;}
|
|
155
|
+
// In the foreground the room is live over XMPP; the host's
|
|
156
|
+
// notification handler decides about plain pushes there too.
|
|
157
|
+
if (AppState.currentState === 'active') {return;}
|
|
158
|
+
try {
|
|
159
|
+
await presentSealedPush(map, options);
|
|
160
|
+
} catch (e) {
|
|
161
|
+
pushLog('warn', 'push: showing a sealed push failed', e);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
registered = true;
|
|
165
|
+
notifications.registerTaskAsync(SEALED_PUSH_TASK).catch((e: unknown) => {
|
|
166
|
+
registered = false;
|
|
167
|
+
pushLog('warn', 'push: registering the sealed push task failed', e);
|
|
168
|
+
});
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Test seam only. */
|
|
173
|
+
export const __resetSealedPushTaskForTests = (): void => {
|
|
174
|
+
registered = false;
|
|
175
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Middleware } from '@reduxjs/toolkit';
|
|
2
2
|
import { IMessage, IRoom, User } from '../types/types';
|
|
3
|
-
import type { FailedMessagePayload } from './roomHeapSlice';
|
|
3
|
+
import type { FailedMediaFile, FailedMessagePayload } from './roomHeapSlice';
|
|
4
4
|
import {
|
|
5
5
|
getPersistBackend,
|
|
6
6
|
legacyAsyncStorageBackend,
|
|
@@ -107,6 +107,11 @@ const PERSISTED_MESSAGE_FIELDS: (keyof IMessage)[] = [
|
|
|
107
107
|
'fileName',
|
|
108
108
|
'originalName',
|
|
109
109
|
'size',
|
|
110
|
+
// Every file of a multi-attach message and its caption: without them a
|
|
111
|
+
// restored album paints as its first file only (see compactAttachments).
|
|
112
|
+
'attachments',
|
|
113
|
+
'caption',
|
|
114
|
+
'captionMentions',
|
|
110
115
|
'langSource',
|
|
111
116
|
// `translations` MUST be here. A previous version of this list dropped
|
|
112
117
|
// it, on the theory that it mirrored the web SDK's persist contract —
|
|
@@ -185,8 +190,45 @@ const pickDefined = <T extends object>(
|
|
|
185
190
|
return out;
|
|
186
191
|
};
|
|
187
192
|
|
|
193
|
+
// What a restored attachment is rendered from. Ownership and timestamp
|
|
194
|
+
// fields the storage returns, and the sender's local upload state, are not
|
|
195
|
+
// ours to cache and would repeat across up to ten files of every message.
|
|
196
|
+
const PERSISTED_ATTACHMENT_FIELDS = [
|
|
197
|
+
'attachmentId',
|
|
198
|
+
'location',
|
|
199
|
+
'locationPreview',
|
|
200
|
+
'mimetype',
|
|
201
|
+
'originalName',
|
|
202
|
+
'fileName',
|
|
203
|
+
'size',
|
|
204
|
+
'duration',
|
|
205
|
+
'width',
|
|
206
|
+
'height',
|
|
207
|
+
] as const;
|
|
208
|
+
|
|
209
|
+
const compactAttachments = (attachments: unknown): IMessage['attachments'] => {
|
|
210
|
+
if (!Array.isArray(attachments) || attachments.length === 0) {return undefined;}
|
|
211
|
+
return attachments
|
|
212
|
+
.filter((item) => item && typeof item === 'object')
|
|
213
|
+
.map(
|
|
214
|
+
(item) =>
|
|
215
|
+
pickDefined(
|
|
216
|
+
item,
|
|
217
|
+
PERSISTED_ATTACHMENT_FIELDS as unknown as readonly string[]
|
|
218
|
+
) as NonNullable<IMessage['attachments']>[number]
|
|
219
|
+
);
|
|
220
|
+
};
|
|
221
|
+
|
|
188
222
|
const compactMessageForPersist = (message: IMessage): IMessage => {
|
|
189
223
|
const compact = pickDefined(message, PERSISTED_MESSAGE_FIELDS) as IMessage;
|
|
224
|
+
if (compact.attachments !== undefined) {
|
|
225
|
+
const attachments = compactAttachments(compact.attachments);
|
|
226
|
+
if (attachments) {
|
|
227
|
+
compact.attachments = attachments;
|
|
228
|
+
} else {
|
|
229
|
+
delete compact.attachments;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
190
232
|
const user = message?.user as Record<string, any> | undefined;
|
|
191
233
|
if (user) {
|
|
192
234
|
compact.user = pickDefined(
|
|
@@ -209,11 +251,17 @@ const sanitizeMessages = (messages: IMessage[]): IMessage[] => {
|
|
|
209
251
|
/**
|
|
210
252
|
* Room fields that describe ONE session and must never be written to disk:
|
|
211
253
|
* the server's unread snapshot (a moment-in-time number the next /chats/my
|
|
212
|
-
* re-seeds). Volatile slice state that is not on the room at all (jumpWindow,
|
|
254
|
+
* re-seeds) and the read watermarks other occupants sent this session. Volatile slice state that is not on the room at all (jumpWindow,
|
|
213
255
|
* pendingJump, archivedMessage, usersSet) is never persisted because only
|
|
214
256
|
* `rooms.rooms` is picked from the slice.
|
|
215
257
|
*/
|
|
216
|
-
const SESSION_ROOM_FIELDS = [
|
|
258
|
+
const SESSION_ROOM_FIELDS = [
|
|
259
|
+
'apiUnreadCount',
|
|
260
|
+
'apiUnreadSeededAt',
|
|
261
|
+
// XEP-0333 read watermarks: a peer's "seen" state is re-learned from the
|
|
262
|
+
// live marker stanzas, never restored from a previous session.
|
|
263
|
+
'lastDisplayedByUser',
|
|
264
|
+
] as const;
|
|
217
265
|
|
|
218
266
|
/**
|
|
219
267
|
* Rehydrate step: a persisted room must be refetched once per session, so
|
|
@@ -303,6 +351,21 @@ const sanitizeMediaData = (
|
|
|
303
351
|
return out;
|
|
304
352
|
};
|
|
305
353
|
|
|
354
|
+
// Every file of a multi-file failed send, trimmed the same way as `data`.
|
|
355
|
+
// Capped so a corrupt payload cannot grow the cache without bound.
|
|
356
|
+
const MAX_PERSISTED_FAILED_FILES = 10;
|
|
357
|
+
const sanitizeMediaFiles = (files: unknown): FailedMediaFile[] | undefined => {
|
|
358
|
+
if (!Array.isArray(files)) {return undefined;}
|
|
359
|
+
const out: FailedMediaFile[] = [];
|
|
360
|
+
for (const file of files.slice(0, MAX_PERSISTED_FAILED_FILES)) {
|
|
361
|
+
const clean = sanitizeMediaData(file) as FailedMediaFile;
|
|
362
|
+
if (typeof (file as any)?.duration === 'number') {clean.duration = (file as any).duration;}
|
|
363
|
+
if (typeof (file as any)?.waveForm === 'string') {clean.waveForm = (file as any).waveForm;}
|
|
364
|
+
out.push(clean);
|
|
365
|
+
}
|
|
366
|
+
return out.length > 0 ? out : undefined;
|
|
367
|
+
};
|
|
368
|
+
|
|
306
369
|
const sanitizeFailedMessagePayload = (
|
|
307
370
|
payload: FailedMessagePayload
|
|
308
371
|
): FailedMessagePayload | null => {
|
|
@@ -310,6 +373,8 @@ const sanitizeFailedMessagePayload = (
|
|
|
310
373
|
if (!payload.id || typeof payload.id !== 'string') {return null;}
|
|
311
374
|
if (!payload.roomJID || typeof payload.roomJID !== 'string') {return null;}
|
|
312
375
|
if (payload.kind === 'media') {
|
|
376
|
+
const files = sanitizeMediaFiles(payload.files);
|
|
377
|
+
const caption = typeof payload.caption === 'string' ? payload.caption : undefined;
|
|
313
378
|
return {
|
|
314
379
|
kind: 'media',
|
|
315
380
|
id: payload.id,
|
|
@@ -319,6 +384,11 @@ const sanitizeFailedMessagePayload = (
|
|
|
319
384
|
isReply: payload.isReply,
|
|
320
385
|
isChecked: payload.isChecked,
|
|
321
386
|
mainMessage: payload.mainMessage,
|
|
387
|
+
...(files ? { files } : {}),
|
|
388
|
+
...(caption ? { caption } : {}),
|
|
389
|
+
...(caption && Array.isArray(payload.captionMentions)
|
|
390
|
+
? { captionMentions: payload.captionMentions }
|
|
391
|
+
: {}),
|
|
322
392
|
};
|
|
323
393
|
}
|
|
324
394
|
if (payload.kind === 'text') {
|
|
@@ -377,6 +447,25 @@ export function reconstructFailedMessagePayload(
|
|
|
377
447
|
const isChecked = message.showInChannel === 'true';
|
|
378
448
|
const mainMessage = message.mainMessage;
|
|
379
449
|
if (message.isMediafile === 'true') {
|
|
450
|
+
// A multi-file send keeps every file. Only a file still on this device
|
|
451
|
+
// has a uri to replay; an uploaded one has no local copy left to send.
|
|
452
|
+
const localUri = (value: unknown): string | undefined =>
|
|
453
|
+
typeof value === 'string' && /^(file|content|ph|assets-library):\/\//.test(value)
|
|
454
|
+
? value
|
|
455
|
+
: undefined;
|
|
456
|
+
const attachments = Array.isArray(message.attachments) ? message.attachments : [];
|
|
457
|
+
const files: FailedMediaFile[] | undefined =
|
|
458
|
+
attachments.length > 1
|
|
459
|
+
? attachments.slice(0, MAX_PERSISTED_FAILED_FILES).map((attachment) => ({
|
|
460
|
+
uri: localUri(attachment.localUrl) ?? localUri(attachment.location),
|
|
461
|
+
name: attachment.originalName || attachment.fileName,
|
|
462
|
+
type: attachment.mimetype,
|
|
463
|
+
size:
|
|
464
|
+
attachment.size !== undefined && attachment.size !== null
|
|
465
|
+
? Number(attachment.size)
|
|
466
|
+
: undefined,
|
|
467
|
+
}))
|
|
468
|
+
: undefined;
|
|
380
469
|
return {
|
|
381
470
|
kind: 'media',
|
|
382
471
|
id: message.id,
|
|
@@ -394,6 +483,8 @@ export function reconstructFailedMessagePayload(
|
|
|
394
483
|
isReply,
|
|
395
484
|
isChecked,
|
|
396
485
|
mainMessage,
|
|
486
|
+
...(files ? { files } : {}),
|
|
487
|
+
...(message.caption ? { caption: message.caption } : {}),
|
|
397
488
|
};
|
|
398
489
|
}
|
|
399
490
|
return {
|
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import { createSlice, PayloadAction, type Slice } from '@reduxjs/toolkit';
|
|
2
2
|
import type { WritableDraft } from 'immer';
|
|
3
|
-
import { IMessage } from '../types/types';
|
|
3
|
+
import { IMentionSpan, IMessage } from '../types/types';
|
|
4
4
|
|
|
5
5
|
// Saved alongside a failed-message id so the retry button can resend
|
|
6
6
|
// without the bubble needing to remember the original draft itself.
|
|
7
7
|
// `kind === 'text'` carries the plain body; `kind === 'media'` carries
|
|
8
8
|
// the upload payload so we don't have to make the user re-pick the file.
|
|
9
|
+
/**
|
|
10
|
+
* One file of a failed media send, as the retry replays it. Only the fields
|
|
11
|
+
* an upload needs, so it persists as a plain JSON object.
|
|
12
|
+
*/
|
|
13
|
+
export interface FailedMediaFile {
|
|
14
|
+
uri?: string;
|
|
15
|
+
type?: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
size?: number;
|
|
18
|
+
duration?: number;
|
|
19
|
+
waveForm?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
9
22
|
export type FailedMessagePayload =
|
|
10
23
|
| {
|
|
11
24
|
kind: 'text';
|
|
@@ -20,11 +33,20 @@ export type FailedMessagePayload =
|
|
|
20
33
|
kind: 'media';
|
|
21
34
|
id: string;
|
|
22
35
|
roomJID: string;
|
|
36
|
+
/** The first file (or the only one); kept for single-file readers. */
|
|
23
37
|
data: any;
|
|
24
38
|
type: string;
|
|
25
39
|
isReply?: boolean;
|
|
26
40
|
isChecked?: boolean;
|
|
27
41
|
mainMessage?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Every file of the message, in selection order. Retry replays the
|
|
44
|
+
* whole list; a message with no list falls back to `data`.
|
|
45
|
+
*/
|
|
46
|
+
files?: FailedMediaFile[];
|
|
47
|
+
/** The caption the files were sent with, if any. */
|
|
48
|
+
caption?: string;
|
|
49
|
+
captionMentions?: IMentionSpan[];
|
|
28
50
|
};
|
|
29
51
|
|
|
30
52
|
export interface roomHeapSliceState {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createAsyncThunk, createSlice, PayloadAction, type Slice } from '@reduxjs/toolkit';
|
|
2
2
|
import type { WritableDraft } from 'immer';
|
|
3
|
-
import { EditAction, HistoryPreloadState, IMessage, IRoom } from '../types/types';
|
|
3
|
+
import { EditAction, HistoryPreloadState, IAttachment, IMessage, IRoom } from '../types/types';
|
|
4
4
|
import { insertMessageWithDelimiter } from '../helpers/insertMessageWithDelimiter';
|
|
5
5
|
import { msgSortableMs } from '../helpers/msgSortableMs';
|
|
6
6
|
import type XmppClient from '../networking/xmppClient';
|
|
@@ -955,6 +955,66 @@ const reducers = {
|
|
|
955
955
|
message.translations[locale] = entry;
|
|
956
956
|
});
|
|
957
957
|
},
|
|
958
|
+
/**
|
|
959
|
+
* Local upload state of one file of an outgoing media message: its
|
|
960
|
+
* `uploadStatus` ('uploading' | 'failed') and `uploadProgress` (0-100).
|
|
961
|
+
* `null` for `uploadStatus` clears both, once the file is up. Matches the
|
|
962
|
+
* message in the room and in the jump window, like the other patches.
|
|
963
|
+
* Never persisted: the persist whitelist drops these fields.
|
|
964
|
+
*/
|
|
965
|
+
setMediaUploadState(
|
|
966
|
+
state: WritableDraft<RoomMessagesState>,
|
|
967
|
+
action: PayloadAction<{
|
|
968
|
+
roomJID: string;
|
|
969
|
+
messageId: string;
|
|
970
|
+
attachmentId: string;
|
|
971
|
+
uploadStatus?: 'uploading' | 'failed' | null;
|
|
972
|
+
uploadProgress?: number;
|
|
973
|
+
}>
|
|
974
|
+
) {
|
|
975
|
+
const { roomJID, messageId, attachmentId, uploadStatus, uploadProgress } =
|
|
976
|
+
action.payload;
|
|
977
|
+
if (!isSafeKey(roomJID) || !isSafeKey(messageId) || !isSafeKey(attachmentId)) {
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
forEachMessageCopy(state, roomJID, messageId, (message) => {
|
|
981
|
+
const target = message.attachments?.find(
|
|
982
|
+
(attachment) => attachment.attachmentId === attachmentId
|
|
983
|
+
);
|
|
984
|
+
if (!target) {return;}
|
|
985
|
+
if (uploadStatus === null) {
|
|
986
|
+
delete target.uploadStatus;
|
|
987
|
+
delete target.uploadProgress;
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
if (uploadStatus !== undefined) {target.uploadStatus = uploadStatus;}
|
|
991
|
+
if (typeof uploadProgress === 'number' && Number.isFinite(uploadProgress)) {
|
|
992
|
+
target.uploadProgress = Math.min(100, Math.max(0, uploadProgress));
|
|
993
|
+
}
|
|
994
|
+
});
|
|
995
|
+
},
|
|
996
|
+
/**
|
|
997
|
+
* Replaces the attachments of a message the sender already has, without
|
|
998
|
+
* touching its other fields: `pending` stays as it is, so the bubble is
|
|
999
|
+
* still waiting for the server echo. (addRoomMessage would count this as
|
|
1000
|
+
* the echo and clear `pending`.)
|
|
1001
|
+
*/
|
|
1002
|
+
setMessageAttachments(
|
|
1003
|
+
state: WritableDraft<RoomMessagesState>,
|
|
1004
|
+
action: PayloadAction<{
|
|
1005
|
+
roomJID: string;
|
|
1006
|
+
messageId: string;
|
|
1007
|
+
attachments: IAttachment[];
|
|
1008
|
+
}>
|
|
1009
|
+
) {
|
|
1010
|
+
const { roomJID, messageId, attachments } = action.payload;
|
|
1011
|
+
if (!isSafeKey(roomJID) || !isSafeKey(messageId) || !Array.isArray(attachments)) {
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
forEachMessageCopy(state, roomJID, messageId, (message) => {
|
|
1015
|
+
message.attachments = attachments.map((attachment) => ({ ...attachment }));
|
|
1016
|
+
});
|
|
1017
|
+
},
|
|
958
1018
|
addRoomMessage(
|
|
959
1019
|
state: WritableDraft<RoomMessagesState>,
|
|
960
1020
|
action: PayloadAction<{
|
|
@@ -1412,6 +1472,53 @@ const reducers = {
|
|
|
1412
1472
|
list.push({ from, reactions, data, ts: latestReactionTimestamp });
|
|
1413
1473
|
}
|
|
1414
1474
|
},
|
|
1475
|
+
/**
|
|
1476
|
+
* XEP-0184: an occupant's client acknowledged one of our messages. Only
|
|
1477
|
+
* the message id is matched (archive ids), on the live list and on a jump
|
|
1478
|
+
* window copy. Session-only: not persisted.
|
|
1479
|
+
*/
|
|
1480
|
+
setReceivedReceipt: (
|
|
1481
|
+
state: WritableDraft<RoomMessagesState>,
|
|
1482
|
+
action: PayloadAction<{
|
|
1483
|
+
roomJID: string;
|
|
1484
|
+
messageId: string;
|
|
1485
|
+
fromXmppUsername: string;
|
|
1486
|
+
timestamp: number;
|
|
1487
|
+
}>
|
|
1488
|
+
) => {
|
|
1489
|
+
const { roomJID, messageId, fromXmppUsername, timestamp } = action.payload;
|
|
1490
|
+
// Both keys become property names: an unsafe key must never reach the write.
|
|
1491
|
+
if (!isSafeKey(roomJID) || !isSafeKey(fromXmppUsername)) {return;}
|
|
1492
|
+
if (!state.rooms[roomJID] || !messageId) {return;}
|
|
1493
|
+
forEachMessageCopy(
|
|
1494
|
+
state,
|
|
1495
|
+
roomJID,
|
|
1496
|
+
(message) => message.id === messageId,
|
|
1497
|
+
(message) => {
|
|
1498
|
+
if (!message.receivedBy) {message.receivedBy = {};}
|
|
1499
|
+
message.receivedBy[fromXmppUsername] = timestamp;
|
|
1500
|
+
}
|
|
1501
|
+
);
|
|
1502
|
+
},
|
|
1503
|
+
/**
|
|
1504
|
+
* XEP-0333: an occupant displayed up to `messageId`. One watermark per
|
|
1505
|
+
* user and room, replaced on every marker. Session-only: not persisted.
|
|
1506
|
+
*/
|
|
1507
|
+
setDisplayedMarker: (
|
|
1508
|
+
state: WritableDraft<RoomMessagesState>,
|
|
1509
|
+
action: PayloadAction<{
|
|
1510
|
+
roomJID: string;
|
|
1511
|
+
xmppUsername: string;
|
|
1512
|
+
messageId: string;
|
|
1513
|
+
}>
|
|
1514
|
+
) => {
|
|
1515
|
+
const { roomJID, xmppUsername, messageId } = action.payload;
|
|
1516
|
+
if (!isSafeKey(roomJID) || !isSafeKey(xmppUsername)) {return;}
|
|
1517
|
+
const room = state.rooms[roomJID];
|
|
1518
|
+
if (!room || !messageId) {return;}
|
|
1519
|
+
if (!room.lastDisplayedByUser) {room.lastDisplayedByUser = {};}
|
|
1520
|
+
room.lastDisplayedByUser[xmppUsername] = messageId;
|
|
1521
|
+
},
|
|
1415
1522
|
setLogoutState: (state: WritableDraft<RoomMessagesState>) => {
|
|
1416
1523
|
state.rooms = {};
|
|
1417
1524
|
state.pendingReactions = {};
|
|
@@ -1640,6 +1747,8 @@ export const {
|
|
|
1640
1747
|
deleteAllRooms,
|
|
1641
1748
|
setRoomMessages,
|
|
1642
1749
|
addRoomMessage,
|
|
1750
|
+
setMediaUploadState,
|
|
1751
|
+
setMessageAttachments,
|
|
1643
1752
|
deleteRoomMessage,
|
|
1644
1753
|
setEditAction,
|
|
1645
1754
|
editRoomMessage,
|
|
@@ -1671,6 +1780,8 @@ export const {
|
|
|
1671
1780
|
setJoiningRoom,
|
|
1672
1781
|
clearJoiningRoom,
|
|
1673
1782
|
setReactions,
|
|
1783
|
+
setReceivedReceipt,
|
|
1784
|
+
setDisplayedMarker,
|
|
1674
1785
|
mergeUsersSet,
|
|
1675
1786
|
removeRoomMessage,
|
|
1676
1787
|
addRoomFromApi,
|
|
@@ -11,6 +11,13 @@ import { pushLog } from '../utils/devLogger';
|
|
|
11
11
|
|
|
12
12
|
export interface RegisterPushTokenOptions {
|
|
13
13
|
tokenType?: PushTokenType;
|
|
14
|
+
/**
|
|
15
|
+
* Encrypted pushes: base64 of the device's X25519 public key
|
|
16
|
+
* (`ensurePushKeyPair().publicKey`). Sent with the token as `pushKey`;
|
|
17
|
+
* the service seals every chat push for it. A backend without the
|
|
18
|
+
* feature refuses the registration instead of delivering in the clear.
|
|
19
|
+
*/
|
|
20
|
+
pushKey?: string;
|
|
14
21
|
}
|
|
15
22
|
|
|
16
23
|
export type RegisterPushTokenOutcome =
|
|
@@ -29,6 +36,7 @@ export type UnregisterPushTokenOutcome =
|
|
|
29
36
|
export interface RegisteredPushToken {
|
|
30
37
|
token: string;
|
|
31
38
|
tokenType: PushTokenType;
|
|
39
|
+
pushKey?: string;
|
|
32
40
|
}
|
|
33
41
|
|
|
34
42
|
type Slot = 'chat' | 'voip';
|
|
@@ -93,6 +101,7 @@ const sameRegistration = (
|
|
|
93
101
|
!!current &&
|
|
94
102
|
current.token === token.token &&
|
|
95
103
|
current.tokenType === token.tokenType &&
|
|
104
|
+
(current.pushKey || '') === (token.pushKey || '') &&
|
|
96
105
|
current.gateway === isGateway(session) &&
|
|
97
106
|
current.appId === session.appId &&
|
|
98
107
|
current.xmppUsername === session.xmppUsername;
|
|
@@ -147,6 +156,7 @@ const registerOne = async (
|
|
|
147
156
|
token: token.token,
|
|
148
157
|
tokenType: token.tokenType,
|
|
149
158
|
authToken: session.authToken,
|
|
159
|
+
pushKey: token.pushKey,
|
|
150
160
|
});
|
|
151
161
|
}
|
|
152
162
|
registered.set(slotOf(token.tokenType), {
|
|
@@ -158,7 +168,8 @@ const registerOne = async (
|
|
|
158
168
|
pushLog(
|
|
159
169
|
'rn',
|
|
160
170
|
`push: ${token.tokenType} token registered` +
|
|
161
|
-
(isGateway(session) ? ' (gateway)' : ` for app ${session.appId}`)
|
|
171
|
+
(isGateway(session) ? ' (gateway)' : ` for app ${session.appId}`) +
|
|
172
|
+
(token.pushKey && !isGateway(session) ? ' with a device key (encrypted pushes)' : '')
|
|
162
173
|
);
|
|
163
174
|
};
|
|
164
175
|
|
|
@@ -209,7 +220,7 @@ const runTokenHook = (session: Session): void => {
|
|
|
209
220
|
if (typeof entry?.token !== 'string' || !entry.token.trim()) {
|
|
210
221
|
continue;
|
|
211
222
|
}
|
|
212
|
-
hold(entry.token.trim(), entry.tokenType);
|
|
223
|
+
hold(entry.token.trim(), entry.tokenType, entry.pushKey);
|
|
213
224
|
}
|
|
214
225
|
await registerNow();
|
|
215
226
|
})().catch((error) => {
|
|
@@ -219,9 +230,18 @@ const runTokenHook = (session: Session): void => {
|
|
|
219
230
|
});
|
|
220
231
|
};
|
|
221
232
|
|
|
222
|
-
const hold = (
|
|
233
|
+
const hold = (
|
|
234
|
+
token: string,
|
|
235
|
+
tokenType?: PushTokenType,
|
|
236
|
+
pushKey?: string
|
|
237
|
+
): RegisteredPushToken => {
|
|
223
238
|
const resolved = tokenType ?? detectPushTokenType(token);
|
|
224
|
-
const
|
|
239
|
+
const key = typeof pushKey === 'string' ? pushKey.trim() : '';
|
|
240
|
+
const entry: RegisteredPushToken = {
|
|
241
|
+
token,
|
|
242
|
+
tokenType: resolved,
|
|
243
|
+
...(key ? { pushKey: key } : {}),
|
|
244
|
+
};
|
|
225
245
|
held.set(slotOf(resolved), entry);
|
|
226
246
|
return entry;
|
|
227
247
|
};
|
|
@@ -274,7 +294,7 @@ export async function registerPushToken(
|
|
|
274
294
|
if (typeof token !== 'string' || !token.trim()) {
|
|
275
295
|
throw new TypeError('registerPushToken: token must be a non-empty string');
|
|
276
296
|
}
|
|
277
|
-
hold(token.trim(), options?.tokenType);
|
|
297
|
+
hold(token.trim(), options?.tokenType, options?.pushKey);
|
|
278
298
|
ensureWatching();
|
|
279
299
|
|
|
280
300
|
const session = readSession();
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One uploaded file carried by a media message.
|
|
3
|
+
*
|
|
4
|
+
* Historically a media message described exactly one file through flat
|
|
5
|
+
* `IMessage` fields (`location`, `mimetype`, `originalName`, ...). Those
|
|
6
|
+
* fields are still written (they are what every already-shipped client
|
|
7
|
+
* reads), and they now double as attachment #0 of `IMessage.attachments`.
|
|
8
|
+
* Anything past #0 only exists in the `attachments` payload, so old clients
|
|
9
|
+
* degrade to "shows the first file" instead of breaking.
|
|
10
|
+
*/
|
|
11
|
+
export interface IAttachment {
|
|
12
|
+
/** `_id` returned by POST /files/ - stable per upload, used as a render key. */
|
|
13
|
+
attachmentId?: string;
|
|
14
|
+
/** Canonical file URL. */
|
|
15
|
+
location: string;
|
|
16
|
+
/** Server-side generated preview (images always, other types sometimes). */
|
|
17
|
+
locationPreview?: string;
|
|
18
|
+
mimetype?: string;
|
|
19
|
+
/** Name the user picked. */
|
|
20
|
+
originalName?: string;
|
|
21
|
+
/** Name on the storage side. */
|
|
22
|
+
fileName?: string;
|
|
23
|
+
/** Bytes, as a string - that is how it travels on the wire. */
|
|
24
|
+
size?: string;
|
|
25
|
+
duration?: string;
|
|
26
|
+
/** Natural pixel size, when the storage recorded it. */
|
|
27
|
+
width?: number;
|
|
28
|
+
height?: number;
|
|
29
|
+
ownerKey?: string;
|
|
30
|
+
userId?: string;
|
|
31
|
+
createdAt?: string;
|
|
32
|
+
updatedAt?: string;
|
|
33
|
+
expiresAt?: string;
|
|
34
|
+
isVisible?: boolean;
|
|
35
|
+
isPrivate?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Local-only state of an outgoing attachment, set by the sender while its
|
|
38
|
+
* message is still being sent. Never written to the wire.
|
|
39
|
+
* uploading - the file is going up (or every file is up and the message
|
|
40
|
+
* is waiting on its siblings); `uploadProgress` is 0 to 100.
|
|
41
|
+
* failed - this file did not upload; the message is held until it
|
|
42
|
+
* is retried.
|
|
43
|
+
*/
|
|
44
|
+
uploadStatus?: 'uploading' | 'failed';
|
|
45
|
+
uploadProgress?: number;
|
|
46
|
+
/**
|
|
47
|
+
* The file as it is on this device (`file://`), shown on the sender's own
|
|
48
|
+
* bubble while it uploads. Local only, like the upload state above.
|
|
49
|
+
*/
|
|
50
|
+
localUrl?: string;
|
|
51
|
+
}
|