@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
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
import { Element } from 'ltx';
|
|
2
|
+
import { xml } from '@xmpp/client';
|
|
3
|
+
import { store } from '../roomStore';
|
|
4
|
+
import {
|
|
5
|
+
endCall,
|
|
6
|
+
setCallKind,
|
|
7
|
+
setIncomingCallToken,
|
|
8
|
+
setOutgoingCallToken,
|
|
9
|
+
} from '../roomStore/callSlice';
|
|
10
|
+
import { getGlobalXmppClient } from '../utils/clientRegistry';
|
|
11
|
+
import { callStateHasLogData } from '../helpers/callLogMessage';
|
|
12
|
+
import { pushLog } from '../utils/devLogger';
|
|
13
|
+
|
|
14
|
+
// Backend currently doesn't propagate `kind` on the broadcast call-token
|
|
15
|
+
// stanza, both ends default to video unless we tell them otherwise. We
|
|
16
|
+
// work around it by having the dialer send a separate `<data type=
|
|
17
|
+
// "call-invite" kind="..." ... />` direct-chat to the peer right before
|
|
18
|
+
// POST /v1/chats/call/create. The invite arrives over ~50ms direct chat,
|
|
19
|
+
// the call-token relays through the server in ~200-300ms, so the invite
|
|
20
|
+
// almost always lands first. This stash keys the kind hint by callId AND
|
|
21
|
+
// by room bare name (server sometimes drops callId on the token).
|
|
22
|
+
//
|
|
23
|
+
// TTL keeps the map from growing unbounded if a token never arrives.
|
|
24
|
+
const KIND_HINT_TTL_MS = 60_000;
|
|
25
|
+
const kindHints = new Map<
|
|
26
|
+
string,
|
|
27
|
+
{ kind: 'audio' | 'video'; expiresAt: number }
|
|
28
|
+
>();
|
|
29
|
+
|
|
30
|
+
const stashKindHint = (
|
|
31
|
+
key: string | null | undefined,
|
|
32
|
+
kind: 'audio' | 'video'
|
|
33
|
+
) => {
|
|
34
|
+
if (!key) {return;}
|
|
35
|
+
// Cheap cleanup pass, drop expired entries before inserting.
|
|
36
|
+
const now = Date.now();
|
|
37
|
+
for (const [k, v] of kindHints) {
|
|
38
|
+
if (v.expiresAt < now) {kindHints.delete(k);}
|
|
39
|
+
}
|
|
40
|
+
kindHints.set(key, { kind, expiresAt: now + KIND_HINT_TTL_MS });
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const consumeKindHint = (
|
|
44
|
+
keys: Array<string | null | undefined>
|
|
45
|
+
): 'audio' | 'video' | null => {
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
for (const key of keys) {
|
|
48
|
+
if (!key) {continue;}
|
|
49
|
+
const hit = kindHints.get(key);
|
|
50
|
+
if (hit && hit.expiresAt >= now) {
|
|
51
|
+
kindHints.delete(key);
|
|
52
|
+
return hit.kind;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const MAX_CALL_TOKEN_AGE_MS = 5000;
|
|
59
|
+
|
|
60
|
+
// Hermes has no global `atob` and RN ships no Buffer polyfill by default,
|
|
61
|
+
// so the JWT payload is decoded here by hand. Only used on the ~500-byte
|
|
62
|
+
// middle segment of a call token, so a table-driven loop is plenty fast
|
|
63
|
+
// and saves pulling a base64 dependency into the SDK.
|
|
64
|
+
const B64_ALPHABET =
|
|
65
|
+
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
66
|
+
|
|
67
|
+
const decodeBase64 = (input: string): string => {
|
|
68
|
+
const clean = input.replace(/[^A-Za-z0-9+/]/g, '');
|
|
69
|
+
let out = '';
|
|
70
|
+
let buffer = 0;
|
|
71
|
+
let bits = 0;
|
|
72
|
+
for (let i = 0; i < clean.length; i++) {
|
|
73
|
+
const idx = B64_ALPHABET.indexOf(clean[i]);
|
|
74
|
+
if (idx === -1) {continue;}
|
|
75
|
+
buffer = (buffer << 6) | idx;
|
|
76
|
+
bits += 6;
|
|
77
|
+
if (bits >= 8) {
|
|
78
|
+
bits -= 8;
|
|
79
|
+
out += String.fromCharCode((buffer >> bits) & 0xff);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// A JWT payload is UTF-8, and decodeBase64 hands back one char per byte.
|
|
86
|
+
// Re-interpret those bytes as UTF-8 so non-ASCII caller names (accents,
|
|
87
|
+
// Cyrillic) survive instead of turning into mojibake on the ring screen.
|
|
88
|
+
const bytesToUtf8 = (binary: string): string => {
|
|
89
|
+
try {
|
|
90
|
+
return decodeURIComponent(
|
|
91
|
+
binary
|
|
92
|
+
.split('')
|
|
93
|
+
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
|
|
94
|
+
.join('')
|
|
95
|
+
);
|
|
96
|
+
} catch {
|
|
97
|
+
return binary;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Pull the JWT payload out of the call-token. We only read non-sensitive
|
|
102
|
+
// claims (`sub` = caller display name; `metadata.callId`, `video.room`),
|
|
103
|
+
// no signature verification, which is fine because the server has already
|
|
104
|
+
// vetted the token and LiveKit will reject anything tampered with on
|
|
105
|
+
// connect. Returns null when the token isn't a parseable JWT.
|
|
106
|
+
const decodeJwtPayload = (token: string): Record<string, any> | null => {
|
|
107
|
+
if (!token || typeof token !== 'string') {return null;}
|
|
108
|
+
const parts = token.split('.');
|
|
109
|
+
if (parts.length < 2) {return null;}
|
|
110
|
+
try {
|
|
111
|
+
const b64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
112
|
+
const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4);
|
|
113
|
+
return JSON.parse(bytesToUtf8(decodeBase64(padded))) as Record<string, any>;
|
|
114
|
+
} catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const extractCallId = (
|
|
120
|
+
data: Element,
|
|
121
|
+
jwtPayload: Record<string, any> | null
|
|
122
|
+
) => {
|
|
123
|
+
const fromAttr = String(data.attrs?.callId || data.attrs?.callid || '').trim();
|
|
124
|
+
if (fromAttr) {return fromAttr;}
|
|
125
|
+
const metadata = jwtPayload?.metadata;
|
|
126
|
+
if (typeof metadata === 'string') {
|
|
127
|
+
try {
|
|
128
|
+
const parsed = JSON.parse(metadata);
|
|
129
|
+
if (parsed?.callId) {return String(parsed.callId);}
|
|
130
|
+
} catch {
|
|
131
|
+
// metadata isn't JSON, ignore
|
|
132
|
+
}
|
|
133
|
+
} else if (metadata && typeof metadata === 'object' && metadata.callId) {
|
|
134
|
+
return String(metadata.callId);
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const extractCallerDisplayName = (
|
|
140
|
+
jwtPayload: Record<string, any> | null
|
|
141
|
+
): string => {
|
|
142
|
+
const sub = jwtPayload?.sub;
|
|
143
|
+
if (typeof sub !== 'string') {return '';}
|
|
144
|
+
const trimmed = sub.trim();
|
|
145
|
+
if (!trimmed) {return '';}
|
|
146
|
+
// Defense: server occasionally falls back to xmppUsername / userId for
|
|
147
|
+
// `sub` when the caller's profile is incomplete. Those look like raw
|
|
148
|
+
// ID strings (long, no spaces, all alphanumeric with underscores). Skip
|
|
149
|
+
// them so we don't render an opaque ID as the "caller name".
|
|
150
|
+
const looksLikeRawId = /^[0-9a-f_-]{16,}$/i.test(trimmed);
|
|
151
|
+
if (looksLikeRawId) {return '';}
|
|
152
|
+
return trimmed;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Room name format is "<userA>-<userB>" for 1:1 private chats (post
|
|
156
|
+
// /chats/private convention). Derive the peer xmpp localpart by removing
|
|
157
|
+
// the current user's xmpp localpart from the split.
|
|
158
|
+
const derivePeerXmppUsername = (
|
|
159
|
+
bareRoomName: string,
|
|
160
|
+
selfXmppUsername: string
|
|
161
|
+
): string | null => {
|
|
162
|
+
if (!bareRoomName.includes('-')) {return null;}
|
|
163
|
+
const parts = bareRoomName.split('-');
|
|
164
|
+
if (parts.length !== 2) {return null;}
|
|
165
|
+
const selfLocal = String(selfXmppUsername || '').split('@')[0];
|
|
166
|
+
const peer = parts.find((p) => p && p !== selfLocal);
|
|
167
|
+
return peer || null;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// Body-text values the server may use to broadcast call signaling into a
|
|
171
|
+
// MUC room (so other clients without `<data>` parsing still receive the
|
|
172
|
+
// event in their MAM archive). These must NEVER reach `onRealtimeMessage`
|
|
173
|
+
// or they render as chat bubbles / new-message notifications saying
|
|
174
|
+
// "Deleted User: call-state".
|
|
175
|
+
const CALL_BODY_KEYWORDS = new Set([
|
|
176
|
+
'call-token',
|
|
177
|
+
'call-state',
|
|
178
|
+
'call-ringing',
|
|
179
|
+
'call-ended',
|
|
180
|
+
'call-declined',
|
|
181
|
+
'call-cancelled',
|
|
182
|
+
'call-canceled',
|
|
183
|
+
'call-timeout',
|
|
184
|
+
'call-rejected',
|
|
185
|
+
]);
|
|
186
|
+
|
|
187
|
+
const extractNumericTimestamp = (value?: string): number | null => {
|
|
188
|
+
const source = String(value || '').trim();
|
|
189
|
+
if (!source) {return null;}
|
|
190
|
+
|
|
191
|
+
const direct = Number(source);
|
|
192
|
+
if (Number.isFinite(direct) && direct > 0) {
|
|
193
|
+
return source.length > 13 ? Number(source.slice(0, 13)) : direct;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const match = source.match(/\d{13,}/)?.[0];
|
|
197
|
+
if (!match) {return null;}
|
|
198
|
+
return Number(match.slice(0, 13));
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const isFreshCallToken = (stanza: Element): boolean => {
|
|
202
|
+
const stanzaId = stanza.getChild('stanza-id')?.attrs?.id;
|
|
203
|
+
const createdTs = extractNumericTimestamp(stanzaId);
|
|
204
|
+
if (!createdTs) {return false;}
|
|
205
|
+
return Math.abs(Date.now() - createdTs) <= MAX_CALL_TOKEN_AGE_MS;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// Match a call-token stanza's `room` attribute against the local rooms map.
|
|
209
|
+
// The server sends the bare room name (e.g. "${appId}_<uuid>"), but our
|
|
210
|
+
// store keys rooms by full JID, and IRoom.name actually holds the display
|
|
211
|
+
// title (see createRoomFromApi, the API's `name` is mapped onto the JID
|
|
212
|
+
// localpart). So lookup order is: full JID match -> JID-localpart match ->
|
|
213
|
+
// last-resort display-title match (only useful when the server ever decides
|
|
214
|
+
// to send a fully-qualified JID or a title verbatim).
|
|
215
|
+
const resolveRoomByStanzaRoom = (roomAttr: string) => {
|
|
216
|
+
const rooms = (store.getState().rooms.rooms || {}) as Record<
|
|
217
|
+
string,
|
|
218
|
+
{ jid?: string; name?: string; type?: string; usersCnt?: number }
|
|
219
|
+
>;
|
|
220
|
+
const roomEntries = Object.values(rooms);
|
|
221
|
+
|
|
222
|
+
const byJid = rooms[roomAttr];
|
|
223
|
+
if (byJid) {return byJid;}
|
|
224
|
+
|
|
225
|
+
const byLocalpart = roomEntries.find(
|
|
226
|
+
(room) => (room?.jid || '').split('@')[0] === roomAttr
|
|
227
|
+
);
|
|
228
|
+
if (byLocalpart) {return byLocalpart;}
|
|
229
|
+
|
|
230
|
+
const byName = roomEntries.find((room) => room?.name === roomAttr);
|
|
231
|
+
if (byName) {return byName;}
|
|
232
|
+
|
|
233
|
+
return null;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// Mirror of handleStanzas.unwrapMucsubMessage so we can look at the real
|
|
237
|
+
// payload that the server pushed into the room, mucsub wraps the inner
|
|
238
|
+
// <message> inside <event><items><item>.
|
|
239
|
+
const unwrapMucsub = (stanza: Element): Element => {
|
|
240
|
+
// The web SDK also passes the pubsub#event xmlns here, but the ltx
|
|
241
|
+
// typings bundled with the RN toolchain only declare getChild(name).
|
|
242
|
+
// Matching on the element name alone is enough: the items > item >
|
|
243
|
+
// message chain below fails fast on any other <event>.
|
|
244
|
+
const inner = stanza
|
|
245
|
+
?.getChild?.('event')
|
|
246
|
+
?.getChild?.('items')
|
|
247
|
+
?.getChild?.('item')
|
|
248
|
+
?.getChild?.('message');
|
|
249
|
+
return (inner as Element) || stanza;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const findCallData = (stanza: Element): Element | null => {
|
|
253
|
+
// Direct `<data type="call-*">` on the message. Optional-called for the
|
|
254
|
+
// same reason as onCallTokenMessage's name check: not every stanza that
|
|
255
|
+
// reaches handleStanza is a real ltx Element.
|
|
256
|
+
const direct = stanza?.getChild?.('data');
|
|
257
|
+
if (direct && /^call-/i.test(String(direct.attrs?.type || ''))) {
|
|
258
|
+
return direct;
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const getBodyText = (stanza: Element): string => {
|
|
264
|
+
const body = stanza?.getChild?.('body');
|
|
265
|
+
return String(body?.getText?.() || '').trim();
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// Tear down the active call overlay in response to a call-state stanza.
|
|
269
|
+
// Two shapes arrive here:
|
|
270
|
+
// 1. Client signaling (sendCallStateSignal): has explicit `state`
|
|
271
|
+
// (cancelled / declined / ended). Terminal -> end.
|
|
272
|
+
// 2. Server call-log: NO `state` attr, but carries `callId` /
|
|
273
|
+
// `durationMs` and is broadcast into the room when the LiveKit room
|
|
274
|
+
// empties. For a 1:1 call that means the other side left -> end.
|
|
275
|
+
// `fallbackRoomAttr` is the bare room name derived from the message's
|
|
276
|
+
// `from` JID (server call-logs don't put `room` on the <data>).
|
|
277
|
+
const handleCallStateData = (
|
|
278
|
+
data: Element,
|
|
279
|
+
fallbackRoomAttr?: string
|
|
280
|
+
): void => {
|
|
281
|
+
const state = String(
|
|
282
|
+
data.attrs?.state || data.attrs?.event || ''
|
|
283
|
+
).toLowerCase();
|
|
284
|
+
const stanzaRoomAttr =
|
|
285
|
+
String(data.attrs?.room || '').trim() || (fallbackRoomAttr || '');
|
|
286
|
+
const stanzaCallId = String(
|
|
287
|
+
data.attrs?.callId || data.attrs?.callid || ''
|
|
288
|
+
).trim();
|
|
289
|
+
const currentCall = store.getState().call;
|
|
290
|
+
|
|
291
|
+
if (currentCall.phase === 'idle') {return;}
|
|
292
|
+
|
|
293
|
+
const roomMatches =
|
|
294
|
+
!!stanzaRoomAttr &&
|
|
295
|
+
(currentCall.roomJid === stanzaRoomAttr ||
|
|
296
|
+
currentCall.roomName === stanzaRoomAttr ||
|
|
297
|
+
currentCall.roomBareName === stanzaRoomAttr ||
|
|
298
|
+
(currentCall.roomJid || '').split('@')[0] === stanzaRoomAttr);
|
|
299
|
+
const callIdMatches =
|
|
300
|
+
!!stanzaCallId &&
|
|
301
|
+
!!currentCall.callId &&
|
|
302
|
+
currentCall.callId === stanzaCallId;
|
|
303
|
+
|
|
304
|
+
const isTerminalState =
|
|
305
|
+
state === 'ended' ||
|
|
306
|
+
state === 'declined' ||
|
|
307
|
+
state === 'cancelled' ||
|
|
308
|
+
state === 'canceled' ||
|
|
309
|
+
state === 'timeout' ||
|
|
310
|
+
state === 'rejected';
|
|
311
|
+
|
|
312
|
+
// A server call-log (no explicit state, but with callId/duration) is
|
|
313
|
+
// itself a "session concluded" signal.
|
|
314
|
+
const isServerCallLog = !state && callStateHasLogData(data.attrs || {});
|
|
315
|
+
|
|
316
|
+
if ((roomMatches || callIdMatches) && (isTerminalState || isServerCallLog)) {
|
|
317
|
+
store.dispatch(endCall());
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
export const onCallTokenMessage = (stanza: Element): boolean => {
|
|
322
|
+
// The web SDK calls `stanza.is('message')`. Here we read `.name`
|
|
323
|
+
// directly: handleStanza is also fed plain stanza-shaped objects (test
|
|
324
|
+
// fixtures, and the mucsub inner payloads we synthesize below), and
|
|
325
|
+
// `.is` only exists on real ltx Elements. Name equality is what `.is`
|
|
326
|
+
// checks anyway for a stanza with no explicit xmlns.
|
|
327
|
+
if (stanza?.name !== 'message') {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// 1. Unwrap mucsub wrappers so server broadcasts into the MUC archive
|
|
332
|
+
// are inspected the same way as direct chat stanzas.
|
|
333
|
+
const inner = unwrapMucsub(stanza);
|
|
334
|
+
|
|
335
|
+
// 2. Anything carrying a `<data type="call-*">` element is signaling, no
|
|
336
|
+
// matter the message type (chat / groupchat / headline), swallow it
|
|
337
|
+
// before any chat / history / notification handler touches it.
|
|
338
|
+
const data = findCallData(inner);
|
|
339
|
+
if (data) {
|
|
340
|
+
const dataType = String(data.attrs?.type || '').toLowerCase();
|
|
341
|
+
|
|
342
|
+
if (dataType === 'call-state') {
|
|
343
|
+
// Surface the raw call-state attrs so we can confirm what the server
|
|
344
|
+
// sends for durationMs / callerXmppUsername during QA.
|
|
345
|
+
try {
|
|
346
|
+
pushLog('xmpp', '[call-state] received', {
|
|
347
|
+
callId: data.attrs?.callId,
|
|
348
|
+
durationMs: data.attrs?.durationMs,
|
|
349
|
+
state: data.attrs?.state,
|
|
350
|
+
callerXmppUsername: data.attrs?.callerXmppUsername,
|
|
351
|
+
from: stanza.attrs?.from,
|
|
352
|
+
});
|
|
353
|
+
} catch {
|
|
354
|
+
/* noop */
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// The server call-log doesn't put `room` on the <data>; derive it
|
|
358
|
+
// from the message's `from` MUC JID localpart instead.
|
|
359
|
+
const fromRoom = String(stanza.attrs?.from || '')
|
|
360
|
+
.split('/')[0]
|
|
361
|
+
.split('@')[0]
|
|
362
|
+
.trim();
|
|
363
|
+
handleCallStateData(inner, fromRoom);
|
|
364
|
+
|
|
365
|
+
// Server call-logs (carry callerXmppUsername / durationMs) should
|
|
366
|
+
// flow on to onRealtimeMessage / onMessageHistory where they become
|
|
367
|
+
// a friendly "Outgoing call · 12 sec" entry. Bare client-side
|
|
368
|
+
// signaling frames (state=cancelled/declined, no log data) are
|
|
369
|
+
// swallowed, they exist only to tear the overlay down.
|
|
370
|
+
if (callStateHasLogData(data.attrs || {})) {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (dataType === 'call-invite') {
|
|
377
|
+
const inviteKind = String(data.attrs?.kind || '').toLowerCase();
|
|
378
|
+
const normalized: 'audio' | 'video' =
|
|
379
|
+
inviteKind === 'audio' ? 'audio' : 'video';
|
|
380
|
+
const inviteRoom = String(data.attrs?.room || '').trim() || null;
|
|
381
|
+
const inviteCallId =
|
|
382
|
+
String(data.attrs?.callId || data.attrs?.callid || '').trim() || null;
|
|
383
|
+
|
|
384
|
+
// Stash for the upcoming call-token lookup AND patch any existing
|
|
385
|
+
// call state if the token actually arrived first (rare race).
|
|
386
|
+
stashKindHint(inviteRoom, normalized);
|
|
387
|
+
stashKindHint(inviteCallId, normalized);
|
|
388
|
+
|
|
389
|
+
const currentCall = store.getState().call;
|
|
390
|
+
const matchesActive =
|
|
391
|
+
currentCall.phase !== 'idle' &&
|
|
392
|
+
((!!inviteRoom &&
|
|
393
|
+
(currentCall.roomBareName === inviteRoom ||
|
|
394
|
+
currentCall.roomJid === inviteRoom ||
|
|
395
|
+
(currentCall.roomJid || '').split('@')[0] === inviteRoom)) ||
|
|
396
|
+
(!!inviteCallId && currentCall.callId === inviteCallId));
|
|
397
|
+
if (matchesActive && currentCall.kind !== normalized) {
|
|
398
|
+
store.dispatch(setCallKind(normalized));
|
|
399
|
+
}
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (dataType === 'call-token') {
|
|
404
|
+
const videoCallsConfig =
|
|
405
|
+
store.getState().chatSettingStore.config?.videoCalls;
|
|
406
|
+
if (videoCallsConfig?.enabled !== true) {
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
if (!isFreshCallToken(inner)) {
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const token = String(data.attrs?.token || '').trim();
|
|
414
|
+
const stanzaRoom = String(data.attrs?.room || '').trim();
|
|
415
|
+
const stanzaKind = String(data.attrs?.kind || '').toLowerCase();
|
|
416
|
+
const stanzaCallId =
|
|
417
|
+
String(data.attrs?.callId || data.attrs?.callid || '').trim() || null;
|
|
418
|
+
// Server doesn't propagate `kind` on the broadcast stanza, so first
|
|
419
|
+
// look for a client-side `call-invite` hint we stashed when the
|
|
420
|
+
// dialer signaled us directly. Falls back to whatever the server
|
|
421
|
+
// happens to attach, finally to video (legacy default).
|
|
422
|
+
const hintedKind = consumeKindHint([stanzaRoom, stanzaCallId]);
|
|
423
|
+
const kind: 'audio' | 'video' =
|
|
424
|
+
hintedKind || (stanzaKind === 'audio' ? 'audio' : 'video');
|
|
425
|
+
if (!token || !stanzaRoom) {
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const resolvedRoom = resolveRoomByStanzaRoom(stanzaRoom);
|
|
430
|
+
const allowedRoomTypes = videoCallsConfig.allowedRoomTypes || ['private'];
|
|
431
|
+
const usersCnt = Number(resolvedRoom?.usersCnt || 0);
|
|
432
|
+
const isOneToOneLikeRoom = usersCnt > 0 && usersCnt <= 2;
|
|
433
|
+
const isPrivateAllowed = allowedRoomTypes.includes('private');
|
|
434
|
+
if (
|
|
435
|
+
resolvedRoom?.type &&
|
|
436
|
+
resolvedRoom.type !== 'private' &&
|
|
437
|
+
!isOneToOneLikeRoom
|
|
438
|
+
) {
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
if (resolvedRoom?.type === 'private' && !isPrivateAllowed) {
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
444
|
+
if (
|
|
445
|
+
!resolvedRoom?.type &&
|
|
446
|
+
!stanzaRoom.includes('@') &&
|
|
447
|
+
!isOneToOneLikeRoom
|
|
448
|
+
) {
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const roomJid =
|
|
453
|
+
resolvedRoom?.jid || (stanzaRoom.includes('@') ? stanzaRoom : '');
|
|
454
|
+
// Prefer the JWT `sub` claim as the display name on the incoming
|
|
455
|
+
// ring screen, the server populates it with the caller's full name
|
|
456
|
+
// ("Roman Test") which is far more useful than the resolved room
|
|
457
|
+
// title (which for stale / deleted-peer chats reads "deleted").
|
|
458
|
+
const jwtPayload = decodeJwtPayload(token);
|
|
459
|
+
const callerName = extractCallerDisplayName(jwtPayload);
|
|
460
|
+
const resolvedTitle = String(resolvedRoom?.name || '').trim();
|
|
461
|
+
const titleLooksBad =
|
|
462
|
+
!resolvedTitle ||
|
|
463
|
+
['deleted', 'deleted user', 'unknown', 'null'].includes(
|
|
464
|
+
resolvedTitle.toLowerCase()
|
|
465
|
+
);
|
|
466
|
+
const incomingRoomName =
|
|
467
|
+
(callerName && titleLooksBad ? callerName : resolvedTitle) ||
|
|
468
|
+
callerName ||
|
|
469
|
+
stanzaRoom;
|
|
470
|
+
if (!roomJid) {return true;}
|
|
471
|
+
|
|
472
|
+
const callId = extractCallId(data, jwtPayload);
|
|
473
|
+
const selfXmpp =
|
|
474
|
+
store.getState().chatSettingStore.user?.xmppUsername || '';
|
|
475
|
+
const peerXmppUsername = derivePeerXmppUsername(stanzaRoom, selfXmpp);
|
|
476
|
+
|
|
477
|
+
const callState = store.getState().call;
|
|
478
|
+
const isOutgoingRoomMatch =
|
|
479
|
+
callState.direction === 'outgoing' &&
|
|
480
|
+
(callState.roomJid === roomJid ||
|
|
481
|
+
callState.roomName === incomingRoomName ||
|
|
482
|
+
callState.roomBareName === stanzaRoom);
|
|
483
|
+
|
|
484
|
+
if (isOutgoingRoomMatch) {
|
|
485
|
+
store.dispatch(
|
|
486
|
+
setOutgoingCallToken({ roomJid, token, callId: callId || null })
|
|
487
|
+
);
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
store.dispatch(
|
|
492
|
+
setIncomingCallToken({
|
|
493
|
+
roomJid,
|
|
494
|
+
roomName: incomingRoomName,
|
|
495
|
+
roomBareName: stanzaRoom,
|
|
496
|
+
token,
|
|
497
|
+
kind,
|
|
498
|
+
callId: callId || null,
|
|
499
|
+
peerXmppUsername: peerXmppUsername || null,
|
|
500
|
+
})
|
|
501
|
+
);
|
|
502
|
+
return true;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Unknown `call-*` signaling, swallow so it never lands in chat.
|
|
506
|
+
return true;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// 3. Backstop for legacy servers / clients that broadcast the signal as
|
|
510
|
+
// plain body text (no `<data>` element). The body literally reads
|
|
511
|
+
// "call-state" / "call-token" / etc. Drop these so the chat list
|
|
512
|
+
// and the notification toast never display them.
|
|
513
|
+
const bodyText = getBodyText(inner).toLowerCase();
|
|
514
|
+
if (bodyText && CALL_BODY_KEYWORDS.has(bodyText)) {
|
|
515
|
+
if (bodyText.startsWith('call-')) {
|
|
516
|
+
const stateFromBody = bodyText.replace(/^call-/, '');
|
|
517
|
+
// Treat the body-only variant as a state notification; tear down
|
|
518
|
+
// active call on terminal transitions, ignore otherwise.
|
|
519
|
+
handleCallStateData({
|
|
520
|
+
attrs: { state: stateFromBody, room: '' },
|
|
521
|
+
} as unknown as Element);
|
|
522
|
+
}
|
|
523
|
+
return true;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return false;
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
// Outbound counterpart of the swallow logic above. When the local user
|
|
530
|
+
// hangs up, cancels their own dial, or declines an incoming ring we send
|
|
531
|
+
// a `call-state` stanza to the peer so their UI dismisses instead of
|
|
532
|
+
// dangling on a phantom "incoming call" or "calling..." screen.
|
|
533
|
+
//
|
|
534
|
+
// We address the peer directly (`<message type="chat" to="<peer>@<host>">`),
|
|
535
|
+
// going through the room JID isn't reliable because the callee may not
|
|
536
|
+
// have joined the MUC yet during the ring window. The receiving client's
|
|
537
|
+
// onCallTokenMessage handler picks up the same `<data type="call-state">`
|
|
538
|
+
// it would receive from the server and dispatches `endCall()`.
|
|
539
|
+
export type CallSignalState = 'ended' | 'declined' | 'cancelled' | 'rejected';
|
|
540
|
+
|
|
541
|
+
export const sendCallStateSignal = (
|
|
542
|
+
state: CallSignalState,
|
|
543
|
+
options?: {
|
|
544
|
+
peerXmppUsername?: string | null;
|
|
545
|
+
callId?: string | null;
|
|
546
|
+
roomBareName?: string | null;
|
|
547
|
+
}
|
|
548
|
+
): void => {
|
|
549
|
+
const xmppClient = getGlobalXmppClient();
|
|
550
|
+
if (!xmppClient) {return;}
|
|
551
|
+
|
|
552
|
+
const callState = store.getState().call;
|
|
553
|
+
const peer = (
|
|
554
|
+
options?.peerXmppUsername ||
|
|
555
|
+
callState.peerXmppUsername ||
|
|
556
|
+
''
|
|
557
|
+
).trim();
|
|
558
|
+
if (!peer) {return;}
|
|
559
|
+
|
|
560
|
+
const host = (xmppClient as any).host || '';
|
|
561
|
+
if (!host) {return;}
|
|
562
|
+
|
|
563
|
+
const callId = options?.callId || callState.callId || '';
|
|
564
|
+
const room =
|
|
565
|
+
options?.roomBareName ||
|
|
566
|
+
callState.roomBareName ||
|
|
567
|
+
(callState.roomJid || '').split('@')[0] ||
|
|
568
|
+
'';
|
|
569
|
+
|
|
570
|
+
try {
|
|
571
|
+
const stanza = xml(
|
|
572
|
+
'message',
|
|
573
|
+
{
|
|
574
|
+
to: peer.includes('@') ? peer : `${peer}@${host}`,
|
|
575
|
+
type: 'chat',
|
|
576
|
+
id: `call-state-${Date.now()}`,
|
|
577
|
+
},
|
|
578
|
+
xml('data', {
|
|
579
|
+
type: 'call-state',
|
|
580
|
+
state,
|
|
581
|
+
callId,
|
|
582
|
+
room,
|
|
583
|
+
})
|
|
584
|
+
);
|
|
585
|
+
(xmppClient as any).client?.send?.(stanza);
|
|
586
|
+
} catch {
|
|
587
|
+
// Swallow, signaling is best-effort. The LiveKit room disconnect
|
|
588
|
+
// also serves as an implicit "call ended" once both sides are in.
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
// Tell the peer what KIND of call we're starting before the server-side
|
|
593
|
+
// call-token stanza reaches them, the backend currently drops the kind
|
|
594
|
+
// attribute on its broadcast, so without this the callee always lands on
|
|
595
|
+
// the video UI even when the dialer chose audio.
|
|
596
|
+
//
|
|
597
|
+
// Sent right before POST /v1/chats/call/create, addressed directly to
|
|
598
|
+
// the peer's bare JID. Receiver swallows it via onCallTokenMessage and
|
|
599
|
+
// stashes the kind hint for the upcoming call-token lookup.
|
|
600
|
+
export const sendCallInviteSignal = (
|
|
601
|
+
kind: 'audio' | 'video',
|
|
602
|
+
options: {
|
|
603
|
+
peerXmppUsername: string;
|
|
604
|
+
roomBareName: string;
|
|
605
|
+
callId?: string | null;
|
|
606
|
+
}
|
|
607
|
+
): void => {
|
|
608
|
+
const xmppClient = getGlobalXmppClient();
|
|
609
|
+
if (!xmppClient) {return;}
|
|
610
|
+
const peer = String(options.peerXmppUsername || '').trim();
|
|
611
|
+
if (!peer) {return;}
|
|
612
|
+
const host = (xmppClient as any).host || '';
|
|
613
|
+
if (!host) {return;}
|
|
614
|
+
|
|
615
|
+
try {
|
|
616
|
+
const stanza = xml(
|
|
617
|
+
'message',
|
|
618
|
+
{
|
|
619
|
+
to: peer.includes('@') ? peer : `${peer}@${host}`,
|
|
620
|
+
type: 'chat',
|
|
621
|
+
id: `call-invite-${Date.now()}`,
|
|
622
|
+
},
|
|
623
|
+
xml('data', {
|
|
624
|
+
type: 'call-invite',
|
|
625
|
+
kind,
|
|
626
|
+
room: options.roomBareName || '',
|
|
627
|
+
callId: options.callId || '',
|
|
628
|
+
})
|
|
629
|
+
);
|
|
630
|
+
(xmppClient as any).client?.send?.(stanza);
|
|
631
|
+
} catch {
|
|
632
|
+
// Best-effort: missing the hint just means the callee starts on the
|
|
633
|
+
// video UI and the dialer's audio publish auto-corrects once both
|
|
634
|
+
// join LiveKit (UI flicker, not a functional failure).
|
|
635
|
+
}
|
|
636
|
+
};
|