@ethora/chat-component-rn 26.5.21 → 26.6.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 +80 -0
- package/README.md +3 -3
- package/index.js +1 -1
- package/lib/commonjs/components/MainComponents/ChatHeader.js +20 -8
- package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
- package/lib/commonjs/components/MainComponents/ChatWrapper.js +17 -0
- package/lib/commonjs/components/MainComponents/ChatWrapper.js.map +1 -1
- package/lib/commonjs/components/MainComponents/LanguageSelectorButton.js +62 -0
- package/lib/commonjs/components/MainComponents/LanguageSelectorButton.js.map +1 -0
- package/lib/commonjs/components/MainComponents/LanguageSelectorModal.js +253 -0
- package/lib/commonjs/components/MainComponents/LanguageSelectorModal.js.map +1 -0
- package/lib/commonjs/components/MainComponents/RoomList.js +23 -9
- package/lib/commonjs/components/MainComponents/RoomList.js.map +1 -1
- package/lib/commonjs/components/MessageBubble/Message.js +72 -11
- package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
- package/lib/commonjs/components/MessageBubble/MessageTranslate.js +160 -0
- package/lib/commonjs/components/MessageBubble/MessageTranslate.js.map +1 -0
- package/lib/commonjs/components/MessageBubble/TranslatedMessageBody.js +59 -0
- package/lib/commonjs/components/MessageBubble/TranslatedMessageBody.js.map +1 -0
- package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +3 -3
- package/lib/commonjs/components/UsersList/UsersList.js +3 -1
- package/lib/commonjs/components/UsersList/UsersList.js.map +1 -1
- package/lib/commonjs/components/VideoCalls/CallButtons.js +159 -0
- package/lib/commonjs/components/VideoCalls/CallButtons.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/CallIcons.js +328 -0
- package/lib/commonjs/components/VideoCalls/CallIcons.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js +550 -0
- package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/VideoCallSession.js +636 -0
- package/lib/commonjs/components/VideoCalls/VideoCallSession.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/livekitRuntime.js +91 -0
- package/lib/commonjs/components/VideoCalls/livekitRuntime.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/useCallKeep.js +215 -0
- package/lib/commonjs/components/VideoCalls/useCallKeep.js.map +1 -0
- package/lib/commonjs/components/styled/AudioMessage.js +91 -33
- package/lib/commonjs/components/styled/AudioMessage.js.map +1 -1
- package/lib/commonjs/components/styled/RoomListComponents/index.js +13 -1
- package/lib/commonjs/components/styled/RoomListComponents/index.js.map +1 -1
- package/lib/commonjs/components/styled/SendInput.js +83 -49
- package/lib/commonjs/components/styled/SendInput.js.map +1 -1
- package/lib/commonjs/components/styled/StyledComponents.js +9 -4
- package/lib/commonjs/components/styled/StyledComponents.js.map +1 -1
- package/lib/commonjs/components/styled/StyledInputComponents/Composing.js +2 -0
- package/lib/commonjs/components/styled/StyledInputComponents/Composing.js.map +1 -1
- package/lib/commonjs/context/xmppProvider.js +10 -3
- package/lib/commonjs/context/xmppProvider.js.map +1 -1
- package/lib/commonjs/helpers/callLogMessage.js +162 -0
- package/lib/commonjs/helpers/callLogMessage.js.map +1 -0
- package/lib/commonjs/helpers/callPush.js +61 -0
- package/lib/commonjs/helpers/callPush.js.map +1 -0
- package/lib/commonjs/helpers/constants/LANGUAGE_OPTIONS.js +24 -12
- package/lib/commonjs/helpers/constants/LANGUAGE_OPTIONS.js.map +1 -1
- package/lib/commonjs/helpers/createUserNameFromSetUser.js +51 -2
- package/lib/commonjs/helpers/createUserNameFromSetUser.js.map +1 -1
- package/lib/commonjs/helpers/getDataFromXml.js +19 -1
- package/lib/commonjs/helpers/getDataFromXml.js.map +1 -1
- package/lib/commonjs/helpers/incomingCallFromPush.js +74 -0
- package/lib/commonjs/helpers/incomingCallFromPush.js.map +1 -0
- package/lib/commonjs/helpers/insertMessageWithDelimiter.js +13 -1
- package/lib/commonjs/helpers/insertMessageWithDelimiter.js.map +1 -1
- package/lib/commonjs/helpers/isUnresolvedSenderId.js +32 -0
- package/lib/commonjs/helpers/isUnresolvedSenderId.js.map +1 -0
- package/lib/commonjs/hooks/useChatSettingState.js +2 -0
- package/lib/commonjs/hooks/useChatSettingState.js.map +1 -1
- package/lib/commonjs/hooks/useMessageTranslation.js +51 -0
- package/lib/commonjs/hooks/useMessageTranslation.js.map +1 -0
- package/lib/commonjs/hooks/usePushNotifications.js +13 -3
- package/lib/commonjs/hooks/usePushNotifications.js.map +1 -1
- package/lib/commonjs/hooks/useSendMessage.js +12 -2
- package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
- package/lib/commonjs/i18n/strings.js +1308 -0
- package/lib/commonjs/i18n/strings.js.map +1 -0
- package/lib/commonjs/i18n/useT.js +60 -0
- package/lib/commonjs/i18n/useT.js.map +1 -0
- package/lib/commonjs/main.js +44 -0
- package/lib/commonjs/main.js.map +1 -1
- package/lib/commonjs/networking/api-requests/push.api.js +34 -5
- package/lib/commonjs/networking/api-requests/push.api.js.map +1 -1
- package/lib/commonjs/networking/api-requests/rooms.api.js +108 -0
- package/lib/commonjs/networking/api-requests/rooms.api.js.map +1 -1
- package/lib/commonjs/networking/callTokenStanza.js +531 -0
- package/lib/commonjs/networking/callTokenStanza.js.map +1 -0
- package/lib/commonjs/networking/outboundQueue.js.map +1 -1
- package/lib/commonjs/networking/stanzaHandlers.js +75 -8
- package/lib/commonjs/networking/stanzaHandlers.js.map +1 -1
- package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js +47 -11
- package/lib/commonjs/networking/xmpp/handleStanzas.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/xmpp/sendTextMessageWithTranslateTag.xmpp.js +42 -5
- package/lib/commonjs/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.js.map +1 -1
- package/lib/commonjs/networking/xmppClient.js +78 -16
- package/lib/commonjs/networking/xmppClient.js.map +1 -1
- package/lib/commonjs/roomStore/callSlice.js +144 -0
- package/lib/commonjs/roomStore/callSlice.js.map +1 -0
- package/lib/commonjs/roomStore/chatSettingsSlice.js +8 -2
- package/lib/commonjs/roomStore/chatSettingsSlice.js.map +1 -1
- package/lib/commonjs/roomStore/index.js +7 -1
- package/lib/commonjs/roomStore/index.js.map +1 -1
- package/lib/commonjs/roomStore/persistence.js +69 -5
- package/lib/commonjs/roomStore/persistence.js.map +1 -1
- package/lib/commonjs/roomStore/roomsSlice.js +136 -6
- package/lib/commonjs/roomStore/roomsSlice.js.map +1 -1
- package/lib/commonjs/utils/translateModePolicy.js +33 -0
- package/lib/commonjs/utils/translateModePolicy.js.map +1 -0
- package/lib/module/components/MainComponents/ChatHeader.js +20 -8
- package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
- package/lib/module/components/MainComponents/ChatWrapper.js +17 -0
- package/lib/module/components/MainComponents/ChatWrapper.js.map +1 -1
- package/lib/module/components/MainComponents/LanguageSelectorButton.js +57 -0
- package/lib/module/components/MainComponents/LanguageSelectorButton.js.map +1 -0
- package/lib/module/components/MainComponents/LanguageSelectorModal.js +246 -0
- package/lib/module/components/MainComponents/LanguageSelectorModal.js.map +1 -0
- package/lib/module/components/MainComponents/RoomList.js +23 -9
- package/lib/module/components/MainComponents/RoomList.js.map +1 -1
- package/lib/module/components/MessageBubble/Message.js +73 -12
- package/lib/module/components/MessageBubble/Message.js.map +1 -1
- package/lib/module/components/MessageBubble/MessageTranslate.js +155 -0
- package/lib/module/components/MessageBubble/MessageTranslate.js.map +1 -0
- package/lib/module/components/MessageBubble/TranslatedMessageBody.js +53 -0
- package/lib/module/components/MessageBubble/TranslatedMessageBody.js.map +1 -0
- package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +3 -3
- package/lib/module/components/UsersList/UsersList.js +3 -1
- package/lib/module/components/UsersList/UsersList.js.map +1 -1
- package/lib/module/components/VideoCalls/CallButtons.js +154 -0
- package/lib/module/components/VideoCalls/CallButtons.js.map +1 -0
- package/lib/module/components/VideoCalls/CallIcons.js +309 -0
- package/lib/module/components/VideoCalls/CallIcons.js.map +1 -0
- package/lib/module/components/VideoCalls/VideoCallOverlay.js +544 -0
- package/lib/module/components/VideoCalls/VideoCallOverlay.js.map +1 -0
- package/lib/module/components/VideoCalls/VideoCallSession.js +630 -0
- package/lib/module/components/VideoCalls/VideoCallSession.js.map +1 -0
- package/lib/module/components/VideoCalls/livekitRuntime.js +83 -0
- package/lib/module/components/VideoCalls/livekitRuntime.js.map +1 -0
- package/lib/module/components/VideoCalls/useCallKeep.js +210 -0
- package/lib/module/components/VideoCalls/useCallKeep.js.map +1 -0
- package/lib/module/components/styled/AudioMessage.js +91 -33
- package/lib/module/components/styled/AudioMessage.js.map +1 -1
- package/lib/module/components/styled/RoomListComponents/index.js +13 -1
- package/lib/module/components/styled/RoomListComponents/index.js.map +1 -1
- package/lib/module/components/styled/SendInput.js +83 -49
- package/lib/module/components/styled/SendInput.js.map +1 -1
- package/lib/module/components/styled/StyledComponents.js +9 -4
- package/lib/module/components/styled/StyledComponents.js.map +1 -1
- package/lib/module/components/styled/StyledInputComponents/Composing.js +2 -0
- package/lib/module/components/styled/StyledInputComponents/Composing.js.map +1 -1
- package/lib/module/context/xmppProvider.js +11 -4
- package/lib/module/context/xmppProvider.js.map +1 -1
- package/lib/module/helpers/callLogMessage.js +152 -0
- package/lib/module/helpers/callLogMessage.js.map +1 -0
- package/lib/module/helpers/callPush.js +55 -0
- package/lib/module/helpers/callPush.js.map +1 -0
- package/lib/module/helpers/constants/LANGUAGE_OPTIONS.js +24 -12
- package/lib/module/helpers/constants/LANGUAGE_OPTIONS.js.map +1 -1
- package/lib/module/helpers/createUserNameFromSetUser.js +49 -1
- package/lib/module/helpers/createUserNameFromSetUser.js.map +1 -1
- package/lib/module/helpers/getDataFromXml.js +20 -1
- package/lib/module/helpers/getDataFromXml.js.map +1 -1
- package/lib/module/helpers/incomingCallFromPush.js +70 -0
- package/lib/module/helpers/incomingCallFromPush.js.map +1 -0
- package/lib/module/helpers/insertMessageWithDelimiter.js +13 -1
- package/lib/module/helpers/insertMessageWithDelimiter.js.map +1 -1
- package/lib/module/helpers/isUnresolvedSenderId.js +27 -0
- package/lib/module/helpers/isUnresolvedSenderId.js.map +1 -0
- package/lib/module/hooks/useChatSettingState.js +2 -0
- package/lib/module/hooks/useChatSettingState.js.map +1 -1
- package/lib/module/hooks/useMessageTranslation.js +46 -0
- package/lib/module/hooks/useMessageTranslation.js.map +1 -0
- package/lib/module/hooks/usePushNotifications.js +13 -3
- package/lib/module/hooks/usePushNotifications.js.map +1 -1
- package/lib/module/hooks/useSendMessage.js +12 -2
- package/lib/module/hooks/useSendMessage.js.map +1 -1
- package/lib/module/i18n/strings.js +1300 -0
- package/lib/module/i18n/strings.js.map +1 -0
- package/lib/module/i18n/useT.js +55 -0
- package/lib/module/i18n/useT.js.map +1 -0
- package/lib/module/main.js +7 -1
- package/lib/module/main.js.map +1 -1
- package/lib/module/networking/api-requests/push.api.js +35 -5
- package/lib/module/networking/api-requests/push.api.js.map +1 -1
- package/lib/module/networking/api-requests/rooms.api.js +109 -1
- package/lib/module/networking/api-requests/rooms.api.js.map +1 -1
- package/lib/module/networking/callTokenStanza.js +526 -0
- package/lib/module/networking/callTokenStanza.js.map +1 -0
- package/lib/module/networking/outboundQueue.js.map +1 -1
- package/lib/module/networking/stanzaHandlers.js +76 -9
- package/lib/module/networking/stanzaHandlers.js.map +1 -1
- package/lib/module/networking/xmpp/handleStanzas.xmpp.js +48 -11
- package/lib/module/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
- package/lib/module/networking/xmpp/sendTextMessage.xmpp.js +1 -1
- package/lib/module/networking/xmpp/sendTextMessage.xmpp.js.map +1 -1
- package/lib/module/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.js +43 -5
- package/lib/module/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.js.map +1 -1
- package/lib/module/networking/xmppClient.js +78 -16
- package/lib/module/networking/xmppClient.js.map +1 -1
- package/lib/module/roomStore/callSlice.js +130 -0
- package/lib/module/roomStore/callSlice.js.map +1 -0
- package/lib/module/roomStore/chatSettingsSlice.js +6 -1
- package/lib/module/roomStore/chatSettingsSlice.js.map +1 -1
- package/lib/module/roomStore/index.js +6 -1
- package/lib/module/roomStore/index.js.map +1 -1
- package/lib/module/roomStore/persistence.js +69 -5
- package/lib/module/roomStore/persistence.js.map +1 -1
- package/lib/module/roomStore/roomsSlice.js +134 -5
- package/lib/module/roomStore/roomsSlice.js.map +1 -1
- package/lib/module/utils/translateModePolicy.js +27 -0
- package/lib/module/utils/translateModePolicy.js.map +1 -0
- package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/ChatWrapper.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/LanguageSelectorButton.d.ts +12 -0
- package/lib/typescript/components/MainComponents/LanguageSelectorButton.d.ts.map +1 -0
- package/lib/typescript/components/MainComponents/LanguageSelectorModal.d.ts +31 -0
- package/lib/typescript/components/MainComponents/LanguageSelectorModal.d.ts.map +1 -0
- package/lib/typescript/components/MainComponents/RoomList.d.ts.map +1 -1
- package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
- package/lib/typescript/components/MessageBubble/MessageTranslate.d.ts +44 -0
- package/lib/typescript/components/MessageBubble/MessageTranslate.d.ts.map +1 -0
- package/lib/typescript/components/MessageBubble/TranslatedMessageBody.d.ts +24 -0
- package/lib/typescript/components/MessageBubble/TranslatedMessageBody.d.ts.map +1 -0
- package/lib/typescript/components/UsersList/UsersList.d.ts.map +1 -1
- package/lib/typescript/components/VideoCalls/CallButtons.d.ts +16 -0
- package/lib/typescript/components/VideoCalls/CallButtons.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/CallIcons.d.ts +30 -0
- package/lib/typescript/components/VideoCalls/CallIcons.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/VideoCallOverlay.d.ts +10 -0
- package/lib/typescript/components/VideoCalls/VideoCallOverlay.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/VideoCallSession.d.ts +38 -0
- package/lib/typescript/components/VideoCalls/VideoCallSession.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/livekitRuntime.d.ts +52 -0
- package/lib/typescript/components/VideoCalls/livekitRuntime.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/useCallKeep.d.ts +32 -0
- package/lib/typescript/components/VideoCalls/useCallKeep.d.ts.map +1 -0
- package/lib/typescript/components/styled/AudioMessage.d.ts.map +1 -1
- package/lib/typescript/components/styled/RoomListComponents/index.d.ts.map +1 -1
- package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
- package/lib/typescript/components/styled/StyledComponents.d.ts.map +1 -1
- package/lib/typescript/components/styled/StyledInputComponents/Composing.d.ts.map +1 -1
- package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
- package/lib/typescript/helpers/callLogMessage.d.ts +34 -0
- package/lib/typescript/helpers/callLogMessage.d.ts.map +1 -0
- package/lib/typescript/helpers/callPush.d.ts +23 -0
- package/lib/typescript/helpers/callPush.d.ts.map +1 -0
- package/lib/typescript/helpers/constants/LANGUAGE_OPTIONS.d.ts +14 -0
- package/lib/typescript/helpers/constants/LANGUAGE_OPTIONS.d.ts.map +1 -1
- package/lib/typescript/helpers/createUserNameFromSetUser.d.ts +33 -1
- package/lib/typescript/helpers/createUserNameFromSetUser.d.ts.map +1 -1
- package/lib/typescript/helpers/getDataFromXml.d.ts.map +1 -1
- package/lib/typescript/helpers/incomingCallFromPush.d.ts +14 -0
- package/lib/typescript/helpers/incomingCallFromPush.d.ts.map +1 -0
- package/lib/typescript/helpers/insertMessageWithDelimiter.d.ts.map +1 -1
- package/lib/typescript/helpers/isUnresolvedSenderId.d.ts +2 -0
- package/lib/typescript/helpers/isUnresolvedSenderId.d.ts.map +1 -0
- package/lib/typescript/hooks/useChatSettingState.d.ts +2 -1
- package/lib/typescript/hooks/useChatSettingState.d.ts.map +1 -1
- package/lib/typescript/hooks/useMessageTranslation.d.ts +25 -0
- package/lib/typescript/hooks/useMessageTranslation.d.ts.map +1 -0
- package/lib/typescript/hooks/usePushNotifications.d.ts.map +1 -1
- package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
- package/lib/typescript/i18n/strings.d.ts +16 -0
- package/lib/typescript/i18n/strings.d.ts.map +1 -0
- package/lib/typescript/i18n/useT.d.ts +34 -0
- package/lib/typescript/i18n/useT.d.ts.map +1 -0
- package/lib/typescript/main.d.ts +6 -1
- package/lib/typescript/main.d.ts.map +1 -1
- package/lib/typescript/networking/api-requests/push.api.d.ts.map +1 -1
- package/lib/typescript/networking/api-requests/rooms.api.d.ts +3 -0
- package/lib/typescript/networking/api-requests/rooms.api.d.ts.map +1 -1
- package/lib/typescript/networking/callTokenStanza.d.ts +14 -0
- package/lib/typescript/networking/callTokenStanza.d.ts.map +1 -0
- package/lib/typescript/networking/outboundQueue.d.ts +1 -0
- package/lib/typescript/networking/outboundQueue.d.ts.map +1 -1
- 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/sendTextMessage.xmpp.d.ts +10 -0
- package/lib/typescript/networking/xmpp/sendTextMessage.xmpp.d.ts.map +1 -1
- package/lib/typescript/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.d.ts +16 -1
- package/lib/typescript/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.d.ts.map +1 -1
- package/lib/typescript/networking/xmppClient.d.ts +1 -1
- package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
- package/lib/typescript/roomStore/callSlice.d.ts +39 -0
- package/lib/typescript/roomStore/callSlice.d.ts.map +1 -0
- package/lib/typescript/roomStore/chatSettingsSlice.d.ts +10 -1
- package/lib/typescript/roomStore/chatSettingsSlice.d.ts.map +1 -1
- package/lib/typescript/roomStore/index.d.ts +4 -0
- package/lib/typescript/roomStore/index.d.ts.map +1 -1
- package/lib/typescript/roomStore/persistence.d.ts.map +1 -1
- package/lib/typescript/roomStore/roomsSlice.d.ts +6 -1
- package/lib/typescript/roomStore/roomsSlice.d.ts.map +1 -1
- package/lib/typescript/types/models/language.model.d.ts +1 -1
- package/lib/typescript/types/models/language.model.d.ts.map +1 -1
- package/lib/typescript/types/models/message.model.d.ts +7 -0
- package/lib/typescript/types/models/message.model.d.ts.map +1 -1
- package/lib/typescript/types/types.d.ts +135 -1
- package/lib/typescript/types/types.d.ts.map +1 -1
- package/lib/typescript/utils/translateModePolicy.d.ts +20 -0
- package/lib/typescript/utils/translateModePolicy.d.ts.map +1 -0
- package/package.json +20 -4
- package/src/components/MainComponents/ChatHeader.tsx +30 -9
- package/src/components/MainComponents/ChatWrapper.tsx +15 -0
- package/src/components/MainComponents/LanguageSelectorButton.tsx +53 -0
- package/src/components/MainComponents/LanguageSelectorModal.tsx +276 -0
- package/src/components/MainComponents/RoomList.tsx +27 -9
- package/src/components/MessageBubble/Message.tsx +109 -17
- package/src/components/MessageBubble/MessageTranslate.tsx +182 -0
- package/src/components/MessageBubble/TranslatedMessageBody.tsx +59 -0
- package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +3 -3
- package/src/components/UsersList/UsersList.tsx +3 -1
- package/src/components/VideoCalls/CallButtons.tsx +171 -0
- package/src/components/VideoCalls/CallIcons.tsx +246 -0
- package/src/components/VideoCalls/VideoCallOverlay.tsx +627 -0
- package/src/components/VideoCalls/VideoCallSession.tsx +675 -0
- package/src/components/VideoCalls/livekitRuntime.ts +100 -0
- package/src/components/VideoCalls/useCallKeep.ts +252 -0
- package/src/components/styled/AudioMessage.tsx +97 -37
- package/src/components/styled/RoomListComponents/index.tsx +15 -3
- package/src/components/styled/SendInput.tsx +94 -51
- package/src/components/styled/StyledComponents.ts +9 -6
- package/src/components/styled/StyledInputComponents/Composing.tsx +7 -1
- package/src/context/xmppProvider.tsx +14 -1
- package/src/helpers/callLogMessage.ts +174 -0
- package/src/helpers/callPush.ts +90 -0
- package/src/helpers/constants/LANGUAGE_OPTIONS.ts +20 -5
- package/src/helpers/createUserNameFromSetUser.ts +68 -2
- package/src/helpers/getDataFromXml.ts +40 -4
- package/src/helpers/incomingCallFromPush.ts +83 -0
- package/src/helpers/insertMessageWithDelimiter.ts +27 -9
- package/src/helpers/isUnresolvedSenderId.ts +25 -0
- package/src/hooks/useChatSettingState.tsx +4 -0
- package/src/hooks/useMessageTranslation.ts +70 -0
- package/src/hooks/usePushNotifications.ts +15 -3
- package/src/hooks/useSendMessage.tsx +15 -2
- package/src/i18n/strings.ts +1322 -0
- package/src/i18n/useT.ts +56 -0
- package/src/main.ts +18 -1
- package/src/networking/api-requests/push.api.ts +39 -8
- package/src/networking/api-requests/rooms.api.ts +109 -1
- package/src/networking/callTokenStanza.ts +636 -0
- package/src/networking/outboundQueue.ts +1 -0
- package/src/networking/stanzaHandlers.ts +74 -15
- package/src/networking/xmpp/handleStanzas.xmpp.ts +52 -11
- package/src/networking/xmpp/sendTextMessage.xmpp.ts +1 -1
- package/src/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.ts +41 -5
- package/src/networking/xmppClient.ts +97 -28
- package/src/roomStore/callSlice.ts +190 -0
- package/src/roomStore/chatSettingsSlice.ts +16 -0
- package/src/roomStore/index.ts +5 -0
- package/src/roomStore/persistence.ts +103 -3
- package/src/roomStore/roomsSlice.ts +149 -4
- package/src/types/models/language.model.ts +17 -1
- package/src/types/models/message.model.ts +9 -0
- package/src/types/types.ts +142 -2
- package/src/utils/translateModePolicy.ts +31 -0
|
@@ -14,12 +14,20 @@ import AttachSheet from '../Modals/AttachSheet/AttachSheet';
|
|
|
14
14
|
import { MediaFilePreview } from './MediaFilePreview';
|
|
15
15
|
import { getIconColor } from '../../helpers/getIconColor';
|
|
16
16
|
import { getElementFont } from '../../helpers/getElementFont';
|
|
17
|
+
import { useT } from '../../i18n/useT';
|
|
17
18
|
import * as ImagePicker from 'expo-image-picker';
|
|
18
19
|
import * as DocumentPicker from 'expo-document-picker';
|
|
19
20
|
import * as ImageManipulator from 'expo-image-manipulator';
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
|
|
21
|
+
// expo-audio replaces the discontinued expo-av: `useAudioRecorder` owns a
|
|
22
|
+
// single reusable recorder for this input's lifetime (prepare → record →
|
|
23
|
+
// stop → prepare again), and the module-level helpers cover permissions
|
|
24
|
+
// and the iOS audio session.
|
|
25
|
+
import {
|
|
26
|
+
useAudioRecorder,
|
|
27
|
+
setAudioModeAsync,
|
|
28
|
+
requestRecordingPermissionsAsync,
|
|
29
|
+
RecordingPresets,
|
|
30
|
+
} from 'expo-audio';
|
|
23
31
|
|
|
24
32
|
// iOS photos default to HEIC; many web backends (incl. ours) 500 on
|
|
25
33
|
// HEIC uploads because they can't decode it. Convert to JPEG before
|
|
@@ -88,6 +96,7 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
88
96
|
isLoading,
|
|
89
97
|
isMessageProcessing,
|
|
90
98
|
}) => {
|
|
99
|
+
const t = useT();
|
|
91
100
|
const [message, setMessage] = useState('');
|
|
92
101
|
const [isRecording, setIsRecording] = useState(false);
|
|
93
102
|
// Live counter (seconds) for the recording overlay. Updated by a
|
|
@@ -97,14 +106,20 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
97
106
|
const [filePreviews, setFilePreviews] = useState<MediaFile[]>([]);
|
|
98
107
|
const [showMediaMenu, setShowMediaMenu] = useState(false);
|
|
99
108
|
|
|
100
|
-
// expo-
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
|
|
109
|
+
// expo-audio's recorder is a long-lived native object owned by the hook
|
|
110
|
+
// (released automatically on unmount) and reused across recordings, so
|
|
111
|
+
// unlike expo-av's one-shot `Audio.Recording` there is no per-recording
|
|
112
|
+
// instance to hold. The hook has to run unconditionally, but the
|
|
113
|
+
// constructor is inert on both platforms — iOS allocates a bare
|
|
114
|
+
// AVAudioRecorder (no file), Android leaves its MediaRecorder null.
|
|
115
|
+
// Nothing claims the mic, writes a file, or switches the iOS session to
|
|
116
|
+
// playAndRecord until `prepareToRecordAsync`, so consumers with
|
|
117
|
+
// `enableAudio` off carry an idle object and nothing more.
|
|
118
|
+
const audioRecorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY);
|
|
119
|
+
// Tracks whether a recording is actually in flight, so cancel/stop and
|
|
120
|
+
// the unmount teardown can tell "never started" from "needs stopping"
|
|
121
|
+
// without reading native state.
|
|
122
|
+
const isRecordingRef = useRef(false);
|
|
108
123
|
const recordingTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
109
124
|
|
|
110
125
|
// Refs shadow `message` and `filePreviews` so handleSendClick can:
|
|
@@ -290,7 +305,7 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
290
305
|
//
|
|
291
306
|
// Mirrors the web experience: tap mic → start recording → either tap
|
|
292
307
|
// X to discard, or tap send to stop + immediately upload + post the
|
|
293
|
-
// voice message. Uses expo-
|
|
308
|
+
// voice message. Uses expo-audio's AudioRecorder (already a peer dep
|
|
294
309
|
// for AudioMessage playback). Filename is `voice-<timestamp>.m4a` with
|
|
295
310
|
// mimetype `audio/m4a` so the receiver routes through the audio
|
|
296
311
|
// branch in MediaMessage directly, no octet-stream sniffing needed.
|
|
@@ -306,9 +321,9 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
306
321
|
// playback elsewhere in the app isn't routed to the earpiece.
|
|
307
322
|
const restoreAudioMode = async () => {
|
|
308
323
|
try {
|
|
309
|
-
await
|
|
310
|
-
|
|
311
|
-
|
|
324
|
+
await setAudioModeAsync({
|
|
325
|
+
allowsRecording: false,
|
|
326
|
+
playsInSilentMode: true,
|
|
312
327
|
});
|
|
313
328
|
} catch {
|
|
314
329
|
/* non-fatal */
|
|
@@ -316,27 +331,30 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
316
331
|
};
|
|
317
332
|
|
|
318
333
|
const handleStartRecording = async () => {
|
|
319
|
-
if (isRecording ||
|
|
334
|
+
if (isRecording || isRecordingRef.current) {return;}
|
|
320
335
|
try {
|
|
321
|
-
const perm = await
|
|
336
|
+
const perm = await requestRecordingPermissionsAsync();
|
|
322
337
|
if (!perm.granted) {
|
|
323
338
|
promptOpenSettings(
|
|
324
339
|
'Microphone permission is needed to record voice messages.'
|
|
325
340
|
);
|
|
326
341
|
return;
|
|
327
342
|
}
|
|
328
|
-
// iOS
|
|
329
|
-
//
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
343
|
+
// `allowsRecording` is not just an iOS session flag: expo-audio's
|
|
344
|
+
// recorder throws RecordingDisabledException from record() when the
|
|
345
|
+
// audio mode has it off. Flipped back in restoreAudioMode after
|
|
346
|
+
// stop/cancel so playback elsewhere isn't stuck on the earpiece.
|
|
347
|
+
await setAudioModeAsync({
|
|
348
|
+
allowsRecording: true,
|
|
349
|
+
playsInSilentMode: true,
|
|
333
350
|
});
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
await
|
|
339
|
-
|
|
351
|
+
// Passing the preset (rather than preparing bare) makes the native
|
|
352
|
+
// side allocate a FRESH output file for this take. Without it the
|
|
353
|
+
// recorder reuses the previous URL, so a second voice message would
|
|
354
|
+
// overwrite the first while its upload is still in flight.
|
|
355
|
+
await audioRecorder.prepareToRecordAsync(RecordingPresets.HIGH_QUALITY);
|
|
356
|
+
audioRecorder.record();
|
|
357
|
+
isRecordingRef.current = true;
|
|
340
358
|
setIsRecording(true);
|
|
341
359
|
setRecordingDuration(0);
|
|
342
360
|
const startedAt = Date.now();
|
|
@@ -346,7 +364,7 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
346
364
|
}, 250);
|
|
347
365
|
} catch (err) {
|
|
348
366
|
console.warn('startRecording failed', err);
|
|
349
|
-
|
|
367
|
+
isRecordingRef.current = false;
|
|
350
368
|
setIsRecording(false);
|
|
351
369
|
setRecordingDuration(0);
|
|
352
370
|
await restoreAudioMode();
|
|
@@ -355,15 +373,15 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
355
373
|
|
|
356
374
|
const handleCancelRecording = async () => {
|
|
357
375
|
stopRecordingTimer();
|
|
358
|
-
const
|
|
359
|
-
|
|
376
|
+
const wasRecording = isRecordingRef.current;
|
|
377
|
+
isRecordingRef.current = false;
|
|
360
378
|
setIsRecording(false);
|
|
361
379
|
setRecordingDuration(0);
|
|
362
|
-
if (
|
|
380
|
+
if (wasRecording) {
|
|
363
381
|
try {
|
|
364
|
-
await
|
|
382
|
+
await audioRecorder.stop();
|
|
365
383
|
} catch {
|
|
366
|
-
/* already stopped
|
|
384
|
+
/* already stopped — fine */
|
|
367
385
|
}
|
|
368
386
|
}
|
|
369
387
|
await restoreAudioMode();
|
|
@@ -371,12 +389,12 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
371
389
|
|
|
372
390
|
const handleStopAndSendRecording = async () => {
|
|
373
391
|
stopRecordingTimer();
|
|
374
|
-
const
|
|
375
|
-
|
|
392
|
+
const wasRecording = isRecordingRef.current;
|
|
393
|
+
isRecordingRef.current = false;
|
|
376
394
|
setIsRecording(false);
|
|
377
395
|
const seconds = recordingDuration;
|
|
378
396
|
setRecordingDuration(0);
|
|
379
|
-
if (!
|
|
397
|
+
if (!wasRecording) {
|
|
380
398
|
await restoreAudioMode();
|
|
381
399
|
return;
|
|
382
400
|
}
|
|
@@ -384,18 +402,20 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
384
402
|
// within the same tick there's nothing audible to send.
|
|
385
403
|
if (seconds < 1) {
|
|
386
404
|
try {
|
|
387
|
-
await
|
|
405
|
+
await audioRecorder.stop();
|
|
388
406
|
} catch {}
|
|
389
407
|
await restoreAudioMode();
|
|
390
408
|
return;
|
|
391
409
|
}
|
|
392
410
|
try {
|
|
393
|
-
await
|
|
411
|
+
await audioRecorder.stop();
|
|
394
412
|
} catch (err) {
|
|
395
|
-
console.warn('
|
|
413
|
+
console.warn('recorder.stop failed', err);
|
|
396
414
|
}
|
|
415
|
+
// Read the URI while it still points at THIS take — the next
|
|
416
|
+
// prepareToRecordAsync() swaps in a new output file.
|
|
417
|
+
const uri = audioRecorder.uri;
|
|
397
418
|
await restoreAudioMode();
|
|
398
|
-
const uri = rec.getURI();
|
|
399
419
|
if (!uri) {return;}
|
|
400
420
|
// HIGH_QUALITY preset writes AAC in an M4A container on both iOS
|
|
401
421
|
// and Android, so a single mimetype works. `voice-<ts>.m4a`
|
|
@@ -419,14 +439,16 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
419
439
|
useEffect(() => {
|
|
420
440
|
return () => {
|
|
421
441
|
stopRecordingTimer();
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
rec.stopAndUnloadAsync().catch(() => {});
|
|
442
|
+
if (isRecordingRef.current) {
|
|
443
|
+
isRecordingRef.current = false;
|
|
444
|
+
audioRecorder.stop().catch(() => {});
|
|
426
445
|
}
|
|
427
446
|
restoreAudioMode();
|
|
428
447
|
};
|
|
429
|
-
|
|
448
|
+
// The hook's recorder identity is stable (its options never change),
|
|
449
|
+
// so this teardown runs on unmount only.
|
|
450
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
451
|
+
}, [audioRecorder]);
|
|
430
452
|
|
|
431
453
|
const handleRemoveImage = (index: number) => {
|
|
432
454
|
const next = filePreviewsRef.current.filter((_, i) => i !== index);
|
|
@@ -487,6 +509,24 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
487
509
|
setMessage(next);
|
|
488
510
|
}, [editMessage]);
|
|
489
511
|
|
|
512
|
+
// iOS's multiline TextInput always top-aligns its content (UITextView
|
|
513
|
+
// has no vertical-centering API RN can hook into — `textAlignVertical`
|
|
514
|
+
// is Android-only), so a `minHeight` taller than one text line leaves
|
|
515
|
+
// the placeholder/cursor pinned near the top instead of centered. Fix:
|
|
516
|
+
// derive vertical padding from the actual line height so the box's
|
|
517
|
+
// resting height already equals its content height — nothing is left
|
|
518
|
+
// to look top-aligned.
|
|
519
|
+
const inputFontSize =
|
|
520
|
+
config?.typography?.input?.fontSize ??
|
|
521
|
+
config?.typography?.elements?.inputText?.fontSize ??
|
|
522
|
+
16;
|
|
523
|
+
const inputMinHeight = config?.typography?.input?.minHeight ?? 40;
|
|
524
|
+
const estimatedLineHeight = inputFontSize * 1.2;
|
|
525
|
+
const inputVerticalPadding = Math.max(
|
|
526
|
+
4,
|
|
527
|
+
(inputMinHeight - estimatedLineHeight) / 2
|
|
528
|
+
);
|
|
529
|
+
|
|
490
530
|
return (
|
|
491
531
|
<InputContainer isText={!!message}>
|
|
492
532
|
{filePreviews.length > 0 && (
|
|
@@ -521,7 +561,7 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
521
561
|
color={config?.colors?.primary}
|
|
522
562
|
fontSize={config?.typography?.input?.fontSize}
|
|
523
563
|
fontWeight={config?.typography?.input?.fontWeight as any}
|
|
524
|
-
placeholder=
|
|
564
|
+
placeholder={t('input.placeholder')}
|
|
525
565
|
placeholderTextColor="#999"
|
|
526
566
|
value={message}
|
|
527
567
|
onChangeText={(text) => {
|
|
@@ -541,13 +581,16 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
541
581
|
// No fixed height: a multiline TextInput auto-grows to fit
|
|
542
582
|
// its content between min/max. Locking `height` made iOS
|
|
543
583
|
// top-align the text inside the taller box (it looked like
|
|
544
|
-
// the text had dropped).
|
|
545
|
-
//
|
|
546
|
-
// the
|
|
584
|
+
// the text had dropped). paddingVertical (computed above)
|
|
585
|
+
// makes the resting box height equal minHeight without
|
|
586
|
+
// leaving slack above the text, which is what actually
|
|
587
|
+
// centers it on iOS.
|
|
547
588
|
style={[
|
|
548
589
|
{
|
|
549
|
-
minHeight:
|
|
590
|
+
minHeight: inputMinHeight,
|
|
550
591
|
maxHeight: config?.typography?.input?.maxHeight ?? 120,
|
|
592
|
+
paddingTop: inputVerticalPadding,
|
|
593
|
+
paddingBottom: inputVerticalPadding,
|
|
551
594
|
flex: 1,
|
|
552
595
|
},
|
|
553
596
|
getElementFont(config, 'inputText'),
|
|
@@ -40,12 +40,15 @@ export const CenterContainer = styled.View<{
|
|
|
40
40
|
}>`
|
|
41
41
|
align-items: center;
|
|
42
42
|
justify-content: center;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
/* flex:1 so the title fills the space BETWEEN the left (back/avatar) and
|
|
44
|
+
* right (globe / call / menu) clusters and shrinks to make room for them.
|
|
45
|
+
* The previous fixed 90% width overflowed the row (15% left + 90% center
|
|
46
|
+
* + 25% right = 130%), pushing the right cluster — the translate globe,
|
|
47
|
+
* call and room-menu icons — off-screen where it rendered but was clipped
|
|
48
|
+
* by the device edge. min-width:0 lets the title ellipsize instead of
|
|
49
|
+
* forcing the row wider. Mirrors the web header's flexbox layout. */
|
|
50
|
+
flex: 1;
|
|
51
|
+
min-width: 0;
|
|
49
52
|
padding-right: ${({ rightSpace }) => (rightSpace ? '16' : '0')}px;
|
|
50
53
|
padding-left: ${({ leftSpace }) => (leftSpace ? '16' : '0')}px;
|
|
51
54
|
`;
|
|
@@ -51,7 +51,13 @@ const Composing: FC<ComposingProps> = ({ usersTyping = ['User'], style }) => {
|
|
|
51
51
|
return (
|
|
52
52
|
<View style={[styles.wrapper, style]}>
|
|
53
53
|
{typingText ? (
|
|
54
|
-
<Text
|
|
54
|
+
<Text
|
|
55
|
+
style={styles.typingText}
|
|
56
|
+
numberOfLines={1}
|
|
57
|
+
ellipsizeMode="tail"
|
|
58
|
+
>
|
|
59
|
+
{typingText}
|
|
60
|
+
</Text>
|
|
55
61
|
) : (
|
|
56
62
|
<View />
|
|
57
63
|
)}
|
|
@@ -10,6 +10,7 @@ import React, {
|
|
|
10
10
|
} from 'react';
|
|
11
11
|
import { AppState, DeviceEventEmitter } from 'react-native';
|
|
12
12
|
import NetInfo from '@react-native-community/netinfo';
|
|
13
|
+
import { VideoCallOverlay } from '../components/VideoCalls/VideoCallOverlay';
|
|
13
14
|
import XmppClient, {
|
|
14
15
|
XmppCredentialsProvider,
|
|
15
16
|
} from '../networking/xmppClient';
|
|
@@ -506,7 +507,13 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config, is
|
|
|
506
507
|
useEffect(() => {
|
|
507
508
|
if (!client) {return;}
|
|
508
509
|
const id = setInterval(() => {
|
|
509
|
-
|
|
510
|
+
// Only skip while genuinely backgrounded. iOS also reports 'inactive'
|
|
511
|
+
// for transient states the user still sees the chat through (control
|
|
512
|
+
// centre, an incoming-call banner, app-switcher preview, and — on the
|
|
513
|
+
// simulator — whenever the window is not focused). Gating on
|
|
514
|
+
// `!== 'active'` meant the watchdog silently stopped running in all of
|
|
515
|
+
// those, so a socket that dropped during one never got revived.
|
|
516
|
+
if (AppState.currentState === 'background') {return;}
|
|
510
517
|
if (client.suppressReconnect) {return;}
|
|
511
518
|
if (client.status !== 'online') {
|
|
512
519
|
devPushLog('rn', `watchdog: client ${client.status} → forceReconnect`);
|
|
@@ -770,6 +777,12 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config, is
|
|
|
770
777
|
}}
|
|
771
778
|
>
|
|
772
779
|
{children}
|
|
780
|
+
{/* Lives here, not inside <Chat>: an incoming call-token can arrive
|
|
781
|
+
(and must still ring) while the user is on a different screen of
|
|
782
|
+
the host app, as long as the socket is up. VideoCallOverlay reads
|
|
783
|
+
config and call state from Redux itself, so it needs nothing
|
|
784
|
+
passed in, and renders null whenever no call is in flight. */}
|
|
785
|
+
<VideoCallOverlay />
|
|
773
786
|
</XmppContext.Provider>
|
|
774
787
|
);
|
|
775
788
|
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { IMessage } from '../types/types';
|
|
2
|
+
|
|
3
|
+
// A server "call-state" stanza is emitted into the room when a call session
|
|
4
|
+
// concludes (someone leaves the LiveKit room). It carries:
|
|
5
|
+
// type="call-state" callId="..." durationMs="<ms>"
|
|
6
|
+
// callerXmppUsername="<who started the call>" isSystemMessage="true"
|
|
7
|
+
// We turn it into a friendly chat-log entry, Telegram/WhatsApp style:
|
|
8
|
+
// - durationMs > 0 -> "Outgoing call · 12 sec" / "Incoming call · 1 min"
|
|
9
|
+
// - durationMs == 0 -> "No answer" (we called) / "Missed call" (they called)
|
|
10
|
+
// Direction is derived from callerXmppUsername vs the logged-in user.
|
|
11
|
+
|
|
12
|
+
export interface CallLogMeta {
|
|
13
|
+
callId: string;
|
|
14
|
+
direction: 'outgoing' | 'incoming';
|
|
15
|
+
durationMs: number;
|
|
16
|
+
missed: boolean;
|
|
17
|
+
kind: 'audio' | 'video';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const isCallLogMessage = (message: unknown): boolean => {
|
|
21
|
+
const type = String((message as any)?.type || '').toLowerCase();
|
|
22
|
+
return type === 'call-state';
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Only a call-state that actually carries log metadata (a caller and/or a
|
|
26
|
+
// duration) is worth rendering. Bare client-side signaling frames
|
|
27
|
+
// (state=cancelled/declined/ended with no caller/duration) must NOT become
|
|
28
|
+
// chat entries, they're real-time teardown only.
|
|
29
|
+
export const callStateHasLogData = (attrs: Record<string, any>): boolean => {
|
|
30
|
+
if (!attrs) {return false;}
|
|
31
|
+
const hasCaller = !!String(attrs.callerXmppUsername || '').trim();
|
|
32
|
+
const hasDuration =
|
|
33
|
+
attrs.durationMs !== undefined &&
|
|
34
|
+
attrs.durationMs !== null &&
|
|
35
|
+
attrs.durationMs !== '';
|
|
36
|
+
return hasCaller || hasDuration;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const formatCallDuration = (ms: number): string => {
|
|
40
|
+
const totalSeconds = Math.max(0, Math.round(ms / 1000));
|
|
41
|
+
if (totalSeconds < 60) {return `${totalSeconds} sec`;}
|
|
42
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
43
|
+
const seconds = totalSeconds % 60;
|
|
44
|
+
if (seconds === 0) {return `${minutes} min`;}
|
|
45
|
+
return `${minutes} min ${seconds} sec`;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const transformCallLogMessage = (
|
|
49
|
+
message: IMessage,
|
|
50
|
+
selfXmppUsername: string
|
|
51
|
+
): IMessage => {
|
|
52
|
+
if (!isCallLogMessage(message)) {return message;}
|
|
53
|
+
|
|
54
|
+
const raw = message as any;
|
|
55
|
+
const callerLocal = String(raw.callerXmppUsername || '')
|
|
56
|
+
.split('@')[0]
|
|
57
|
+
.trim();
|
|
58
|
+
const selfLocal = String(selfXmppUsername || '').split('@')[0].trim();
|
|
59
|
+
// When we know the caller, "outgoing" means we started it. With no
|
|
60
|
+
// caller info, default to incoming (safer label than claiming we dialed).
|
|
61
|
+
const isOutgoing = !!callerLocal && callerLocal === selfLocal;
|
|
62
|
+
const durationMs = Number(raw.durationMs || 0);
|
|
63
|
+
const kind: 'audio' | 'video' =
|
|
64
|
+
String(raw.kind || '').toLowerCase() === 'audio' ? 'audio' : 'video';
|
|
65
|
+
|
|
66
|
+
let body: string;
|
|
67
|
+
if (durationMs > 0) {
|
|
68
|
+
body = `${isOutgoing ? 'Outgoing' : 'Incoming'} call · ${formatCallDuration(durationMs)}`;
|
|
69
|
+
} else {
|
|
70
|
+
body = isOutgoing ? 'No answer' : 'Missed call';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const callLog: CallLogMeta = {
|
|
74
|
+
callId: String(raw.callId || raw.callid || '').trim(),
|
|
75
|
+
direction: isOutgoing ? 'outgoing' : 'incoming',
|
|
76
|
+
durationMs,
|
|
77
|
+
missed: durationMs === 0,
|
|
78
|
+
kind,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
...message,
|
|
83
|
+
body,
|
|
84
|
+
// Render through the existing centered SystemMessage pipeline.
|
|
85
|
+
isSystemMessage: 'true',
|
|
86
|
+
callLog,
|
|
87
|
+
} as IMessage;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Build a "call ended" log entry locally (client-side) when a call finishes.
|
|
91
|
+
// The authoritative entry is a SERVER `call-state` broadcast, but that isn't
|
|
92
|
+
// guaranteed to arrive (see header comment). This fallback keeps the chat log
|
|
93
|
+
// working regardless. It's deduplicated against any server copy in
|
|
94
|
+
// `addRoomMessage` by `callLog.callId` (the entry with the largest duration
|
|
95
|
+
// wins), so if the server does broadcast, the two collapse into one.
|
|
96
|
+
export const buildLocalCallLogMessage = (params: {
|
|
97
|
+
callId: string;
|
|
98
|
+
direction: 'outgoing' | 'incoming';
|
|
99
|
+
durationMs: number;
|
|
100
|
+
kind: 'audio' | 'video';
|
|
101
|
+
selfXmppUsername: string;
|
|
102
|
+
}): IMessage => {
|
|
103
|
+
const { callId, direction, durationMs, kind, selfXmppUsername } = params;
|
|
104
|
+
const isOutgoing = direction === 'outgoing';
|
|
105
|
+
const safeDuration = Math.max(0, Math.round(durationMs || 0));
|
|
106
|
+
|
|
107
|
+
let body: string;
|
|
108
|
+
if (safeDuration > 0) {
|
|
109
|
+
body = `${isOutgoing ? 'Outgoing' : 'Incoming'} call · ${formatCallDuration(safeDuration)}`;
|
|
110
|
+
} else {
|
|
111
|
+
body = isOutgoing ? 'No answer' : 'Missed call';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const callLog: CallLogMeta = {
|
|
115
|
+
callId,
|
|
116
|
+
direction,
|
|
117
|
+
durationMs: safeDuration,
|
|
118
|
+
missed: safeDuration === 0,
|
|
119
|
+
kind,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
// Deterministic id so a re-fire for the same call collapses in place.
|
|
124
|
+
id: callId ? `calllog-${callId}` : `calllog-${Date.now()}`,
|
|
125
|
+
body,
|
|
126
|
+
date: new Date().toISOString(),
|
|
127
|
+
isSystemMessage: 'true',
|
|
128
|
+
type: 'call-state',
|
|
129
|
+
callLog,
|
|
130
|
+
user: { id: selfXmppUsername },
|
|
131
|
+
} as unknown as IMessage;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The label for a call-log entry, built at RENDER time from the callLog
|
|
136
|
+
* meta rather than read off `message.body`.
|
|
137
|
+
*
|
|
138
|
+
* transformCallLogMessage bakes an English sentence into `body` the moment
|
|
139
|
+
* the stanza arrives, which freezes the language: switch to French and
|
|
140
|
+
* every call log you already received stays English forever, because the
|
|
141
|
+
* text is data by then, not a rendering. The meta (direction/duration/
|
|
142
|
+
* missed) is the actual fact, the sentence is a view of it, so it belongs
|
|
143
|
+
* where the view is. `body` stays as the fallback for entries received
|
|
144
|
+
* before this existed, and for the notification/room-list preview paths
|
|
145
|
+
* that only have the raw message.
|
|
146
|
+
*/
|
|
147
|
+
export const formatCallLogLabel = (
|
|
148
|
+
callLog: CallLogMeta | undefined,
|
|
149
|
+
t: (key: string, vars?: Record<string, string | number>) => string,
|
|
150
|
+
fallbackBody = ''
|
|
151
|
+
): string => {
|
|
152
|
+
if (!callLog) {return fallbackBody;}
|
|
153
|
+
|
|
154
|
+
if (!callLog.durationMs || callLog.durationMs <= 0) {
|
|
155
|
+
return callLog.direction === 'outgoing'
|
|
156
|
+
? t('call.noAnswer')
|
|
157
|
+
: t('call.missed');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const totalSeconds = Math.max(0, Math.round(callLog.durationMs / 1000));
|
|
161
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
162
|
+
const seconds = totalSeconds % 60;
|
|
163
|
+
const duration =
|
|
164
|
+
totalSeconds < 60
|
|
165
|
+
? t('call.durationSec', { n: totalSeconds })
|
|
166
|
+
: seconds === 0
|
|
167
|
+
? t('call.durationMin', { n: minutes })
|
|
168
|
+
: t('call.durationMinSec', { m: minutes, s: seconds });
|
|
169
|
+
|
|
170
|
+
const direction =
|
|
171
|
+
callLog.direction === 'outgoing' ? t('call.outgoing') : t('call.incoming');
|
|
172
|
+
|
|
173
|
+
return `${direction} · ${duration}`;
|
|
174
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Recognise and parse an *incoming call* push notification.
|
|
2
|
+
//
|
|
3
|
+
// Calls normally ring via a live XMPP `call-token` stanza, which only arrives
|
|
4
|
+
// while the socket is connected. On mobile the socket is dead whenever the OS
|
|
5
|
+
// backgrounds or kills the app, so the backend also delivers the call over
|
|
6
|
+
// push, in the same `data`-payload format as message pushes. This helper lets
|
|
7
|
+
// the push layer tell a call push apart from a chat push and pull the call
|
|
8
|
+
// fields out, tolerating the field-name variations backends tend to use.
|
|
9
|
+
|
|
10
|
+
export interface ParsedCallPush {
|
|
11
|
+
callId: string | null;
|
|
12
|
+
/** LiveKit token, if the push carried one (lets us ring + accept offline). */
|
|
13
|
+
token: string | null;
|
|
14
|
+
kind: 'audio' | 'video';
|
|
15
|
+
/** Bare room name (JID localpart) the call belongs to. */
|
|
16
|
+
roomBareName: string | null;
|
|
17
|
+
/** Raw room reference from the payload (full JID or bare name). */
|
|
18
|
+
roomRef: string | null;
|
|
19
|
+
/** Display name for the ring screen (caller). */
|
|
20
|
+
callerName: string | null;
|
|
21
|
+
/** Caller's xmpp localpart, when provided. */
|
|
22
|
+
callerXmppUsername: string | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const pick = (data: Record<string, any>, keys: string[]): string => {
|
|
26
|
+
for (const k of keys) {
|
|
27
|
+
const v = data?.[k];
|
|
28
|
+
if (v !== undefined && v !== null && String(v).trim() !== '') {
|
|
29
|
+
return String(v).trim();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return '';
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* True when a push `data` payload represents an incoming call. Matches on an
|
|
37
|
+
* explicit call marker (`type`/`pushType`/`category`/`event` === "call"-ish)
|
|
38
|
+
* or on a callId paired with a call-specific field, so a normal message push
|
|
39
|
+
* is never misread as a call.
|
|
40
|
+
*/
|
|
41
|
+
export const isCallPush = (
|
|
42
|
+
data: Record<string, any> | null | undefined
|
|
43
|
+
): boolean => {
|
|
44
|
+
if (!data) {return false;}
|
|
45
|
+
const marker = pick(data, [
|
|
46
|
+
'type',
|
|
47
|
+
'pushType',
|
|
48
|
+
'category',
|
|
49
|
+
'messageType',
|
|
50
|
+
'event',
|
|
51
|
+
]).toLowerCase();
|
|
52
|
+
if (
|
|
53
|
+
marker === 'call' ||
|
|
54
|
+
marker === 'incoming-call' ||
|
|
55
|
+
marker === 'call-token' ||
|
|
56
|
+
marker === 'call-invite'
|
|
57
|
+
) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
const callId = pick(data, ['callId', 'callid', 'callID']);
|
|
61
|
+
const hasCallField =
|
|
62
|
+
!!pick(data, ['callToken', 'livekitToken']) ||
|
|
63
|
+
!!pick(data, ['callKind']) ||
|
|
64
|
+
pick(data, ['kind']).toLowerCase() === 'audio' ||
|
|
65
|
+
pick(data, ['kind']).toLowerCase() === 'video';
|
|
66
|
+
return !!callId && hasCallField;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const parseCallPush = (
|
|
70
|
+
data: Record<string, any> | null | undefined
|
|
71
|
+
): ParsedCallPush => {
|
|
72
|
+
const d = data || {};
|
|
73
|
+
const roomRef =
|
|
74
|
+
pick(d, ['callRoom', 'room', 'roomJid', 'jid', 'chatId']) || null;
|
|
75
|
+
const roomBareName = roomRef ? roomRef.split('@')[0] || roomRef : null;
|
|
76
|
+
const kindRaw = pick(d, ['callKind', 'kind']).toLowerCase();
|
|
77
|
+
return {
|
|
78
|
+
callId: pick(d, ['callId', 'callid', 'callID']) || null,
|
|
79
|
+
token: pick(d, ['callToken', 'livekitToken', 'token']) || null,
|
|
80
|
+
kind: kindRaw === 'audio' ? 'audio' : 'video',
|
|
81
|
+
roomBareName,
|
|
82
|
+
roomRef,
|
|
83
|
+
callerName:
|
|
84
|
+
pick(d, ['callerName', 'callerDisplayName', 'senderName', 'title']) ||
|
|
85
|
+
null,
|
|
86
|
+
callerXmppUsername:
|
|
87
|
+
pick(d, ['callerXmppUsername', 'callerUsername', 'userJid', 'senderJid'])
|
|
88
|
+
.split('@')[0] || null,
|
|
89
|
+
};
|
|
90
|
+
};
|
|
@@ -5,10 +5,25 @@ type LanguageOption = {
|
|
|
5
5
|
id: Iso639_1Codes;
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Languages the in-chat globe picker offers.
|
|
10
|
+
*
|
|
11
|
+
* Names are AUTONYMS (each language written in itself), which is what
|
|
12
|
+
* every mainstream language picker does: a reader who only speaks French
|
|
13
|
+
* can find "Français" but not "French". Ids carry the region because the
|
|
14
|
+
* deployment's readers are Canadian and US, and the region is forwarded to
|
|
15
|
+
* the translation service so it can tell fr-CA from fr-FR. Base-language
|
|
16
|
+
* lookups (i18n tables, Translate-button visibility) strip it via
|
|
17
|
+
* toBaseLanguage, so both forms interoperate.
|
|
18
|
+
*
|
|
19
|
+
* Commented-out entries still have full string tables in i18n/strings.ts,
|
|
20
|
+
* they're just not offered to readers in the current deployment.
|
|
21
|
+
*/
|
|
8
22
|
export const LANGUAGE_OPTIONS: LanguageOption[] = [
|
|
9
|
-
{ name: 'English', id: 'en' },
|
|
10
|
-
{ name: '
|
|
11
|
-
{ name: '
|
|
12
|
-
{ name: '
|
|
13
|
-
{ name: '
|
|
23
|
+
{ name: 'English', id: 'en-CA' },
|
|
24
|
+
{ name: 'Español', id: 'es-US' },
|
|
25
|
+
{ name: 'Français', id: 'fr-CA' },
|
|
26
|
+
// { name: 'Português', id: 'pt' },
|
|
27
|
+
// { name: 'Kreyòl ayisyen', id: 'ht' },
|
|
28
|
+
// { name: '中文', id: 'zh' },
|
|
14
29
|
];
|