@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,207 @@
|
|
|
1
|
+
import type { ViewStyle } from 'react-native';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Geometry for a multi-media album bubble. Pure numbers and RN style objects,
|
|
5
|
+
* so AlbumGrid stays a dumb renderer and the rules are testable without a
|
|
6
|
+
* renderer.
|
|
7
|
+
*
|
|
8
|
+
* 1 item full width, 4:3
|
|
9
|
+
* 2 items side by side, 2:1 (square cells)
|
|
10
|
+
* 3 items one large cell on the left, two stacked on the right
|
|
11
|
+
* 4 items 2 x 2
|
|
12
|
+
* 5+ items 2 x 2 showing the first four, the last cell carries "+N"
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** Gap between neighbouring tiles. */
|
|
16
|
+
export const ALBUM_GAP = 3;
|
|
17
|
+
/** Radius of a tile corner that sits on the bubble's own corner. */
|
|
18
|
+
export const ALBUM_OUTER_RADIUS = 10;
|
|
19
|
+
/** Radius of every other tile corner, the ones that face a neighbour. */
|
|
20
|
+
export const ALBUM_INNER_RADIUS = 3;
|
|
21
|
+
|
|
22
|
+
export interface AlbumCellPlacement {
|
|
23
|
+
/** 0-based grid column and row of the tile's top-left cell. */
|
|
24
|
+
column: number;
|
|
25
|
+
row: number;
|
|
26
|
+
columnSpan: number;
|
|
27
|
+
rowSpan: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type AlbumKind = 'full' | 'pair' | 'hero' | 'quad';
|
|
31
|
+
|
|
32
|
+
export interface AlbumLayout {
|
|
33
|
+
kind: AlbumKind;
|
|
34
|
+
/** Number of tiles actually drawn. */
|
|
35
|
+
visible: number;
|
|
36
|
+
/** Items folded into the "+N" overlay on the last visible tile. */
|
|
37
|
+
hidden: number;
|
|
38
|
+
/** Relative width of each grid column. */
|
|
39
|
+
columns: number[];
|
|
40
|
+
/** Relative height of each grid row. */
|
|
41
|
+
rows: number[];
|
|
42
|
+
/** Width divided by height of the whole grid. */
|
|
43
|
+
aspectRatio: number;
|
|
44
|
+
/** One entry per visible tile, in order. */
|
|
45
|
+
placements: AlbumCellPlacement[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const cell = (
|
|
49
|
+
column: number,
|
|
50
|
+
row: number,
|
|
51
|
+
rowSpan = 1
|
|
52
|
+
): AlbumCellPlacement => ({ column, row, columnSpan: 1, rowSpan });
|
|
53
|
+
|
|
54
|
+
const LAYOUTS: Record<AlbumKind, Omit<AlbumLayout, 'visible' | 'hidden'>> = {
|
|
55
|
+
full: {
|
|
56
|
+
kind: 'full',
|
|
57
|
+
columns: [1],
|
|
58
|
+
rows: [1],
|
|
59
|
+
aspectRatio: 4 / 3,
|
|
60
|
+
placements: [cell(0, 0)],
|
|
61
|
+
},
|
|
62
|
+
pair: {
|
|
63
|
+
kind: 'pair',
|
|
64
|
+
columns: [1, 1],
|
|
65
|
+
rows: [1],
|
|
66
|
+
aspectRatio: 2,
|
|
67
|
+
placements: [cell(0, 0), cell(1, 0)],
|
|
68
|
+
},
|
|
69
|
+
hero: {
|
|
70
|
+
kind: 'hero',
|
|
71
|
+
columns: [2, 1],
|
|
72
|
+
rows: [1, 1],
|
|
73
|
+
aspectRatio: 4 / 3,
|
|
74
|
+
placements: [cell(0, 0, 2), cell(1, 0), cell(1, 1)],
|
|
75
|
+
},
|
|
76
|
+
quad: {
|
|
77
|
+
kind: 'quad',
|
|
78
|
+
columns: [1, 1],
|
|
79
|
+
rows: [1, 1],
|
|
80
|
+
aspectRatio: 1,
|
|
81
|
+
placements: [cell(0, 0), cell(1, 0), cell(0, 1), cell(1, 1)],
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/** Layout for `count` media tiles. Counts below 1 are treated as 1. */
|
|
86
|
+
export const getAlbumLayout = (count: number): AlbumLayout => {
|
|
87
|
+
const total = Math.max(1, Math.floor(Number.isFinite(count) ? count : 1));
|
|
88
|
+
const kind: AlbumKind =
|
|
89
|
+
total === 1 ? 'full' : total === 2 ? 'pair' : total === 3 ? 'hero' : 'quad';
|
|
90
|
+
const base = LAYOUTS[kind];
|
|
91
|
+
const visible = base.placements.length;
|
|
92
|
+
return {
|
|
93
|
+
...base,
|
|
94
|
+
columns: [...base.columns],
|
|
95
|
+
rows: [...base.rows],
|
|
96
|
+
placements: base.placements.map((placement) => ({ ...placement })),
|
|
97
|
+
visible,
|
|
98
|
+
hidden: Math.max(0, total - visible),
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** Splits `total` across weighted tracks, leaving `gap` between neighbours. */
|
|
103
|
+
const trackSizes = (weights: number[], total: number, gap: number): number[] => {
|
|
104
|
+
const room = total - gap * (weights.length - 1);
|
|
105
|
+
const sum = weights.reduce((acc, weight) => acc + weight, 0);
|
|
106
|
+
return weights.map((weight) => (room * weight) / sum);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export interface AlbumFrame {
|
|
110
|
+
width: number;
|
|
111
|
+
height: number;
|
|
112
|
+
/** One absolutely positioned style per visible tile, in order. */
|
|
113
|
+
tiles: ViewStyle[];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Pixel frame of the grid and of every tile for a grid `width` wide. */
|
|
117
|
+
export const getAlbumFrame = (layout: AlbumLayout, width: number): AlbumFrame => {
|
|
118
|
+
const height = Math.round(width / layout.aspectRatio);
|
|
119
|
+
const colWidths = trackSizes(layout.columns, width, ALBUM_GAP);
|
|
120
|
+
const rowHeights = trackSizes(layout.rows, height, ALBUM_GAP);
|
|
121
|
+
const offset = (sizes: number[], index: number) =>
|
|
122
|
+
sizes.slice(0, index).reduce((acc, size) => acc + size + ALBUM_GAP, 0);
|
|
123
|
+
const span = (sizes: number[], index: number, count: number) =>
|
|
124
|
+
sizes.slice(index, index + count).reduce((acc, size) => acc + size, 0) +
|
|
125
|
+
ALBUM_GAP * (count - 1);
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
width,
|
|
129
|
+
height,
|
|
130
|
+
tiles: layout.placements.map((placement, position) => ({
|
|
131
|
+
position: 'absolute',
|
|
132
|
+
left: Math.round(offset(colWidths, placement.column)),
|
|
133
|
+
top: Math.round(offset(rowHeights, placement.row)),
|
|
134
|
+
width: Math.round(span(colWidths, placement.column, placement.columnSpan)),
|
|
135
|
+
height: Math.round(span(rowHeights, placement.row, placement.rowSpan)),
|
|
136
|
+
overflow: 'hidden',
|
|
137
|
+
...getTileBorderRadius(layout, position),
|
|
138
|
+
})),
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Which corners of one tile lie on the bubble's outer edge. Only those get
|
|
144
|
+
* the large radius; corners that face a neighbour get the small inner one.
|
|
145
|
+
*/
|
|
146
|
+
export interface TileCorners {
|
|
147
|
+
topLeft: boolean;
|
|
148
|
+
topRight: boolean;
|
|
149
|
+
bottomRight: boolean;
|
|
150
|
+
bottomLeft: boolean;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const corners = (
|
|
154
|
+
topLeft: boolean,
|
|
155
|
+
topRight: boolean,
|
|
156
|
+
bottomRight: boolean,
|
|
157
|
+
bottomLeft: boolean
|
|
158
|
+
): TileCorners => ({ topLeft, topRight, bottomRight, bottomLeft });
|
|
159
|
+
|
|
160
|
+
const OUTER_CORNERS: Record<AlbumKind, TileCorners[]> = {
|
|
161
|
+
full: [corners(true, true, true, true)],
|
|
162
|
+
pair: [corners(true, false, false, true), corners(false, true, true, false)],
|
|
163
|
+
// Hero: the tall left cell owns both left corners; the right column owns
|
|
164
|
+
// its top-right and bottom-right corners respectively.
|
|
165
|
+
hero: [
|
|
166
|
+
corners(true, false, false, true),
|
|
167
|
+
corners(false, true, false, false),
|
|
168
|
+
corners(false, false, true, false),
|
|
169
|
+
],
|
|
170
|
+
quad: [
|
|
171
|
+
corners(true, false, false, false),
|
|
172
|
+
corners(false, true, false, false),
|
|
173
|
+
corners(false, false, false, true),
|
|
174
|
+
corners(false, false, true, false),
|
|
175
|
+
],
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/** Outer-corner flags for the tile at `position` (0-based) of `layout`. */
|
|
179
|
+
export const getTileCorners = (
|
|
180
|
+
layout: AlbumLayout,
|
|
181
|
+
position: number
|
|
182
|
+
): TileCorners => {
|
|
183
|
+
const found = OUTER_CORNERS[layout.kind][position];
|
|
184
|
+
return found ? { ...found } : corners(false, false, false, false);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/** The four border radii of one tile, as RN style props. */
|
|
188
|
+
export const getTileBorderRadius = (
|
|
189
|
+
layout: AlbumLayout,
|
|
190
|
+
position: number
|
|
191
|
+
): Pick<
|
|
192
|
+
ViewStyle,
|
|
193
|
+
| 'borderTopLeftRadius'
|
|
194
|
+
| 'borderTopRightRadius'
|
|
195
|
+
| 'borderBottomRightRadius'
|
|
196
|
+
| 'borderBottomLeftRadius'
|
|
197
|
+
> => {
|
|
198
|
+
const c = getTileCorners(layout, position);
|
|
199
|
+
const radius = (outer: boolean) =>
|
|
200
|
+
outer ? ALBUM_OUTER_RADIUS : ALBUM_INNER_RADIUS;
|
|
201
|
+
return {
|
|
202
|
+
borderTopLeftRadius: radius(c.topLeft),
|
|
203
|
+
borderTopRightRadius: radius(c.topRight),
|
|
204
|
+
borderBottomRightRadius: radius(c.bottomRight),
|
|
205
|
+
borderBottomLeftRadius: radius(c.bottomLeft),
|
|
206
|
+
};
|
|
207
|
+
};
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { IAttachment, IMessage } from '../types/types';
|
|
2
|
+
import { safeJsonParse } from './safeJson';
|
|
3
|
+
import { repairFilenameEncoding } from './repairFilenameEncoding';
|
|
4
|
+
import { inferMimeFromName, isGenericMime } from './mimeToExtension';
|
|
5
|
+
|
|
6
|
+
/** Parses a dimension that may arrive as a number or a numeric string. */
|
|
7
|
+
const parseDimension = (value: unknown): number | undefined => {
|
|
8
|
+
const n = typeof value === 'string' ? Number.parseFloat(value) : value;
|
|
9
|
+
return typeof n === 'number' && Number.isFinite(n) && n > 0 ? n : undefined;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Hard ceiling on how many attachments we will serialise into a stanza.
|
|
14
|
+
* The composer caps the picker far below this; this is the parser-side
|
|
15
|
+
* guard so a malformed or hostile stanza cannot make us render an
|
|
16
|
+
* unbounded grid.
|
|
17
|
+
*/
|
|
18
|
+
export const MAX_ATTACHMENTS_PER_MESSAGE = 10;
|
|
19
|
+
|
|
20
|
+
const asOptionalString = (value: unknown): string | undefined => {
|
|
21
|
+
if (value === null || value === undefined) return undefined;
|
|
22
|
+
if (typeof value === 'string') return value || undefined;
|
|
23
|
+
if (typeof value === 'number') return String(value);
|
|
24
|
+
return undefined;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** A file name from the wire/API, with UTF-8-read-as-Latin-1 mojibake undone. */
|
|
28
|
+
const asOptionalFileName = (value: unknown): string | undefined => {
|
|
29
|
+
const name = asOptionalString(value);
|
|
30
|
+
return name === undefined ? undefined : repairFilenameEncoding(name);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const asOptionalBoolean = (value: unknown): boolean | undefined => {
|
|
34
|
+
if (typeof value === 'boolean') return value;
|
|
35
|
+
if (value === 'true') return true;
|
|
36
|
+
if (value === 'false') return false;
|
|
37
|
+
return undefined;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Turns one raw wire/API object into an `IAttachment`, or null when there is
|
|
42
|
+
* nothing renderable in it. `location` is allowed to be empty: that is what
|
|
43
|
+
* an optimistic (still-uploading) attachment looks like.
|
|
44
|
+
*/
|
|
45
|
+
export const normalizeAttachment = (raw: unknown): IAttachment | null => {
|
|
46
|
+
if (!raw || typeof raw !== 'object') return null;
|
|
47
|
+
|
|
48
|
+
const source = raw as Record<string, unknown>;
|
|
49
|
+
const location = asOptionalString(source.location) ?? '';
|
|
50
|
+
const originalName =
|
|
51
|
+
asOptionalFileName(source.originalName) ?? asOptionalFileName(source.originalname);
|
|
52
|
+
const rawMimetype = asOptionalString(source.mimetype);
|
|
53
|
+
const fileName = asOptionalFileName(source.fileName) ?? asOptionalFileName(source.filename);
|
|
54
|
+
const duration =
|
|
55
|
+
asOptionalString(source.duration) ?? asOptionalString(source.mediaDuration);
|
|
56
|
+
const mimetype = resolveLegacyMimetype(rawMimetype, {
|
|
57
|
+
names: [originalName, fileName, urlPath(location)],
|
|
58
|
+
duration,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (!location && !originalName && !mimetype && !fileName) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
attachmentId: asOptionalString(source.attachmentId) ?? asOptionalString(source._id),
|
|
67
|
+
location,
|
|
68
|
+
locationPreview: asOptionalString(source.locationPreview),
|
|
69
|
+
mimetype,
|
|
70
|
+
originalName,
|
|
71
|
+
fileName,
|
|
72
|
+
size: asOptionalString(source.size),
|
|
73
|
+
duration,
|
|
74
|
+
...optionalDimensions(source),
|
|
75
|
+
ownerKey: asOptionalString(source.ownerKey),
|
|
76
|
+
userId: asOptionalString(source.userId),
|
|
77
|
+
createdAt: asOptionalString(source.createdAt),
|
|
78
|
+
updatedAt: asOptionalString(source.updatedAt),
|
|
79
|
+
expiresAt: asOptionalString(source.expiresAt),
|
|
80
|
+
isVisible: asOptionalBoolean(source.isVisible),
|
|
81
|
+
isPrivate: asOptionalBoolean(source.isPrivate),
|
|
82
|
+
...normalizeUploadState(source),
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/** The path part of a URL (no query or fragment), for reading an extension off it. */
|
|
87
|
+
const urlPath = (url: string): string | undefined =>
|
|
88
|
+
url ? url.split(/[?#]/)[0] : undefined;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Older clients and the storage send media with no `mimetype`, or with the
|
|
92
|
+
* "could not sniff" `application/octet-stream`. The extension of any name the
|
|
93
|
+
* file carries tells which picture, video, audio or PDF it is. A nameless or
|
|
94
|
+
* extensionless file with a duration is a recorded voice note (browsers upload
|
|
95
|
+
* those as a `blob`), which is exactly what octet-stream renders as.
|
|
96
|
+
*/
|
|
97
|
+
const resolveLegacyMimetype = (
|
|
98
|
+
mimetype: string | undefined,
|
|
99
|
+
hints: { names: Array<string | undefined>; duration?: string }
|
|
100
|
+
): string | undefined => {
|
|
101
|
+
if (!isGenericMime(mimetype)) return mimetype;
|
|
102
|
+
const hasDuration = Number.parseFloat(hints.duration ?? '') > 0;
|
|
103
|
+
// A recorded note keeps its generic type: browsers upload it as a nameless
|
|
104
|
+
// `blob` (and storage may add a `.webm` to the stored name, which is audio
|
|
105
|
+
// here, not video). Only a name that says what it is overrides that.
|
|
106
|
+
if (hasDuration && !inferMimeFromName(hints.names[0])) return mimetype ?? 'application/octet-stream';
|
|
107
|
+
for (const name of hints.names) {
|
|
108
|
+
const inferred = inferMimeFromName(name);
|
|
109
|
+
if (inferred) return inferred;
|
|
110
|
+
}
|
|
111
|
+
return mimetype;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/** Pixel size, only when the source carries a usable one. */
|
|
115
|
+
const optionalDimensions = (
|
|
116
|
+
source: Record<string, unknown>
|
|
117
|
+
): Pick<IAttachment, 'width' | 'height'> => {
|
|
118
|
+
const width = parseDimension(source.width);
|
|
119
|
+
const height = parseDimension(source.height);
|
|
120
|
+
return {
|
|
121
|
+
...(width !== undefined ? { width } : {}),
|
|
122
|
+
...(height !== undefined ? { height } : {}),
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Local upload state rides along on optimistic attachments only; it is
|
|
128
|
+
* never serialised, so a received attachment never carries it.
|
|
129
|
+
*/
|
|
130
|
+
const normalizeUploadState = (
|
|
131
|
+
source: Record<string, unknown>
|
|
132
|
+
): Pick<IAttachment, 'uploadStatus' | 'uploadProgress' | 'localUrl'> => {
|
|
133
|
+
const status = source.uploadStatus === 'uploading' || source.uploadStatus === 'failed'
|
|
134
|
+
? source.uploadStatus
|
|
135
|
+
: undefined;
|
|
136
|
+
if (!status) return {};
|
|
137
|
+
const progress =
|
|
138
|
+
typeof source.uploadProgress === 'number' && Number.isFinite(source.uploadProgress)
|
|
139
|
+
? Math.min(100, Math.max(0, source.uploadProgress))
|
|
140
|
+
: 0;
|
|
141
|
+
// Only a file on this device can be the local preview of an outgoing file.
|
|
142
|
+
const localUrl =
|
|
143
|
+
typeof source.localUrl === 'string' && /^(file|content):\/\//.test(source.localUrl)
|
|
144
|
+
? source.localUrl
|
|
145
|
+
: undefined;
|
|
146
|
+
return {
|
|
147
|
+
uploadStatus: status,
|
|
148
|
+
uploadProgress: progress,
|
|
149
|
+
...(localUrl ? { localUrl } : {}),
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/** Accepts the parsed array, the raw JSON string, or junk. Never throws. */
|
|
154
|
+
export const parseAttachments = (raw: unknown): IAttachment[] => {
|
|
155
|
+
const candidate = typeof raw === 'string' ? safeJsonParse<unknown>(raw, []) : raw;
|
|
156
|
+
if (!Array.isArray(candidate)) return [];
|
|
157
|
+
|
|
158
|
+
return candidate
|
|
159
|
+
.slice(0, MAX_ATTACHMENTS_PER_MESSAGE)
|
|
160
|
+
.map(normalizeAttachment)
|
|
161
|
+
.filter((item): item is IAttachment => item !== null);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The wire form: a JSON string on the `<data attachments="...">` attribute.
|
|
166
|
+
* Only the fields a receiver renders are sent - the stanza is already wide
|
|
167
|
+
* and every byte here is duplicated per attachment.
|
|
168
|
+
*/
|
|
169
|
+
export const serializeAttachments = (attachments: IAttachment[]): string =>
|
|
170
|
+
JSON.stringify(
|
|
171
|
+
attachments.slice(0, MAX_ATTACHMENTS_PER_MESSAGE).map((attachment) => ({
|
|
172
|
+
attachmentId: attachment.attachmentId,
|
|
173
|
+
location: attachment.location,
|
|
174
|
+
locationPreview: attachment.locationPreview,
|
|
175
|
+
mimetype: attachment.mimetype,
|
|
176
|
+
originalName: attachment.originalName,
|
|
177
|
+
fileName: attachment.fileName,
|
|
178
|
+
size: attachment.size,
|
|
179
|
+
duration: attachment.duration,
|
|
180
|
+
}))
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The only supported way to read a media message's files.
|
|
185
|
+
*
|
|
186
|
+
* Order of truth: the multi-attach payload when present, otherwise the flat
|
|
187
|
+
* legacy fields synthesised into a single attachment. Every message that
|
|
188
|
+
* predates this feature - and every message from a client that does not
|
|
189
|
+
* speak it - takes the second path.
|
|
190
|
+
*/
|
|
191
|
+
export const getMessageAttachments = (
|
|
192
|
+
message?: Pick<
|
|
193
|
+
IMessage,
|
|
194
|
+
| 'attachments'
|
|
195
|
+
| 'location'
|
|
196
|
+
| 'locationPreview'
|
|
197
|
+
| 'mimetype'
|
|
198
|
+
| 'originalName'
|
|
199
|
+
| 'fileName'
|
|
200
|
+
| 'size'
|
|
201
|
+
> & { duration?: unknown; mediaDuration?: unknown } | null
|
|
202
|
+
): IAttachment[] => {
|
|
203
|
+
if (!message) return [];
|
|
204
|
+
|
|
205
|
+
const parsed = parseAttachments(message.attachments);
|
|
206
|
+
if (parsed.length > 0) return parsed;
|
|
207
|
+
|
|
208
|
+
// Legacy clients may pack several files into the flat fields as a
|
|
209
|
+
// comma separated list of URLs; the other flat fields then list one value
|
|
210
|
+
// each, in the same order, or one value for all.
|
|
211
|
+
const locations = splitUrlList(message.location);
|
|
212
|
+
if (locations.length > 1) {
|
|
213
|
+
const previews = splitUrlList(message.locationPreview);
|
|
214
|
+
const mimetypes = splitList(message.mimetype);
|
|
215
|
+
const names = splitList(message.originalName);
|
|
216
|
+
return locations
|
|
217
|
+
.slice(0, MAX_ATTACHMENTS_PER_MESSAGE)
|
|
218
|
+
.map((location, index) =>
|
|
219
|
+
normalizeAttachment({
|
|
220
|
+
location,
|
|
221
|
+
locationPreview: previews.length === locations.length ? previews[index] : undefined,
|
|
222
|
+
mimetype: pickNth(mimetypes, index, locations.length),
|
|
223
|
+
originalName: pickNth(names, index, locations.length),
|
|
224
|
+
size: undefined,
|
|
225
|
+
})
|
|
226
|
+
)
|
|
227
|
+
.filter((item): item is IAttachment => item !== null);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const legacy = normalizeAttachment({
|
|
231
|
+
location: message.location,
|
|
232
|
+
locationPreview: message.locationPreview,
|
|
233
|
+
mimetype: message.mimetype,
|
|
234
|
+
originalName: message.originalName,
|
|
235
|
+
fileName: message.fileName,
|
|
236
|
+
size: message.size,
|
|
237
|
+
duration: message.duration,
|
|
238
|
+
mediaDuration: message.mediaDuration,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
return legacy ? [legacy] : [];
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
/** "https://a/x.jpg, https://a/y.jpg" -> two URLs. A single URL stays whole, commas and all. */
|
|
245
|
+
const splitUrlList = (value: unknown): string[] =>
|
|
246
|
+
typeof value === 'string' && /,\s*https?:\/\//i.test(value)
|
|
247
|
+
? value.split(/,\s*(?=https?:\/\/)/i).map((item) => item.trim()).filter(Boolean)
|
|
248
|
+
: typeof value === 'string' && value
|
|
249
|
+
? [value]
|
|
250
|
+
: [];
|
|
251
|
+
|
|
252
|
+
const splitList = (value: unknown): string[] =>
|
|
253
|
+
typeof value === 'string' && value ? value.split(',').map((item) => item.trim()) : [];
|
|
254
|
+
|
|
255
|
+
/** The nth entry of a parallel list, or its only entry when it is shared by all. */
|
|
256
|
+
const pickNth = (list: string[], index: number, total: number): string | undefined =>
|
|
257
|
+
list.length === total ? list[index] : list.length === 1 ? list[0] : undefined;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* True when the message carries a file: flagged `isMediafile`, or (older
|
|
261
|
+
* senders that never set the flag) with a real file URL or attachment. The
|
|
262
|
+
* one test every surface uses for "is this a media bubble".
|
|
263
|
+
*/
|
|
264
|
+
export const isMediaMessage = (
|
|
265
|
+
message?:
|
|
266
|
+
| (Partial<Pick<IMessage, 'isDeleted' | 'isMediafile' | 'attachments'>> & {
|
|
267
|
+
location?: unknown;
|
|
268
|
+
mimetype?: unknown;
|
|
269
|
+
originalName?: unknown;
|
|
270
|
+
fileName?: unknown;
|
|
271
|
+
locationPreview?: unknown;
|
|
272
|
+
})
|
|
273
|
+
| null
|
|
274
|
+
): boolean => {
|
|
275
|
+
if (!message || message.isDeleted) return false;
|
|
276
|
+
if (String(message.isMediafile) === 'true') return true;
|
|
277
|
+
return getMessageAttachments(message as never).some(
|
|
278
|
+
(attachment) => Boolean(attachment.location)
|
|
279
|
+
);
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Maps one object from POST /files/ `results` to the attachment a sent
|
|
284
|
+
* message carries. Field names here follow the API's own spelling
|
|
285
|
+
* (`originalname`, `filename`), not the wire attribute names.
|
|
286
|
+
*/
|
|
287
|
+
export const toUploadedAttachment = (raw: any): IAttachment => ({
|
|
288
|
+
attachmentId: raw?._id,
|
|
289
|
+
location: raw?.location,
|
|
290
|
+
locationPreview: raw?.locationPreview,
|
|
291
|
+
mimetype: raw?.mimetype,
|
|
292
|
+
originalName:
|
|
293
|
+
typeof raw?.originalname === 'string'
|
|
294
|
+
? repairFilenameEncoding(raw.originalname)
|
|
295
|
+
: raw?.originalname,
|
|
296
|
+
fileName:
|
|
297
|
+
typeof raw?.filename === 'string'
|
|
298
|
+
? repairFilenameEncoding(raw.filename)
|
|
299
|
+
: raw?.filename,
|
|
300
|
+
size: raw?.size?.toString?.() ?? raw?.size,
|
|
301
|
+
duration: raw?.duration,
|
|
302
|
+
ownerKey: raw?.ownerKey,
|
|
303
|
+
userId: raw?.userId,
|
|
304
|
+
createdAt: raw?.createdAt,
|
|
305
|
+
updatedAt: raw?.updatedAt,
|
|
306
|
+
expiresAt: raw?.expiresAt,
|
|
307
|
+
isVisible: raw?.isVisible,
|
|
308
|
+
isPrivate: raw?.isPrivate,
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
/** Name to show for an attachment, with the URL tail as the last resort. */
|
|
312
|
+
export const getAttachmentName = (attachment: IAttachment): string =>
|
|
313
|
+
attachment.originalName ||
|
|
314
|
+
attachment.fileName ||
|
|
315
|
+
attachment.location?.split('/')?.pop() ||
|
|
316
|
+
'MediaFile';
|