@ethora/chat-component-rn 26.5.7 → 26.5.10
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 +133 -0
- package/README.md +67 -10
- package/index.js +24 -0
- package/lib/commonjs/assets/icons.js +5 -1
- package/lib/commonjs/assets/icons.js.map +1 -1
- package/lib/commonjs/components/ContextMenu/ContextMenuComponents.js +11 -5
- package/lib/commonjs/components/ContextMenu/ContextMenuComponents.js.map +1 -1
- package/lib/commonjs/components/MainComponents/ChatRoom.js +64 -38
- package/lib/commonjs/components/MainComponents/ChatRoom.js.map +1 -1
- package/lib/commonjs/components/MainComponents/ChatWrapper.js +57 -13
- package/lib/commonjs/components/MainComponents/ChatWrapper.js.map +1 -1
- package/lib/commonjs/components/MainComponents/MediaMessage.js +4 -2
- package/lib/commonjs/components/MainComponents/MediaMessage.js.map +1 -1
- package/lib/commonjs/components/MainComponents/MessageContainer.js +2 -2
- package/lib/commonjs/components/MainComponents/MessageContainer.js.map +1 -1
- package/lib/commonjs/components/MainComponents/MessageList.js +165 -28
- package/lib/commonjs/components/MainComponents/MessageList.js.map +1 -1
- package/lib/commonjs/components/MainComponents/ReduxWrapper.js +17 -8
- package/lib/commonjs/components/MainComponents/ReduxWrapper.js.map +1 -1
- package/lib/commonjs/components/MainComponents/SystemMessage.js +34 -22
- package/lib/commonjs/components/MainComponents/SystemMessage.js.map +1 -1
- package/lib/commonjs/components/MessageBubble/Message.js +84 -39
- package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
- package/lib/commonjs/components/MessageBubble/MessageInteractions.js +82 -22
- package/lib/commonjs/components/MessageBubble/MessageInteractions.js.map +1 -1
- package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js +16 -4
- package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
- package/lib/commonjs/components/Modals/FilePreviewModal/DocumentViewer.js +104 -0
- package/lib/commonjs/components/Modals/FilePreviewModal/DocumentViewer.js.map +1 -0
- package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +140 -24
- package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
- package/lib/commonjs/components/Modals/FilePreviewModal/PdfView.js +29 -1
- package/lib/commonjs/components/Modals/FilePreviewModal/PdfView.js.map +1 -1
- package/lib/commonjs/components/Modals/Modal/Modal.js +5 -1
- package/lib/commonjs/components/Modals/Modal/Modal.js.map +1 -1
- package/lib/commonjs/components/Modals/ModalWrapper/ModalWrapper.js +77 -47
- package/lib/commonjs/components/Modals/ModalWrapper/ModalWrapper.js.map +1 -1
- package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js +3 -3
- package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
- package/lib/commonjs/components/Modals/styledModalComponents.js +4 -9
- package/lib/commonjs/components/Modals/styledModalComponents.js.map +1 -1
- package/lib/commonjs/components/styled/MediaFilePreview.js +47 -28
- package/lib/commonjs/components/styled/MediaFilePreview.js.map +1 -1
- package/lib/commonjs/components/styled/MessageImage.js +49 -9
- package/lib/commonjs/components/styled/MessageImage.js.map +1 -1
- package/lib/commonjs/components/styled/NewMessageLabel.js +8 -6
- package/lib/commonjs/components/styled/NewMessageLabel.js.map +1 -1
- package/lib/commonjs/components/styled/SendInput.js +49 -27
- package/lib/commonjs/components/styled/SendInput.js.map +1 -1
- package/lib/commonjs/components/styled/StyledInputComponents/MediaComponents.js +14 -4
- package/lib/commonjs/components/styled/StyledInputComponents/MediaComponents.js.map +1 -1
- package/lib/commonjs/components/styled/StyledInputComponents/StyledInputComponents.js +7 -2
- package/lib/commonjs/components/styled/StyledInputComponents/StyledInputComponents.js.map +1 -1
- package/lib/commonjs/components/styled/UnsupportedType.js +5 -1
- package/lib/commonjs/components/styled/UnsupportedType.js.map +1 -1
- package/lib/commonjs/components/styled/VideoMessage.js +60 -14
- package/lib/commonjs/components/styled/VideoMessage.js.map +1 -1
- package/lib/commonjs/context/MessageNotificationContext.js +7 -7
- package/lib/commonjs/context/MessageNotificationContext.js.map +1 -1
- package/lib/commonjs/context/xmppProvider.js +244 -32
- package/lib/commonjs/context/xmppProvider.js.map +1 -1
- package/lib/commonjs/helpers/chatAutoEnterer.js +2 -7
- package/lib/commonjs/helpers/chatAutoEnterer.js.map +1 -1
- package/lib/commonjs/helpers/historyPreloadScheduler.js +4 -1
- package/lib/commonjs/helpers/historyPreloadScheduler.js.map +1 -1
- package/lib/commonjs/helpers/insertMessageWithDelimiter.js +15 -5
- package/lib/commonjs/helpers/insertMessageWithDelimiter.js.map +1 -1
- package/lib/commonjs/helpers/keyboardLayout.js +18 -0
- package/lib/commonjs/helpers/keyboardLayout.js.map +1 -0
- package/lib/commonjs/helpers/mediaDimensions.js +51 -0
- package/lib/commonjs/helpers/mediaDimensions.js.map +1 -0
- package/lib/commonjs/helpers/normalizeRoomJid.js +20 -0
- package/lib/commonjs/helpers/normalizeRoomJid.js.map +1 -0
- package/lib/commonjs/hooks/useChatRoomFocus.js +87 -0
- package/lib/commonjs/hooks/useChatRoomFocus.js.map +1 -0
- package/lib/commonjs/hooks/useHeapSender.js +1 -1
- package/lib/commonjs/hooks/useHeapSender.js.map +1 -1
- package/lib/commonjs/hooks/useLogout.js +3 -3
- package/lib/commonjs/hooks/useLogout.js.map +1 -1
- package/lib/commonjs/hooks/usePushNotifications.js +6 -2
- package/lib/commonjs/hooks/usePushNotifications.js.map +1 -1
- package/lib/commonjs/hooks/useRoomState.js +2 -0
- package/lib/commonjs/hooks/useRoomState.js.map +1 -1
- package/lib/commonjs/hooks/useSendMessage.js +201 -10
- package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
- package/lib/commonjs/hooks/useUnreadMessagesCounter.js +12 -14
- package/lib/commonjs/hooks/useUnreadMessagesCounter.js.map +1 -1
- package/lib/commonjs/main.js +7 -0
- package/lib/commonjs/main.js.map +1 -1
- package/lib/commonjs/networking/api-requests/auth.api.js +67 -0
- package/lib/commonjs/networking/api-requests/auth.api.js.map +1 -1
- package/lib/commonjs/networking/apiClient.js +2 -1
- package/lib/commonjs/networking/apiClient.js.map +1 -1
- package/lib/commonjs/networking/outboundQueue.js +92 -0
- package/lib/commonjs/networking/outboundQueue.js.map +1 -0
- package/lib/commonjs/networking/xmpp/createTimeoutPromise.xmpp.js +4 -1
- package/lib/commonjs/networking/xmpp/createTimeoutPromise.xmpp.js.map +1 -1
- package/lib/commonjs/networking/xmpp/flushLastViewedToPrivateStore.js +7 -8
- package/lib/commonjs/networking/xmpp/flushLastViewedToPrivateStore.js.map +1 -1
- package/lib/commonjs/networking/xmpp/getRoomsPaged.xmpp.js +26 -3
- package/lib/commonjs/networking/xmpp/getRoomsPaged.xmpp.js.map +1 -1
- package/lib/commonjs/networking/xmpp/presenceInRoom.xmpp.js +26 -8
- package/lib/commonjs/networking/xmpp/presenceInRoom.xmpp.js.map +1 -1
- package/lib/commonjs/networking/xmpp/sendTextMessage.xmpp.js +2 -1
- package/lib/commonjs/networking/xmpp/sendTextMessage.xmpp.js.map +1 -1
- package/lib/commonjs/networking/xmppClient.js +294 -35
- package/lib/commonjs/networking/xmppClient.js.map +1 -1
- package/lib/commonjs/roomStore/Middleware/unreadMidlleware.js +22 -6
- package/lib/commonjs/roomStore/Middleware/unreadMidlleware.js.map +1 -1
- package/lib/commonjs/roomStore/roomsSlice.js +157 -23
- package/lib/commonjs/roomStore/roomsSlice.js.map +1 -1
- package/lib/commonjs/utils/installPromiseRejectionTracker.js +83 -0
- package/lib/commonjs/utils/installPromiseRejectionTracker.js.map +1 -0
- package/lib/commonjs/utils/resendMessage.js +1 -1
- package/lib/commonjs/utils/resendMessage.js.map +1 -1
- package/lib/module/assets/icons.js +5 -1
- package/lib/module/assets/icons.js.map +1 -1
- package/lib/module/components/ContextMenu/ContextMenuComponents.js +11 -5
- package/lib/module/components/ContextMenu/ContextMenuComponents.js.map +1 -1
- package/lib/module/components/MainComponents/ChatRoom.js +65 -39
- package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
- package/lib/module/components/MainComponents/ChatWrapper.js +57 -13
- package/lib/module/components/MainComponents/ChatWrapper.js.map +1 -1
- package/lib/module/components/MainComponents/MediaMessage.js +4 -2
- package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
- package/lib/module/components/MainComponents/MessageContainer.js +2 -2
- package/lib/module/components/MainComponents/MessageContainer.js.map +1 -1
- package/lib/module/components/MainComponents/MessageList.js +166 -29
- package/lib/module/components/MainComponents/MessageList.js.map +1 -1
- package/lib/module/components/MainComponents/ReduxWrapper.js +18 -8
- package/lib/module/components/MainComponents/ReduxWrapper.js.map +1 -1
- package/lib/module/components/MainComponents/SystemMessage.js +33 -21
- package/lib/module/components/MainComponents/SystemMessage.js.map +1 -1
- package/lib/module/components/MessageBubble/Message.js +85 -40
- package/lib/module/components/MessageBubble/Message.js.map +1 -1
- package/lib/module/components/MessageBubble/MessageInteractions.js +84 -24
- package/lib/module/components/MessageBubble/MessageInteractions.js.map +1 -1
- package/lib/module/components/Modals/AttachSheet/AttachSheet.js +15 -3
- package/lib/module/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
- package/lib/module/components/Modals/FilePreviewModal/DocumentViewer.js +99 -0
- package/lib/module/components/Modals/FilePreviewModal/DocumentViewer.js.map +1 -0
- package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +143 -25
- package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
- package/lib/module/components/Modals/FilePreviewModal/PdfView.js +31 -3
- package/lib/module/components/Modals/FilePreviewModal/PdfView.js.map +1 -1
- package/lib/module/components/Modals/Modal/Modal.js +5 -1
- package/lib/module/components/Modals/Modal/Modal.js.map +1 -1
- package/lib/module/components/Modals/ModalWrapper/ModalWrapper.js +79 -49
- package/lib/module/components/Modals/ModalWrapper/ModalWrapper.js.map +1 -1
- package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js +3 -3
- package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
- package/lib/module/components/Modals/styledModalComponents.js +4 -9
- package/lib/module/components/Modals/styledModalComponents.js.map +1 -1
- package/lib/module/components/styled/MediaFilePreview.js +47 -28
- package/lib/module/components/styled/MediaFilePreview.js.map +1 -1
- package/lib/module/components/styled/MessageImage.js +51 -11
- package/lib/module/components/styled/MessageImage.js.map +1 -1
- package/lib/module/components/styled/NewMessageLabel.js +8 -6
- package/lib/module/components/styled/NewMessageLabel.js.map +1 -1
- package/lib/module/components/styled/SendInput.js +50 -28
- package/lib/module/components/styled/SendInput.js.map +1 -1
- package/lib/module/components/styled/StyledInputComponents/MediaComponents.js +14 -4
- package/lib/module/components/styled/StyledInputComponents/MediaComponents.js.map +1 -1
- package/lib/module/components/styled/StyledInputComponents/StyledInputComponents.js +7 -2
- package/lib/module/components/styled/StyledInputComponents/StyledInputComponents.js.map +1 -1
- package/lib/module/components/styled/UnsupportedType.js +5 -1
- package/lib/module/components/styled/UnsupportedType.js.map +1 -1
- package/lib/module/components/styled/VideoMessage.js +61 -15
- package/lib/module/components/styled/VideoMessage.js.map +1 -1
- package/lib/module/context/MessageNotificationContext.js +7 -7
- package/lib/module/context/MessageNotificationContext.js.map +1 -1
- package/lib/module/context/xmppProvider.js +246 -34
- package/lib/module/context/xmppProvider.js.map +1 -1
- package/lib/module/helpers/chatAutoEnterer.js +2 -7
- package/lib/module/helpers/chatAutoEnterer.js.map +1 -1
- package/lib/module/helpers/historyPreloadScheduler.js +4 -1
- package/lib/module/helpers/historyPreloadScheduler.js.map +1 -1
- package/lib/module/helpers/insertMessageWithDelimiter.js +15 -5
- package/lib/module/helpers/insertMessageWithDelimiter.js.map +1 -1
- package/lib/module/helpers/keyboardLayout.js +12 -0
- package/lib/module/helpers/keyboardLayout.js.map +1 -0
- package/lib/module/helpers/mediaDimensions.js +46 -0
- package/lib/module/helpers/mediaDimensions.js.map +1 -0
- package/lib/module/helpers/normalizeRoomJid.js +15 -0
- package/lib/module/helpers/normalizeRoomJid.js.map +1 -0
- package/lib/module/hooks/useChatRoomFocus.js +82 -0
- package/lib/module/hooks/useChatRoomFocus.js.map +1 -0
- package/lib/module/hooks/useHeapSender.js +1 -1
- package/lib/module/hooks/useHeapSender.js.map +1 -1
- package/lib/module/hooks/useLogout.js +3 -3
- package/lib/module/hooks/useLogout.js.map +1 -1
- package/lib/module/hooks/usePushNotifications.js +6 -2
- package/lib/module/hooks/usePushNotifications.js.map +1 -1
- package/lib/module/hooks/useRoomState.js +2 -0
- package/lib/module/hooks/useRoomState.js.map +1 -1
- package/lib/module/hooks/useSendMessage.js +203 -12
- package/lib/module/hooks/useSendMessage.js.map +1 -1
- package/lib/module/hooks/useUnreadMessagesCounter.js +12 -14
- package/lib/module/hooks/useUnreadMessagesCounter.js.map +1 -1
- package/lib/module/main.js +1 -0
- package/lib/module/main.js.map +1 -1
- package/lib/module/networking/api-requests/auth.api.js +67 -1
- package/lib/module/networking/api-requests/auth.api.js.map +1 -1
- package/lib/module/networking/apiClient.js +2 -1
- package/lib/module/networking/apiClient.js.map +1 -1
- package/lib/module/networking/outboundQueue.js +84 -0
- package/lib/module/networking/outboundQueue.js.map +1 -0
- package/lib/module/networking/xmpp/createTimeoutPromise.xmpp.js +4 -1
- package/lib/module/networking/xmpp/createTimeoutPromise.xmpp.js.map +1 -1
- package/lib/module/networking/xmpp/flushLastViewedToPrivateStore.js +7 -8
- package/lib/module/networking/xmpp/flushLastViewedToPrivateStore.js.map +1 -1
- package/lib/module/networking/xmpp/getRoomsPaged.xmpp.js +26 -3
- package/lib/module/networking/xmpp/getRoomsPaged.xmpp.js.map +1 -1
- package/lib/module/networking/xmpp/presenceInRoom.xmpp.js +26 -8
- package/lib/module/networking/xmpp/presenceInRoom.xmpp.js.map +1 -1
- package/lib/module/networking/xmpp/sendTextMessage.xmpp.js +2 -1
- package/lib/module/networking/xmpp/sendTextMessage.xmpp.js.map +1 -1
- package/lib/module/networking/xmppClient.js +294 -35
- package/lib/module/networking/xmppClient.js.map +1 -1
- package/lib/module/roomStore/Middleware/unreadMidlleware.js +22 -6
- package/lib/module/roomStore/Middleware/unreadMidlleware.js.map +1 -1
- package/lib/module/roomStore/roomsSlice.js +154 -22
- package/lib/module/roomStore/roomsSlice.js.map +1 -1
- package/lib/module/utils/installPromiseRejectionTracker.js +78 -0
- package/lib/module/utils/installPromiseRejectionTracker.js.map +1 -0
- package/lib/module/utils/resendMessage.js +1 -1
- package/lib/module/utils/resendMessage.js.map +1 -1
- package/lib/typescript/assets/icons.d.ts +8 -4
- package/lib/typescript/assets/icons.d.ts.map +1 -1
- package/lib/typescript/components/ContextMenu/ContextMenuComponents.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/ChatWrapper.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/MediaMessage.d.ts +1 -0
- package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/MessageContainer.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/MessageList.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/ReduxWrapper.d.ts +1 -0
- package/lib/typescript/components/MainComponents/ReduxWrapper.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/SystemMessage.d.ts +0 -7
- package/lib/typescript/components/MainComponents/SystemMessage.d.ts.map +1 -1
- package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
- package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts +4 -2
- package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts.map +1 -1
- package/lib/typescript/components/Modals/AttachSheet/AttachSheet.d.ts.map +1 -1
- package/lib/typescript/components/Modals/FilePreviewModal/DocumentViewer.d.ts +29 -0
- package/lib/typescript/components/Modals/FilePreviewModal/DocumentViewer.d.ts.map +1 -0
- package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts +0 -1
- package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/FilePreviewModal/PdfView.d.ts.map +1 -1
- package/lib/typescript/components/Modals/Modal/Modal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/ModalWrapper/ModalWrapper.d.ts.map +1 -1
- package/lib/typescript/components/Modals/UserProfileModal/UserProfileModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/styledModalComponents.d.ts.map +1 -1
- package/lib/typescript/components/styled/MediaFilePreview.d.ts.map +1 -1
- package/lib/typescript/components/styled/MessageImage.d.ts.map +1 -1
- package/lib/typescript/components/styled/NewMessageLabel.d.ts +1 -5
- package/lib/typescript/components/styled/NewMessageLabel.d.ts.map +1 -1
- package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
- package/lib/typescript/components/styled/StyledInputComponents/MediaComponents.d.ts +9 -1
- package/lib/typescript/components/styled/StyledInputComponents/MediaComponents.d.ts.map +1 -1
- package/lib/typescript/components/styled/StyledInputComponents/StyledInputComponents.d.ts.map +1 -1
- package/lib/typescript/components/styled/UnsupportedType.d.ts +1 -0
- package/lib/typescript/components/styled/UnsupportedType.d.ts.map +1 -1
- package/lib/typescript/components/styled/VideoMessage.d.ts.map +1 -1
- package/lib/typescript/context/xmppProvider.d.ts +1 -0
- package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
- package/lib/typescript/helpers/chatAutoEnterer.d.ts.map +1 -1
- package/lib/typescript/helpers/historyPreloadScheduler.d.ts.map +1 -1
- package/lib/typescript/helpers/insertMessageWithDelimiter.d.ts.map +1 -1
- package/lib/typescript/helpers/keyboardLayout.d.ts +9 -0
- package/lib/typescript/helpers/keyboardLayout.d.ts.map +1 -0
- package/lib/typescript/helpers/mediaDimensions.d.ts +9 -0
- package/lib/typescript/helpers/mediaDimensions.d.ts.map +1 -0
- package/lib/typescript/helpers/normalizeRoomJid.d.ts +2 -0
- package/lib/typescript/helpers/normalizeRoomJid.d.ts.map +1 -0
- package/lib/typescript/hooks/useChatRoomFocus.d.ts +42 -0
- package/lib/typescript/hooks/useChatRoomFocus.d.ts.map +1 -0
- package/lib/typescript/hooks/useHeapSender.d.ts.map +1 -1
- package/lib/typescript/hooks/usePushNotifications.d.ts.map +1 -1
- package/lib/typescript/hooks/useRoomState.d.ts +1 -0
- package/lib/typescript/hooks/useRoomState.d.ts.map +1 -1
- package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
- package/lib/typescript/hooks/useUnreadMessagesCounter.d.ts.map +1 -1
- package/lib/typescript/main.d.ts +1 -0
- package/lib/typescript/main.d.ts.map +1 -1
- package/lib/typescript/networking/api-requests/auth.api.d.ts +3 -0
- package/lib/typescript/networking/api-requests/auth.api.d.ts.map +1 -1
- package/lib/typescript/networking/apiClient.d.ts.map +1 -1
- package/lib/typescript/networking/outboundQueue.d.ts +57 -0
- package/lib/typescript/networking/outboundQueue.d.ts.map +1 -0
- package/lib/typescript/networking/xmpp/createTimeoutPromise.xmpp.d.ts.map +1 -1
- package/lib/typescript/networking/xmpp/flushLastViewedToPrivateStore.d.ts +1 -1
- package/lib/typescript/networking/xmpp/flushLastViewedToPrivateStore.d.ts.map +1 -1
- package/lib/typescript/networking/xmpp/getRoomsPaged.xmpp.d.ts +1 -1
- package/lib/typescript/networking/xmpp/getRoomsPaged.xmpp.d.ts.map +1 -1
- package/lib/typescript/networking/xmpp/presenceInRoom.xmpp.d.ts.map +1 -1
- package/lib/typescript/networking/xmpp/sendTextMessage.xmpp.d.ts.map +1 -1
- package/lib/typescript/networking/xmppClient.d.ts +33 -4
- package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
- package/lib/typescript/roomStore/Middleware/unreadMidlleware.d.ts.map +1 -1
- package/lib/typescript/roomStore/roomsSlice.d.ts +8 -1
- package/lib/typescript/roomStore/roomsSlice.d.ts.map +1 -1
- package/lib/typescript/types/models/media.model.d.ts +1 -0
- package/lib/typescript/types/models/media.model.d.ts.map +1 -1
- package/lib/typescript/types/models/xmpp.model.d.ts +1 -1
- package/lib/typescript/types/models/xmpp.model.d.ts.map +1 -1
- package/lib/typescript/types/types.d.ts +4 -0
- package/lib/typescript/types/types.d.ts.map +1 -1
- package/lib/typescript/utils/installPromiseRejectionTracker.d.ts +20 -0
- package/lib/typescript/utils/installPromiseRejectionTracker.d.ts.map +1 -0
- package/lib/typescript/utils/resendMessage.d.ts.map +1 -1
- package/package.json +18 -2
- package/src/assets/icons.tsx +23 -14
- package/src/components/ContextMenu/ContextMenuComponents.tsx +10 -5
- package/src/components/MainComponents/ChatRoom.tsx +81 -66
- package/src/components/MainComponents/ChatWrapper.tsx +99 -21
- package/src/components/MainComponents/MediaMessage.tsx +3 -0
- package/src/components/MainComponents/MessageContainer.tsx +4 -2
- package/src/components/MainComponents/MessageList.tsx +175 -28
- package/src/components/MainComponents/ReduxWrapper.tsx +19 -9
- package/src/components/MainComponents/SystemMessage.tsx +38 -26
- package/src/components/MessageBubble/Message.tsx +91 -62
- package/src/components/MessageBubble/MessageInteractions.tsx +98 -29
- package/src/components/Modals/AttachSheet/AttachSheet.tsx +17 -3
- package/src/components/Modals/FilePreviewModal/DocumentViewer.tsx +93 -0
- package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +152 -25
- package/src/components/Modals/FilePreviewModal/PdfView.tsx +32 -4
- package/src/components/Modals/Modal/Modal.tsx +4 -1
- package/src/components/Modals/ModalWrapper/ModalWrapper.tsx +70 -58
- package/src/components/Modals/UserProfileModal/UserProfileModal.tsx +19 -15
- package/src/components/Modals/styledModalComponents.tsx +4 -9
- package/src/components/styled/MediaFilePreview.tsx +46 -26
- package/src/components/styled/MessageImage.tsx +58 -13
- package/src/components/styled/NewMessageLabel.tsx +8 -5
- package/src/components/styled/SendInput.tsx +64 -29
- package/src/components/styled/StyledInputComponents/MediaComponents.tsx +15 -5
- package/src/components/styled/StyledInputComponents/StyledInputComponents.tsx +7 -2
- package/src/components/styled/UnsupportedType.tsx +7 -1
- package/src/components/styled/VideoMessage.tsx +59 -11
- package/src/context/MessageNotificationContext.tsx +8 -8
- package/src/context/xmppProvider.tsx +247 -30
- package/src/helpers/chatAutoEnterer.ts +2 -8
- package/src/helpers/historyPreloadScheduler.ts +4 -1
- package/src/helpers/insertMessageWithDelimiter.ts +22 -15
- package/src/helpers/keyboardLayout.ts +18 -0
- package/src/helpers/mediaDimensions.ts +47 -0
- package/src/helpers/normalizeRoomJid.ts +6 -0
- package/src/hooks/useChatRoomFocus.ts +95 -0
- package/src/hooks/useHeapSender.tsx +2 -1
- package/src/hooks/useLogout.tsx +3 -3
- package/src/hooks/usePushNotifications.ts +21 -8
- package/src/hooks/useRoomState.tsx +4 -0
- package/src/hooks/useSendMessage.tsx +258 -13
- package/src/hooks/useUnreadMessagesCounter.ts +13 -16
- package/src/main.ts +1 -0
- package/src/networking/api-requests/auth.api.ts +55 -1
- package/src/networking/apiClient.ts +2 -1
- package/src/networking/outboundQueue.ts +98 -0
- package/src/networking/xmpp/createTimeoutPromise.xmpp.ts +5 -1
- package/src/networking/xmpp/flushLastViewedToPrivateStore.ts +11 -13
- package/src/networking/xmpp/getRoomsPaged.xmpp.ts +27 -3
- package/src/networking/xmpp/presenceInRoom.xmpp.ts +24 -9
- package/src/networking/xmpp/sendTextMessage.xmpp.ts +2 -1
- package/src/networking/xmppClient.ts +317 -43
- package/src/roomStore/Middleware/unreadMidlleware.tsx +23 -5
- package/src/roomStore/roomsSlice.ts +177 -27
- package/src/types/models/media.model.ts +3 -0
- package/src/types/models/xmpp.model.ts +2 -1
- package/src/types/types.ts +15 -0
- package/src/utils/installPromiseRejectionTracker.ts +91 -0
- package/src/utils/resendMessage.ts +2 -1
|
@@ -9,6 +9,7 @@ import React, {
|
|
|
9
9
|
useState,
|
|
10
10
|
} from 'react';
|
|
11
11
|
import { AppState, DeviceEventEmitter } from 'react-native';
|
|
12
|
+
import NetInfo from '@react-native-community/netinfo';
|
|
12
13
|
import XmppClient, {
|
|
13
14
|
XmppCredentialsProvider,
|
|
14
15
|
} from '../networking/xmppClient';
|
|
@@ -21,6 +22,7 @@ import {
|
|
|
21
22
|
} from '../types/types';
|
|
22
23
|
import {
|
|
23
24
|
buildXmppClientKey,
|
|
25
|
+
getGlobalXmppClient,
|
|
24
26
|
getReusableXmppClientByKey,
|
|
25
27
|
isXmppClientReusable,
|
|
26
28
|
setGlobalXmppClient,
|
|
@@ -36,7 +38,13 @@ import { getRooms as prefetchRoomsViaRest } from '../networking/api-requests/roo
|
|
|
36
38
|
import { allRoomPresences } from '../networking/xmpp/allRoomPresences.xmpp';
|
|
37
39
|
import { store } from '../roomStore';
|
|
38
40
|
import { logout, setStoreClient } from '../roomStore/chatSettingsSlice';
|
|
39
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
setLogoutState,
|
|
43
|
+
setVisibleRoom,
|
|
44
|
+
clearVisibleRoom,
|
|
45
|
+
setLastViewedTimestamp,
|
|
46
|
+
deleteRoomMessage,
|
|
47
|
+
} from '../roomStore/roomsSlice';
|
|
40
48
|
import { runHistoryPreloadScheduler } from '../helpers/historyPreloadScheduler';
|
|
41
49
|
import { asyncLocalStorage } from '../hooks/useLocalStorage';
|
|
42
50
|
import { localStorageConstants } from '../helpers/constants/LOCAL_STORAGE';
|
|
@@ -64,15 +72,18 @@ const XmppContext = createContext<XmppContextType | null>(null);
|
|
|
64
72
|
interface XmppProviderProps {
|
|
65
73
|
children: ReactNode;
|
|
66
74
|
config?: IConfig;
|
|
75
|
+
// Consumer-supplied "is the chat surface shown" signal for hosts that
|
|
76
|
+
// keep <Chat> mounted while hidden (tab/route navigators). See the
|
|
77
|
+
// visibility effect below. Omit when you unmount <Chat> on hide.
|
|
78
|
+
isVisible?: boolean;
|
|
67
79
|
}
|
|
68
80
|
|
|
69
81
|
const LOGOUT_EVENT = 'ethora-xmpp-logout';
|
|
70
82
|
|
|
71
|
-
export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config }) => {
|
|
83
|
+
export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config, isVisible }) => {
|
|
72
84
|
const [client, setClient] = useState<XmppClient | null>(null);
|
|
73
85
|
const [providerBootstrapStatus, setProviderBootstrapStatus] =
|
|
74
86
|
useState<ProviderBootstrapStatus>('idle');
|
|
75
|
-
const [reconnectAttempts, setReconnectAttempts] = useState(0);
|
|
76
87
|
|
|
77
88
|
// Track which "init mode" this provider runs in. When config.initBeforeLoad
|
|
78
89
|
// is true, the provider owns bootstrap and ChatWrapper just waits.
|
|
@@ -181,6 +192,10 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
|
|
|
181
192
|
// 3. Otherwise create a new client under the init lock to dedup
|
|
182
193
|
// concurrent calls for the same key.
|
|
183
194
|
const created = await withXmppClientInitLock(key, async () => {
|
|
195
|
+
const prevGlobal = getGlobalXmppClient();
|
|
196
|
+
if (prevGlobal) {
|
|
197
|
+
try { await prevGlobal.close(); } catch {}
|
|
198
|
+
}
|
|
184
199
|
const newClient = new XmppClient(username, password, settings);
|
|
185
200
|
// Wire up the credentialsProvider so SASL `not-authorized`
|
|
186
201
|
// after idle triggers a refresh chain (jwtLogin →
|
|
@@ -200,9 +215,30 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
|
|
|
200
215
|
// may have changed since the singleton was created.
|
|
201
216
|
created.setCredentialsProvider(credentialsProvider);
|
|
202
217
|
|
|
218
|
+
// Re-join MUC rooms on every 'online'. After a reconnect the new
|
|
219
|
+
// XMPP stream isn't a member of any room, so a sent message gets a
|
|
220
|
+
// local double-tick but never reaches the room (bug #21). Re-sending
|
|
221
|
+
// presence on reconnect fixes delivery. On the very first connect
|
|
222
|
+
// the room list isn't loaded yet so this joins nothing — the
|
|
223
|
+
// bootstrap's own allRoomPresences handles that pass.
|
|
224
|
+
created.setOnOnline(() => {
|
|
225
|
+
const underlying = (created as any).client;
|
|
226
|
+
if (!underlying) {return;}
|
|
227
|
+
allRoomPresences(underlying).catch((e) =>
|
|
228
|
+
devPushLog('warn', 'reconnect: allRoomPresences re-join failed', e)
|
|
229
|
+
);
|
|
230
|
+
// Also refresh the private store so unread / lastViewed markers
|
|
231
|
+
// are accurate after a long reconnect — the MUC re-join above only
|
|
232
|
+
// restores delivery, not unread state. Idempotent on first connect.
|
|
233
|
+
created
|
|
234
|
+
.getChatsPrivateStoreRequestStanza()
|
|
235
|
+
.catch((e: unknown) =>
|
|
236
|
+
devPushLog('warn', 'reconnect: privateStore refresh failed', e)
|
|
237
|
+
);
|
|
238
|
+
});
|
|
239
|
+
|
|
203
240
|
setClient(created);
|
|
204
241
|
lastCredsRef.current = { username, password, settings };
|
|
205
|
-
setReconnectAttempts(0);
|
|
206
242
|
return created;
|
|
207
243
|
},
|
|
208
244
|
[client, config?.xmppSettings, credentialsProvider]
|
|
@@ -219,6 +255,30 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
|
|
|
219
255
|
|
|
220
256
|
const abortController = new AbortController();
|
|
221
257
|
let cancelled = false;
|
|
258
|
+
// Auto-retry a TRANSIENT bootstrap failure a few times with backoff
|
|
259
|
+
// before surfacing the error modal — a flaky network at launch should
|
|
260
|
+
// self-heal, not dead-end the user on a Retry button. (Bad-creds
|
|
261
|
+
// failures resolve to null below and fail fast — they never reach the
|
|
262
|
+
// catch, so they aren't retried.)
|
|
263
|
+
const MAX_BOOTSTRAP_ATTEMPTS = 3;
|
|
264
|
+
let bootstrapAttempt = 0;
|
|
265
|
+
// Overall time budget: if we never reach 'ready' (a step hangs), abort
|
|
266
|
+
// the in-flight work and fail so the loader can't spin forever.
|
|
267
|
+
const BOOTSTRAP_BUDGET_MS = 45000;
|
|
268
|
+
let reachedReady = false;
|
|
269
|
+
let budgetTimer: ReturnType<typeof setTimeout> | null = setTimeout(() => {
|
|
270
|
+
if (cancelled || reachedReady) {return;}
|
|
271
|
+
devPushLog('warn', 'initBeforeLoad: time budget exceeded → failed');
|
|
272
|
+
abortController.abort();
|
|
273
|
+
inflightBootstrapKeyRef.current = '';
|
|
274
|
+
setProviderBootstrapStatus('failed');
|
|
275
|
+
}, BOOTSTRAP_BUDGET_MS);
|
|
276
|
+
const clearBudget = () => {
|
|
277
|
+
if (budgetTimer) {
|
|
278
|
+
clearTimeout(budgetTimer);
|
|
279
|
+
budgetTimer = null;
|
|
280
|
+
}
|
|
281
|
+
};
|
|
222
282
|
|
|
223
283
|
const run = async () => {
|
|
224
284
|
// Stable bootstrap key — re-run only when these change.
|
|
@@ -332,6 +392,8 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
|
|
|
332
392
|
store.dispatch(setStoreClient(c));
|
|
333
393
|
completedBootstrapKeyRef.current = key;
|
|
334
394
|
inflightBootstrapKeyRef.current = '';
|
|
395
|
+
reachedReady = true;
|
|
396
|
+
clearBudget();
|
|
335
397
|
setProviderBootstrapStatus('ready');
|
|
336
398
|
devPushLog('rn', 'initBeforeLoad: READY');
|
|
337
399
|
|
|
@@ -352,13 +414,34 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
|
|
|
352
414
|
roomLimit: qos?.preloadTopKRooms,
|
|
353
415
|
}).catch((err) => console.warn('History preload scheduler failed', err));
|
|
354
416
|
} catch (error: any) {
|
|
355
|
-
if (cancelled) {return;}
|
|
356
|
-
|
|
417
|
+
if (cancelled || abortController.signal.aborted) {return;}
|
|
418
|
+
bootstrapAttempt += 1;
|
|
419
|
+
// Throws here are transient (network / XMPP connect). A bad-creds
|
|
420
|
+
// failure resolves to null above and fails fast without throwing,
|
|
421
|
+
// so it never reaches this catch. Retry with backoff before the
|
|
422
|
+
// modal; release the in-flight key so the recursive run() isn't
|
|
423
|
+
// short-circuited by its own guard.
|
|
424
|
+
if (bootstrapAttempt < MAX_BOOTSTRAP_ATTEMPTS) {
|
|
425
|
+
const backoff = Math.min(8000, 1000 * 2 ** (bootstrapAttempt - 1));
|
|
426
|
+
devPushLog(
|
|
427
|
+
'rn',
|
|
428
|
+
`initBeforeLoad: attempt ${bootstrapAttempt} failed (${error?.message}); retrying in ${backoff}ms`
|
|
429
|
+
);
|
|
430
|
+
inflightBootstrapKeyRef.current = '';
|
|
431
|
+
setTimeout(() => {
|
|
432
|
+
if (!cancelled && !abortController.signal.aborted) {
|
|
433
|
+
run();
|
|
434
|
+
}
|
|
435
|
+
}, backoff);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
devPushLog('error', 'initBeforeLoad: bootstrap threw (giving up)', {
|
|
357
439
|
message: error?.message,
|
|
358
440
|
status: error?.response?.status,
|
|
359
441
|
data: error?.response?.data,
|
|
360
442
|
});
|
|
361
443
|
inflightBootstrapKeyRef.current = '';
|
|
444
|
+
clearBudget();
|
|
362
445
|
setProviderBootstrapStatus('failed');
|
|
363
446
|
}
|
|
364
447
|
};
|
|
@@ -367,6 +450,7 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
|
|
|
367
450
|
|
|
368
451
|
return () => {
|
|
369
452
|
cancelled = true;
|
|
453
|
+
clearBudget();
|
|
370
454
|
abortController.abort();
|
|
371
455
|
// Re-running this effect (deps changed or unmount) must release the
|
|
372
456
|
// in-flight bootstrap key so the next render isn't permanently
|
|
@@ -396,29 +480,30 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
|
|
|
396
480
|
]);
|
|
397
481
|
|
|
398
482
|
// -----------------------------------------------------------
|
|
399
|
-
// Reconnect
|
|
483
|
+
// Reconnect watchdog. The client now owns reconnection (onDisconnect →
|
|
484
|
+
// scheduleReconnect with uncapped, clamped backoff; a connect 'error'
|
|
485
|
+
// reschedules too). This is the single provider-side safety net for the
|
|
486
|
+
// states the event-driven triggers miss: a stuck 'connecting', a connect
|
|
487
|
+
// 'error', or a server-side outage where NetInfo never reported the
|
|
488
|
+
// device offline. While foregrounded, if we're not provably online and
|
|
489
|
+
// not suppressed, force a reconnect (debounced inside forceReconnect).
|
|
490
|
+
//
|
|
491
|
+
// Replaces the old status-keyed reconnect effect, which keyed on
|
|
492
|
+
// `client.status` — a mutated class field React does NOT track — so on a
|
|
493
|
+
// silent socket drop it frequently never re-ran (and its 3-attempts-then-
|
|
494
|
+
// full-reinit path was effectively dead).
|
|
400
495
|
// -----------------------------------------------------------
|
|
401
496
|
useEffect(() => {
|
|
402
497
|
if (!client) {return;}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
// After 3 failed reconnects, drop the client and re-init from scratch.
|
|
413
|
-
if (lastCredsRef.current) {
|
|
414
|
-
const { username, password, settings } = lastCredsRef.current;
|
|
415
|
-
setGlobalXmppClient(null);
|
|
416
|
-
setClient(null);
|
|
417
|
-
initializeClient(username, password, settings).catch((err) =>
|
|
418
|
-
console.error('Full reinit failed:', err)
|
|
419
|
-
);
|
|
420
|
-
}
|
|
421
|
-
}, [client, client?.status, reconnectAttempts, initializeClient]);
|
|
498
|
+
const id = setInterval(() => {
|
|
499
|
+
if (AppState.currentState !== 'active') {return;}
|
|
500
|
+
if (client.status !== 'online' && !client.suppressReconnect) {
|
|
501
|
+
devPushLog('rn', `watchdog: client ${client.status} → forceReconnect`);
|
|
502
|
+
client.forceReconnect();
|
|
503
|
+
}
|
|
504
|
+
}, 30000);
|
|
505
|
+
return () => clearInterval(id);
|
|
506
|
+
}, [client]);
|
|
422
507
|
|
|
423
508
|
// -----------------------------------------------------------
|
|
424
509
|
// AppState → background: flush lastViewedTimestamp into the
|
|
@@ -433,22 +518,143 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
|
|
|
433
518
|
// entry, or when this build has `disableLastRead`.
|
|
434
519
|
// -----------------------------------------------------------
|
|
435
520
|
useEffect(() => {
|
|
521
|
+
// Remembers the room that was visible when we backgrounded so it can
|
|
522
|
+
// be re-marked visible on return. Lives in the effect closure (one per
|
|
523
|
+
// client) — persists across the listener's many invocations.
|
|
524
|
+
let visibleBeforeBackground: string | null = null;
|
|
436
525
|
const sub = AppState.addEventListener('change', (next) => {
|
|
437
|
-
if (next === 'active') {return;}
|
|
438
526
|
const c = client;
|
|
527
|
+
if (next === 'active') {
|
|
528
|
+
// Returned to foreground. If the socket died while backgrounded
|
|
529
|
+
// (iOS suspends the WebSocket; long background kills it), the
|
|
530
|
+
// status may still read 'online' until xmpp.js notices the dead
|
|
531
|
+
// TCP — which can take a full timeout. Force an immediate
|
|
532
|
+
// reconnect when we're not provably online so cached UI gets
|
|
533
|
+
// live again fast instead of "100 years".
|
|
534
|
+
// Only force a reconnect from a genuinely dead state. Don't
|
|
535
|
+
// interrupt an in-progress 'connecting' (iOS delivers spurious
|
|
536
|
+
// inactive→active flaps for control-center / notifications /
|
|
537
|
+
// biometric prompts that would otherwise restart a healthy
|
|
538
|
+
// connect).
|
|
539
|
+
if (c && (c.status === 'offline' || c.status === 'error')) {
|
|
540
|
+
devPushLog('rn', `AppState active: client ${c.status} → forceReconnect`);
|
|
541
|
+
c.forceReconnect();
|
|
542
|
+
}
|
|
543
|
+
// Re-mark the previously-open room visible so its unread clears
|
|
544
|
+
// now that the user is looking again. We restore ONLY what was
|
|
545
|
+
// visible at background time, so a chat the consumer had already
|
|
546
|
+
// blurred (e.g. on a non-chat tab/route) correctly stays cleared.
|
|
547
|
+
if (visibleBeforeBackground) {
|
|
548
|
+
store.dispatch(setVisibleRoom({ roomJID: visibleBeforeBackground }));
|
|
549
|
+
visibleBeforeBackground = null;
|
|
550
|
+
}
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
// Going to background/inactive (sleep / app switcher / tray):
|
|
439
554
|
if (!c) {return;}
|
|
440
555
|
const state = store.getState();
|
|
441
556
|
const rooms = state.rooms?.rooms;
|
|
442
|
-
const
|
|
557
|
+
const visibleRoomJID = state.rooms?.visibleRoomJID || null;
|
|
558
|
+
// Mark the open room "not visible" while backgrounded so messages
|
|
559
|
+
// that arrive (or MAM-replay on reconnect) count as unread instead
|
|
560
|
+
// of being silently treated as read — the "mounted == visible ==
|
|
561
|
+
// read" gap. Stamp lastViewed=now as the read baseline, clear
|
|
562
|
+
// visibility, then flush that marker to the server private store.
|
|
563
|
+
// Restored on the next 'active' transition above. Handled here in
|
|
564
|
+
// the provider so consumers using the component as a package get
|
|
565
|
+
// correct unread without reaching into the chat store themselves.
|
|
566
|
+
visibleBeforeBackground = visibleRoomJID;
|
|
567
|
+
if (visibleRoomJID) {
|
|
568
|
+
store.dispatch(
|
|
569
|
+
setLastViewedTimestamp({ chatJID: visibleRoomJID, timestamp: Date.now() })
|
|
570
|
+
);
|
|
571
|
+
// Drop the "New messages" divider for the room we're leaving so it
|
|
572
|
+
// doesn't linger when the user comes back (mirrors ChatRoom's
|
|
573
|
+
// unmount cleanup, which never runs while the pane stays mounted).
|
|
574
|
+
store.dispatch(
|
|
575
|
+
deleteRoomMessage({ roomJID: visibleRoomJID, messageId: 'delimiter-new' })
|
|
576
|
+
);
|
|
577
|
+
store.dispatch(clearVisibleRoom());
|
|
578
|
+
}
|
|
443
579
|
// Fire-and-forget — we're going to the background and don't
|
|
444
580
|
// care about the resolution path.
|
|
445
|
-
c.flushLastViewedToPrivateStoreStanza(rooms, {
|
|
581
|
+
c.flushLastViewedToPrivateStoreStanza(rooms, { visibleRoomJID }).catch(
|
|
446
582
|
() => {}
|
|
447
583
|
);
|
|
448
584
|
});
|
|
449
585
|
return () => sub.remove();
|
|
450
586
|
}, [client]);
|
|
451
587
|
|
|
588
|
+
// -----------------------------------------------------------
|
|
589
|
+
// Consumer visibility signal (`isVisible` prop). For hosts that keep
|
|
590
|
+
// <Chat> MOUNTED while hidden (tab/route navigators — so the XMPP
|
|
591
|
+
// connection survives tab switches), ChatRoom's mount/unmount can't tell
|
|
592
|
+
// "shown" from "hidden", so the unread middleware would keep the open
|
|
593
|
+
// room's badge at 0 and messages arriving while you're elsewhere look
|
|
594
|
+
// already-read. When the host passes `isVisible`, mirror the AppState
|
|
595
|
+
// background/foreground handling: hidden → stamp lastViewed=now + clear
|
|
596
|
+
// room visibility (so later messages count) + flush; shown → restore
|
|
597
|
+
// visibility for the open room. Doing it here means consumers never
|
|
598
|
+
// touch the chat store themselves. (Undefined = host unmounts on hide,
|
|
599
|
+
// nothing to do.)
|
|
600
|
+
// -----------------------------------------------------------
|
|
601
|
+
useEffect(() => {
|
|
602
|
+
if (typeof isVisible !== 'boolean') {return;}
|
|
603
|
+
const state = store.getState();
|
|
604
|
+
const rooms = state.rooms?.rooms;
|
|
605
|
+
const activeRoomJID = state.rooms?.activeRoomJID || null;
|
|
606
|
+
if (isVisible) {
|
|
607
|
+
if (activeRoomJID) {
|
|
608
|
+
store.dispatch(setVisibleRoom({ roomJID: activeRoomJID }));
|
|
609
|
+
}
|
|
610
|
+
} else {
|
|
611
|
+
if (activeRoomJID) {
|
|
612
|
+
store.dispatch(
|
|
613
|
+
setLastViewedTimestamp({ chatJID: activeRoomJID, timestamp: Date.now() })
|
|
614
|
+
);
|
|
615
|
+
// Drop the "New messages" divider so it's gone when the user
|
|
616
|
+
// returns to the chat (mirrors ChatRoom's unmount cleanup).
|
|
617
|
+
store.dispatch(
|
|
618
|
+
deleteRoomMessage({ roomJID: activeRoomJID, messageId: 'delimiter-new' })
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
store.dispatch(clearVisibleRoom());
|
|
622
|
+
if (client?.flushLastViewedToPrivateStoreStanza) {
|
|
623
|
+
client
|
|
624
|
+
.flushLastViewedToPrivateStoreStanza(rooms, { visibleRoomJID: activeRoomJID })
|
|
625
|
+
.catch(() => {});
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}, [isVisible, client]);
|
|
629
|
+
|
|
630
|
+
// -----------------------------------------------------------
|
|
631
|
+
// NetInfo → proactive reconnect on network restore. Without this the
|
|
632
|
+
// only reconnect trigger is xmpp.js noticing the socket is dead, which
|
|
633
|
+
// on a Wi-Fi/cellular drop can hang on the TCP timeout for tens of
|
|
634
|
+
// seconds. NetInfo tells us the instant connectivity returns so we can
|
|
635
|
+
// force a reconnect immediately (and reset the backoff budget).
|
|
636
|
+
// -----------------------------------------------------------
|
|
637
|
+
useEffect(() => {
|
|
638
|
+
if (!client) {return;}
|
|
639
|
+
const unsub = NetInfo.addEventListener((state) => {
|
|
640
|
+
// Guard against undefined/partial state (jest mock, early emit).
|
|
641
|
+
const reachable =
|
|
642
|
+
!!state?.isConnected && state?.isInternetReachable !== false;
|
|
643
|
+
if (!reachable) {return;}
|
|
644
|
+
// Reachable. If we're in a dead state, kick a reconnect — do NOT
|
|
645
|
+
// require a prior offline→online transition (a missed/coalesced
|
|
646
|
+
// offline event otherwise left us stuck offline forever). Skip
|
|
647
|
+
// 'connecting' so we don't interrupt an in-progress connect; the
|
|
648
|
+
// watchdog covers a stuck 'connecting'. forceReconnect() debounces
|
|
649
|
+
// bursts and resets the backoff itself.
|
|
650
|
+
if (client.status === 'offline' || client.status === 'error') {
|
|
651
|
+
devPushLog('rn', `NetInfo: reachable & client ${client.status} → forceReconnect`);
|
|
652
|
+
client.forceReconnect();
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
return () => unsub();
|
|
656
|
+
}, [client]);
|
|
657
|
+
|
|
452
658
|
// -----------------------------------------------------------
|
|
453
659
|
// Retry-bootstrap listener — fired by ChatWrapper's error modal so
|
|
454
660
|
// a transient failure (bad endpoint, server hiccup, etc.) can be
|
|
@@ -479,7 +685,6 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
|
|
|
479
685
|
setClient(null);
|
|
480
686
|
completedBootstrapKeyRef.current = '';
|
|
481
687
|
inflightBootstrapKeyRef.current = '';
|
|
482
|
-
setReconnectAttempts(0);
|
|
483
688
|
setProviderBootstrapStatus('idle');
|
|
484
689
|
await clearPersistedState();
|
|
485
690
|
});
|
|
@@ -506,6 +711,18 @@ export const useXmppClient = () => {
|
|
|
506
711
|
if (!context) {
|
|
507
712
|
throw new Error('useXmppClient must be used within an XmppProvider');
|
|
508
713
|
}
|
|
714
|
+
if (!context.client) {
|
|
715
|
+
const candidates: Array<XmppClient | null | undefined> = [
|
|
716
|
+
getGlobalXmppClient(),
|
|
717
|
+
store.getState()?.chatSettingStore?.client as XmppClient | undefined,
|
|
718
|
+
];
|
|
719
|
+
const live = candidates.find(
|
|
720
|
+
(c) => c && (c as any).status === 'online'
|
|
721
|
+
);
|
|
722
|
+
if (live) {
|
|
723
|
+
return { ...context, client: live };
|
|
724
|
+
}
|
|
725
|
+
}
|
|
509
726
|
return context;
|
|
510
727
|
};
|
|
511
728
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { AppDispatch } from '../roomStore';
|
|
4
4
|
import { setCurrentRoom } from '../roomStore/roomsSlice';
|
|
5
|
+
import { normalizeRoomJid } from './normalizeRoomJid';
|
|
5
6
|
|
|
6
7
|
interface XmppConfig {
|
|
7
8
|
xmppSettings?: {
|
|
@@ -25,15 +26,8 @@ export const chatAutoEnterer = ({
|
|
|
25
26
|
console.log('🟡 chatAutoEnterer: Called', { roomJID, wasAutoSelected });
|
|
26
27
|
|
|
27
28
|
if (roomJID) {
|
|
28
|
-
// If roomJID is already a full JID, use it directly
|
|
29
|
-
// Otherwise, construct it from chatId if needed
|
|
30
29
|
const conferenceDomain = config.xmppSettings?.conference ?? '';
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (!roomJID.includes('@') && conferenceDomain) {
|
|
34
|
-
finalRoomJID = `${roomJID}@${conferenceDomain}`;
|
|
35
|
-
console.log('🟡 chatAutoEnterer: Constructed roomJID', finalRoomJID);
|
|
36
|
-
}
|
|
30
|
+
const finalRoomJID = normalizeRoomJid(roomJID, conferenceDomain);
|
|
37
31
|
|
|
38
32
|
console.log('🟡 chatAutoEnterer: Setting current room', finalRoomJID);
|
|
39
33
|
dispatch(setCurrentRoom({ roomJID: finalRoomJID }));
|
|
@@ -25,7 +25,10 @@ interface QueueItem {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const DEFAULT_CONCURRENCY = 3;
|
|
28
|
-
|
|
28
|
+
// Fetch the most recent ~20 on re-entry (mirrors web). A larger first page
|
|
29
|
+
// also means more id-overlap with cache, so the merge in
|
|
30
|
+
// `mergeHistoryIntoCache` rarely has to fall back to clear-and-replace.
|
|
31
|
+
const DEFAULT_PAGE_SIZE = 20;
|
|
29
32
|
const DEFAULT_RETRY_LIMIT = 2;
|
|
30
33
|
|
|
31
34
|
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -29,10 +29,21 @@ export function insertMessageWithDelimiter(
|
|
|
29
29
|
);
|
|
30
30
|
|
|
31
31
|
if (existingIndex !== -1) {
|
|
32
|
-
roomMessages[existingIndex]
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
)
|
|
32
|
+
const existing = roomMessages[existingIndex];
|
|
33
|
+
// Preserve the sender identity captured on first insert. The MUC echo
|
|
34
|
+
// parses user.id from the stanza's `from`/senderJID (the room nick or
|
|
35
|
+
// bare-jid local part), which often differs from the optimistic
|
|
36
|
+
// sender id (xmppUsername / walletAddress) set when WE sent it.
|
|
37
|
+
// deepMerge would overwrite it, flipping an own message to the "other"
|
|
38
|
+
// side (rendered on the left, "as if someone else wrote it"). Keep the
|
|
39
|
+
// original id so ownership (user.id === self) stays correct.
|
|
40
|
+
const preservedUserId =
|
|
41
|
+
(existing as any)?.user?.id || (message as any)?.user?.id;
|
|
42
|
+
const merged = deepMerge({ ...existing }, { ...message, pending: false });
|
|
43
|
+
if (merged.user && preservedUserId) {
|
|
44
|
+
merged.user.id = preservedUserId;
|
|
45
|
+
}
|
|
46
|
+
roomMessages[existingIndex] = merged;
|
|
36
47
|
return;
|
|
37
48
|
}
|
|
38
49
|
|
|
@@ -60,25 +71,21 @@ export function insertMessageWithDelimiter(
|
|
|
60
71
|
isDateAfter(msg.date?.toString() ?? '', lastViewedTimestamp.toString())
|
|
61
72
|
);
|
|
62
73
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
if (delimiterIndex !== -1) {
|
|
75
|
+
roomMessages.splice(delimiterIndex, 0, {
|
|
76
|
+
id: 'delimiter-new',
|
|
77
|
+
user: {
|
|
67
78
|
id: 'system',
|
|
68
79
|
name: undefined,
|
|
69
80
|
token: '',
|
|
70
81
|
refreshToken: '',
|
|
71
82
|
},
|
|
72
83
|
date: new Date().toString(),
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (lastViewedTimestamp.toString() === '0') {
|
|
78
|
-
roomMessages.splice(delimiterIndex, 1);
|
|
84
|
+
body: 'New Messages',
|
|
85
|
+
roomJid: '',
|
|
86
|
+
});
|
|
79
87
|
}
|
|
80
88
|
}
|
|
81
|
-
}
|
|
82
89
|
} else if (
|
|
83
90
|
isDateBefore(newMessageDate.toString(), firstMessage?.date?.toString() ?? '')
|
|
84
91
|
) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type KeyboardLayoutArgs = {
|
|
2
|
+
platform: string;
|
|
3
|
+
configuredOffset?: number;
|
|
4
|
+
bottomInset?: number;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const getKeyboardVerticalOffset = ({
|
|
8
|
+
platform,
|
|
9
|
+
configuredOffset = 0,
|
|
10
|
+
bottomInset = 0,
|
|
11
|
+
}: KeyboardLayoutArgs): number =>
|
|
12
|
+
configuredOffset + (platform === 'ios' ? bottomInset : 0);
|
|
13
|
+
|
|
14
|
+
export const getInputDockPaddingBottom = ({
|
|
15
|
+
platform,
|
|
16
|
+
bottomInset = 0,
|
|
17
|
+
}: Omit<KeyboardLayoutArgs, 'configuredOffset'>): number =>
|
|
18
|
+
platform === 'ios' ? bottomInset : 0;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Dimensions } from 'react-native';
|
|
2
|
+
|
|
3
|
+
// Shared sizing for inline media bubbles (images + video posters).
|
|
4
|
+
// Goal: show the media UNCROPPED, just scaled, preserving aspect ratio —
|
|
5
|
+
// portrait stays portrait, landscape stays landscape.
|
|
6
|
+
const screenW = Dimensions.get('window').width;
|
|
7
|
+
|
|
8
|
+
// Box the media must fit inside. Width adapts to the screen so it never
|
|
9
|
+
// blows past the bubble's max-width on small phones, but caps on tablets.
|
|
10
|
+
export const MEDIA_MAX_W = Math.min(260, Math.round(screenW * 0.64));
|
|
11
|
+
export const MEDIA_MAX_H = 320;
|
|
12
|
+
// Keep thumbnails from collapsing into a sliver (very wide/tall media).
|
|
13
|
+
const MEDIA_MIN_W = 140;
|
|
14
|
+
const MEDIA_MIN_H = 120;
|
|
15
|
+
|
|
16
|
+
export interface MediaDims {
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Neutral 4:3 placeholder used before the natural size is known.
|
|
22
|
+
export const defaultMediaDims = (): MediaDims => ({
|
|
23
|
+
width: MEDIA_MAX_W,
|
|
24
|
+
height: Math.round(MEDIA_MAX_W * 0.75),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Fit (natW × natH) inside the box, preserving aspect ratio. Never
|
|
28
|
+
// upscales beyond the source resolution (avoids blur), but bumps very
|
|
29
|
+
// small media up to a sensible minimum so it stays tappable.
|
|
30
|
+
export const fitMediaDimensions = (natW: number, natH: number): MediaDims => {
|
|
31
|
+
if (!natW || !natH) {
|
|
32
|
+
return defaultMediaDims();
|
|
33
|
+
}
|
|
34
|
+
let scale = Math.min(MEDIA_MAX_W / natW, MEDIA_MAX_H / natH);
|
|
35
|
+
if (scale > 1) {
|
|
36
|
+
scale = 1;
|
|
37
|
+
}
|
|
38
|
+
let width = Math.round(natW * scale);
|
|
39
|
+
let height = Math.round(natH * scale);
|
|
40
|
+
|
|
41
|
+
if (width < MEDIA_MIN_W || height < MEDIA_MIN_H) {
|
|
42
|
+
const up = Math.max(MEDIA_MIN_W / width, MEDIA_MIN_H / height);
|
|
43
|
+
width = Math.min(MEDIA_MAX_W, Math.round(width * up));
|
|
44
|
+
height = Math.min(MEDIA_MAX_H, Math.round(height * up));
|
|
45
|
+
}
|
|
46
|
+
return { width, height };
|
|
47
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import { useDispatch } from 'react-redux';
|
|
3
|
+
import {
|
|
4
|
+
clearVisibleRoom,
|
|
5
|
+
setCurrentRoom,
|
|
6
|
+
setLastViewedTimestamp,
|
|
7
|
+
setVisibleRoom,
|
|
8
|
+
} from '../roomStore/roomsSlice';
|
|
9
|
+
import { store } from '../roomStore';
|
|
10
|
+
|
|
11
|
+
interface UseChatRoomFocusOptions {
|
|
12
|
+
/** The room JID that the consumer's tab/screen is currently showing. */
|
|
13
|
+
roomJID: string | null | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Whether the chat tab/screen is currently focused. In tab-based
|
|
16
|
+
* navigators where `<ChatRoom>` never unmounts, the SDK can't tell
|
|
17
|
+
* "user is looking at this tab" from "user is on a different tab"
|
|
18
|
+
* — pass this from `useFocusEffect` (React Navigation) or a similar
|
|
19
|
+
* focus signal so the unread counter clears on focus and starts
|
|
20
|
+
* counting again on blur.
|
|
21
|
+
*/
|
|
22
|
+
isFocused: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Public hook for consumers embedding `<ChatRoom>` inside a tab-based
|
|
27
|
+
* navigator. Mirrors the mount/unmount unread-tracking that ChatRoom
|
|
28
|
+
* does internally — but driven by an explicit focus signal so the
|
|
29
|
+
* unread counter behaves correctly when the chat tab is not focused
|
|
30
|
+
* but the component is still mounted.
|
|
31
|
+
*
|
|
32
|
+
* Without this hook, `useUnread()` will always return 0 for the chat
|
|
33
|
+
* room because the SDK assumes "mounted == active". With this hook,
|
|
34
|
+
* focus marks the room visible (clearing the badge) and blur stamps
|
|
35
|
+
* `lastViewedTimestamp = Date.now()` so future messages count as unread.
|
|
36
|
+
*
|
|
37
|
+
* Usage with React Navigation:
|
|
38
|
+
*
|
|
39
|
+
* ```tsx
|
|
40
|
+
* import { useIsFocused } from '@react-navigation/native';
|
|
41
|
+
*
|
|
42
|
+
* function ChatTab() {
|
|
43
|
+
* const isFocused = useIsFocused();
|
|
44
|
+
* useChatRoomFocus({ roomJID: 'general@conference.host', isFocused });
|
|
45
|
+
* return <Chat config={...} />;
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* Bug #18-adjacent / unread-tracking bug from sdk-bug-tracker.md.
|
|
50
|
+
*/
|
|
51
|
+
export const useChatRoomFocus = ({
|
|
52
|
+
roomJID,
|
|
53
|
+
isFocused,
|
|
54
|
+
}: UseChatRoomFocusOptions) => {
|
|
55
|
+
const dispatch = useDispatch();
|
|
56
|
+
const prevRef = useRef<{ roomJID: string | null; isFocused: boolean }>({
|
|
57
|
+
roomJID: null,
|
|
58
|
+
isFocused: false,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const leaveRoom = (jid: string) => {
|
|
62
|
+
const timestamp = Date.now();
|
|
63
|
+
dispatch(setLastViewedTimestamp({ chatJID: jid, timestamp }));
|
|
64
|
+
dispatch(clearVisibleRoom());
|
|
65
|
+
const rooms = store.getState().rooms?.rooms;
|
|
66
|
+
(store.getState().chatSettingStore as any)?.client
|
|
67
|
+
?.flushLastViewedToPrivateStoreStanza(rooms, { visibleRoomJID: jid })
|
|
68
|
+
.catch(() => {});
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
const prev = prevRef.current;
|
|
73
|
+
if (prev.isFocused && prev.roomJID && (prev.roomJID !== roomJID || !isFocused)) {
|
|
74
|
+
leaveRoom(prev.roomJID);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (roomJID && isFocused) {
|
|
78
|
+
dispatch(setCurrentRoom({ roomJID }));
|
|
79
|
+
dispatch(setVisibleRoom({ roomJID }));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
prevRef.current = { roomJID: roomJID || null, isFocused };
|
|
83
|
+
}, [roomJID, isFocused, dispatch]);
|
|
84
|
+
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
return () => {
|
|
87
|
+
const prev = prevRef.current;
|
|
88
|
+
if (prev.isFocused && prev.roomJID) {
|
|
89
|
+
leaveRoom(prev.roomJID);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}, []);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export default useChatRoomFocus;
|