@ethora/chat-component-rn 26.10.10 → 26.10.11
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 +16 -0
- package/README.md +125 -0
- package/lib/commonjs/booking/index.js +44 -0
- package/lib/commonjs/booking/index.js.map +1 -0
- package/lib/commonjs/booking/pendingScheduledCall.js +275 -0
- package/lib/commonjs/booking/pendingScheduledCall.js.map +1 -0
- package/lib/commonjs/booking/scheduleForm.js +91 -0
- package/lib/commonjs/booking/scheduleForm.js.map +1 -0
- package/lib/commonjs/booking/scheduledCall.js +141 -0
- package/lib/commonjs/booking/scheduledCall.js.map +1 -0
- package/lib/commonjs/booking/scheduledCallActions.js +128 -0
- package/lib/commonjs/booking/scheduledCallActions.js.map +1 -0
- package/lib/commonjs/booking/scheduledCallLink.js +143 -0
- package/lib/commonjs/booking/scheduledCallLink.js.map +1 -0
- package/lib/commonjs/booking/scheduledCallStore.js +64 -0
- package/lib/commonjs/booking/scheduledCallStore.js.map +1 -0
- package/lib/commonjs/booking/startRoomCall.js +84 -0
- package/lib/commonjs/booking/startRoomCall.js.map +1 -0
- package/lib/commonjs/calls/callBusy.js +18 -0
- package/lib/commonjs/calls/callBusy.js.map +1 -0
- package/lib/commonjs/calls/callDisplay.js +73 -0
- package/lib/commonjs/calls/callDisplay.js.map +1 -0
- package/lib/commonjs/calls/callMode.js +175 -0
- package/lib/commonjs/calls/callMode.js.map +1 -0
- package/lib/commonjs/calls/declineReport.js +20 -0
- package/lib/commonjs/calls/declineReport.js.map +1 -0
- package/lib/commonjs/calls/p2p/P2PCallController.js +724 -0
- package/lib/commonjs/calls/p2p/P2PCallController.js.map +1 -0
- package/lib/commonjs/calls/p2p/icePolicy.js +107 -0
- package/lib/commonjs/calls/p2p/icePolicy.js.map +1 -0
- package/lib/commonjs/calls/p2p/negotiator.js +202 -0
- package/lib/commonjs/calls/p2p/negotiator.js.map +1 -0
- package/lib/commonjs/calls/p2p/p2pRuntime.js +147 -0
- package/lib/commonjs/calls/p2p/p2pRuntime.js.map +1 -0
- package/lib/commonjs/calls/p2p/signalInbox.js +98 -0
- package/lib/commonjs/calls/p2p/signalInbox.js.map +1 -0
- package/lib/commonjs/calls/p2p/signalStanza.js +141 -0
- package/lib/commonjs/calls/p2p/signalStanza.js.map +1 -0
- package/lib/commonjs/calls/p2p/webrtcRuntime.js +53 -0
- package/lib/commonjs/calls/p2p/webrtcRuntime.js.map +1 -0
- package/lib/commonjs/calls/senderTrust.js +43 -0
- package/lib/commonjs/calls/senderTrust.js.map +1 -0
- package/lib/commonjs/calls/startCall.js +51 -0
- package/lib/commonjs/calls/startCall.js.map +1 -0
- package/lib/commonjs/components/Booking/BookingIcons.js +55 -0
- package/lib/commonjs/components/Booking/BookingIcons.js.map +1 -0
- package/lib/commonjs/components/Booking/ScheduleCallButton.js +64 -0
- package/lib/commonjs/components/Booking/ScheduleCallButton.js.map +1 -0
- package/lib/commonjs/components/Booking/ScheduleCallSheet.js +351 -0
- package/lib/commonjs/components/Booking/ScheduleCallSheet.js.map +1 -0
- package/lib/commonjs/components/Booking/ScheduledCallCard.js +203 -0
- package/lib/commonjs/components/Booking/ScheduledCallCard.js.map +1 -0
- package/lib/commonjs/components/Booking/ScheduledCallHost.js +23 -0
- package/lib/commonjs/components/Booking/ScheduledCallHost.js.map +1 -0
- package/lib/commonjs/components/Booking/ScheduledCallMessage.js +55 -0
- package/lib/commonjs/components/Booking/ScheduledCallMessage.js.map +1 -0
- package/lib/commonjs/components/Booking/ScheduledCallPreJoin.js +235 -0
- package/lib/commonjs/components/Booking/ScheduledCallPreJoin.js.map +1 -0
- package/lib/commonjs/components/Booking/bookingShared.js +305 -0
- package/lib/commonjs/components/Booking/bookingShared.js.map +1 -0
- package/lib/commonjs/components/MainComponents/ChatHeader.js +2 -1
- package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
- package/lib/commonjs/components/MainComponents/LanguageSelectorButton.js +5 -2
- package/lib/commonjs/components/MainComponents/LanguageSelectorButton.js.map +1 -1
- package/lib/commonjs/components/MainComponents/LanguageSelectorModal.js +6 -3
- package/lib/commonjs/components/MainComponents/LanguageSelectorModal.js.map +1 -1
- package/lib/commonjs/components/MainComponents/MessageContainer.js +13 -0
- package/lib/commonjs/components/MainComponents/MessageContainer.js.map +1 -1
- package/lib/commonjs/components/MainComponents/ReduxWrapper.js +10 -0
- package/lib/commonjs/components/MainComponents/ReduxWrapper.js.map +1 -1
- package/lib/commonjs/components/Modals/ChatProfileModal/ReportChatModal.js +19 -15
- package/lib/commonjs/components/Modals/ChatProfileModal/ReportChatModal.js.map +1 -1
- package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js +15 -8
- package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
- package/lib/commonjs/components/Modals/UserSettingsModal/LanguageCard.js +1 -1
- package/lib/commonjs/components/Modals/UserSettingsModal/LanguageCard.js.map +1 -1
- package/lib/commonjs/components/VideoCalls/ActiveCallBar.js +3 -2
- package/lib/commonjs/components/VideoCalls/ActiveCallBar.js.map +1 -1
- package/lib/commonjs/components/VideoCalls/CallButtons.js +19 -11
- package/lib/commonjs/components/VideoCalls/CallButtons.js.map +1 -1
- package/lib/commonjs/components/VideoCalls/CallControlBar.js +119 -0
- package/lib/commonjs/components/VideoCalls/CallControlBar.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/CallVideo.js +49 -0
- package/lib/commonjs/components/VideoCalls/CallVideo.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/GroupCallView.js +281 -0
- package/lib/commonjs/components/VideoCalls/GroupCallView.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/P2PCallRunner.js +151 -0
- package/lib/commonjs/components/VideoCalls/P2PCallRunner.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js +38 -14
- package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js.map +1 -1
- package/lib/commonjs/components/VideoCalls/VideoCallSession.js +57 -128
- package/lib/commonjs/components/VideoCalls/VideoCallSession.js.map +1 -1
- package/lib/commonjs/components/VideoCalls/callActions.js +3 -0
- package/lib/commonjs/components/VideoCalls/callActions.js.map +1 -1
- package/lib/commonjs/components/VideoCalls/callElapsed.js +38 -0
- package/lib/commonjs/components/VideoCalls/callElapsed.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/callLive.js +15 -0
- package/lib/commonjs/components/VideoCalls/callLive.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/callSessionController.js +81 -24
- package/lib/commonjs/components/VideoCalls/callSessionController.js.map +1 -1
- package/lib/commonjs/components/VideoCalls/callSessionTypes.js +41 -0
- package/lib/commonjs/components/VideoCalls/callSessionTypes.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/groupLayout.js +43 -0
- package/lib/commonjs/components/VideoCalls/groupLayout.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/p2pCallSession.js +272 -0
- package/lib/commonjs/components/VideoCalls/p2pCallSession.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/useCallKeep.js +7 -2
- package/lib/commonjs/components/VideoCalls/useCallKeep.js.map +1 -1
- package/lib/commonjs/components/VideoCalls/useOngoingCallNotification.js +2 -1
- package/lib/commonjs/components/VideoCalls/useOngoingCallNotification.js.map +1 -1
- package/lib/commonjs/context/xmppProvider.js +2 -1
- package/lib/commonjs/context/xmppProvider.js.map +1 -1
- package/lib/commonjs/helpers/callPush.js +3 -1
- package/lib/commonjs/helpers/callPush.js.map +1 -1
- package/lib/commonjs/helpers/incomingCallFromPush.js +63 -7
- package/lib/commonjs/helpers/incomingCallFromPush.js.map +1 -1
- package/lib/commonjs/helpers/pushPayload.js +5 -0
- package/lib/commonjs/helpers/pushPayload.js.map +1 -1
- package/lib/commonjs/helpers/reportRoom.js +76 -0
- package/lib/commonjs/helpers/reportRoom.js.map +1 -0
- package/lib/commonjs/i18n/strings.js +444 -0
- package/lib/commonjs/i18n/strings.js.map +1 -1
- package/lib/commonjs/main.js +93 -0
- package/lib/commonjs/main.js.map +1 -1
- package/lib/commonjs/networking/api-requests/callState.api.js +75 -0
- package/lib/commonjs/networking/api-requests/callState.api.js.map +1 -0
- package/lib/commonjs/networking/api-requests/push.api.js +29 -6
- package/lib/commonjs/networking/api-requests/push.api.js.map +1 -1
- package/lib/commonjs/networking/api-requests/rooms.api.js +8 -2
- package/lib/commonjs/networking/api-requests/rooms.api.js.map +1 -1
- package/lib/commonjs/networking/api-requests/scheduledCalls.api.js +141 -0
- package/lib/commonjs/networking/api-requests/scheduledCalls.api.js.map +1 -0
- package/lib/commonjs/networking/callTokenStanza.js +210 -50
- package/lib/commonjs/networking/callTokenStanza.js.map +1 -1
- package/lib/commonjs/roomStore/callSlice.js +59 -4
- package/lib/commonjs/roomStore/callSlice.js.map +1 -1
- package/lib/commonjs/services/appBadge.js +80 -0
- package/lib/commonjs/services/appBadge.js.map +1 -0
- package/lib/commonjs/services/languageSettings.js +47 -15
- package/lib/commonjs/services/languageSettings.js.map +1 -1
- package/lib/commonjs/services/pushRegistration.js +21 -5
- package/lib/commonjs/services/pushRegistration.js.map +1 -1
- package/lib/commonjs/version.js +1 -1
- package/lib/module/booking/index.js +5 -0
- package/lib/module/booking/index.js.map +1 -0
- package/lib/module/booking/pendingScheduledCall.js +261 -0
- package/lib/module/booking/pendingScheduledCall.js.map +1 -0
- package/lib/module/booking/scheduleForm.js +79 -0
- package/lib/module/booking/scheduleForm.js.map +1 -0
- package/lib/module/booking/scheduledCall.js +129 -0
- package/lib/module/booking/scheduledCall.js.map +1 -0
- package/lib/module/booking/scheduledCallActions.js +118 -0
- package/lib/module/booking/scheduledCallActions.js.map +1 -0
- package/lib/module/booking/scheduledCallLink.js +136 -0
- package/lib/module/booking/scheduledCallLink.js.map +1 -0
- package/lib/module/booking/scheduledCallStore.js +60 -0
- package/lib/module/booking/scheduledCallStore.js.map +1 -0
- package/lib/module/booking/startRoomCall.js +80 -0
- package/lib/module/booking/startRoomCall.js.map +1 -0
- package/lib/module/calls/callBusy.js +13 -0
- package/lib/module/calls/callBusy.js.map +1 -0
- package/lib/module/calls/callDisplay.js +65 -0
- package/lib/module/calls/callDisplay.js.map +1 -0
- package/lib/module/calls/callMode.js +168 -0
- package/lib/module/calls/callMode.js.map +1 -0
- package/lib/module/calls/declineReport.js +16 -0
- package/lib/module/calls/declineReport.js.map +1 -0
- package/lib/module/calls/p2p/P2PCallController.js +718 -0
- package/lib/module/calls/p2p/P2PCallController.js.map +1 -0
- package/lib/module/calls/p2p/icePolicy.js +102 -0
- package/lib/module/calls/p2p/icePolicy.js.map +1 -0
- package/lib/module/calls/p2p/negotiator.js +197 -0
- package/lib/module/calls/p2p/negotiator.js.map +1 -0
- package/lib/module/calls/p2p/p2pRuntime.js +137 -0
- package/lib/module/calls/p2p/p2pRuntime.js.map +1 -0
- package/lib/module/calls/p2p/signalInbox.js +93 -0
- package/lib/module/calls/p2p/signalInbox.js.map +1 -0
- package/lib/module/calls/p2p/signalStanza.js +135 -0
- package/lib/module/calls/p2p/signalStanza.js.map +1 -0
- package/lib/module/calls/p2p/webrtcRuntime.js +46 -0
- package/lib/module/calls/p2p/webrtcRuntime.js.map +1 -0
- package/lib/module/calls/senderTrust.js +38 -0
- package/lib/module/calls/senderTrust.js.map +1 -0
- package/lib/module/calls/startCall.js +47 -0
- package/lib/module/calls/startCall.js.map +1 -0
- package/lib/module/components/Booking/BookingIcons.js +48 -0
- package/lib/module/components/Booking/BookingIcons.js.map +1 -0
- package/lib/module/components/Booking/ScheduleCallButton.js +59 -0
- package/lib/module/components/Booking/ScheduleCallButton.js.map +1 -0
- package/lib/module/components/Booking/ScheduleCallSheet.js +345 -0
- package/lib/module/components/Booking/ScheduleCallSheet.js.map +1 -0
- package/lib/module/components/Booking/ScheduledCallCard.js +197 -0
- package/lib/module/components/Booking/ScheduledCallCard.js.map +1 -0
- package/lib/module/components/Booking/ScheduledCallHost.js +18 -0
- package/lib/module/components/Booking/ScheduledCallHost.js.map +1 -0
- package/lib/module/components/Booking/ScheduledCallMessage.js +48 -0
- package/lib/module/components/Booking/ScheduledCallMessage.js.map +1 -0
- package/lib/module/components/Booking/ScheduledCallPreJoin.js +229 -0
- package/lib/module/components/Booking/ScheduledCallPreJoin.js.map +1 -0
- package/lib/module/components/Booking/bookingShared.js +291 -0
- package/lib/module/components/Booking/bookingShared.js.map +1 -0
- package/lib/module/components/MainComponents/ChatHeader.js +2 -1
- package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
- package/lib/module/components/MainComponents/LanguageSelectorButton.js +5 -2
- package/lib/module/components/MainComponents/LanguageSelectorButton.js.map +1 -1
- package/lib/module/components/MainComponents/LanguageSelectorModal.js +7 -4
- package/lib/module/components/MainComponents/LanguageSelectorModal.js.map +1 -1
- package/lib/module/components/MainComponents/MessageContainer.js +13 -0
- package/lib/module/components/MainComponents/MessageContainer.js.map +1 -1
- package/lib/module/components/MainComponents/ReduxWrapper.js +10 -0
- package/lib/module/components/MainComponents/ReduxWrapper.js.map +1 -1
- package/lib/module/components/Modals/ChatProfileModal/ReportChatModal.js +21 -15
- package/lib/module/components/Modals/ChatProfileModal/ReportChatModal.js.map +1 -1
- package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js +17 -10
- package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
- package/lib/module/components/Modals/UserSettingsModal/LanguageCard.js +1 -1
- package/lib/module/components/Modals/UserSettingsModal/LanguageCard.js.map +1 -1
- package/lib/module/components/VideoCalls/ActiveCallBar.js +3 -2
- package/lib/module/components/VideoCalls/ActiveCallBar.js.map +1 -1
- package/lib/module/components/VideoCalls/CallButtons.js +19 -11
- package/lib/module/components/VideoCalls/CallButtons.js.map +1 -1
- package/lib/module/components/VideoCalls/CallControlBar.js +113 -0
- package/lib/module/components/VideoCalls/CallControlBar.js.map +1 -0
- package/lib/module/components/VideoCalls/CallVideo.js +43 -0
- package/lib/module/components/VideoCalls/CallVideo.js.map +1 -0
- package/lib/module/components/VideoCalls/GroupCallView.js +275 -0
- package/lib/module/components/VideoCalls/GroupCallView.js.map +1 -0
- package/lib/module/components/VideoCalls/P2PCallRunner.js +147 -0
- package/lib/module/components/VideoCalls/P2PCallRunner.js.map +1 -0
- package/lib/module/components/VideoCalls/VideoCallOverlay.js +38 -14
- package/lib/module/components/VideoCalls/VideoCallOverlay.js.map +1 -1
- package/lib/module/components/VideoCalls/VideoCallSession.js +46 -127
- package/lib/module/components/VideoCalls/VideoCallSession.js.map +1 -1
- package/lib/module/components/VideoCalls/callActions.js +3 -0
- package/lib/module/components/VideoCalls/callActions.js.map +1 -1
- package/lib/module/components/VideoCalls/callElapsed.js +33 -0
- package/lib/module/components/VideoCalls/callElapsed.js.map +1 -0
- package/lib/module/components/VideoCalls/callLive.js +10 -0
- package/lib/module/components/VideoCalls/callLive.js.map +1 -0
- package/lib/module/components/VideoCalls/callSessionController.js +80 -24
- package/lib/module/components/VideoCalls/callSessionController.js.map +1 -1
- package/lib/module/components/VideoCalls/callSessionTypes.js +37 -0
- package/lib/module/components/VideoCalls/callSessionTypes.js.map +1 -0
- package/lib/module/components/VideoCalls/groupLayout.js +36 -0
- package/lib/module/components/VideoCalls/groupLayout.js.map +1 -0
- package/lib/module/components/VideoCalls/p2pCallSession.js +256 -0
- package/lib/module/components/VideoCalls/p2pCallSession.js.map +1 -0
- package/lib/module/components/VideoCalls/useCallKeep.js +8 -2
- package/lib/module/components/VideoCalls/useCallKeep.js.map +1 -1
- package/lib/module/components/VideoCalls/useOngoingCallNotification.js +2 -1
- package/lib/module/components/VideoCalls/useOngoingCallNotification.js.map +1 -1
- package/lib/module/context/xmppProvider.js +2 -1
- package/lib/module/context/xmppProvider.js.map +1 -1
- package/lib/module/helpers/callPush.js +3 -1
- package/lib/module/helpers/callPush.js.map +1 -1
- package/lib/module/helpers/incomingCallFromPush.js +63 -7
- package/lib/module/helpers/incomingCallFromPush.js.map +1 -1
- package/lib/module/helpers/pushPayload.js +5 -0
- package/lib/module/helpers/pushPayload.js.map +1 -1
- package/lib/module/helpers/reportRoom.js +70 -0
- package/lib/module/helpers/reportRoom.js.map +1 -0
- package/lib/module/i18n/strings.js +444 -0
- package/lib/module/i18n/strings.js.map +1 -1
- package/lib/module/main.js +5 -0
- package/lib/module/main.js.map +1 -1
- package/lib/module/networking/api-requests/callState.api.js +66 -0
- package/lib/module/networking/api-requests/callState.api.js.map +1 -0
- package/lib/module/networking/api-requests/push.api.js +29 -6
- package/lib/module/networking/api-requests/push.api.js.map +1 -1
- package/lib/module/networking/api-requests/rooms.api.js +8 -2
- package/lib/module/networking/api-requests/rooms.api.js.map +1 -1
- package/lib/module/networking/api-requests/scheduledCalls.api.js +130 -0
- package/lib/module/networking/api-requests/scheduledCalls.api.js.map +1 -0
- package/lib/module/networking/callTokenStanza.js +210 -50
- package/lib/module/networking/callTokenStanza.js.map +1 -1
- package/lib/module/roomStore/callSlice.js +57 -3
- package/lib/module/roomStore/callSlice.js.map +1 -1
- package/lib/module/services/appBadge.js +74 -0
- package/lib/module/services/appBadge.js.map +1 -0
- package/lib/module/services/languageSettings.js +44 -13
- package/lib/module/services/languageSettings.js.map +1 -1
- package/lib/module/services/pushRegistration.js +21 -5
- package/lib/module/services/pushRegistration.js.map +1 -1
- package/lib/module/version.js +1 -1
- package/lib/typescript/booking/index.d.ts +3 -0
- package/lib/typescript/booking/index.d.ts.map +1 -0
- package/lib/typescript/booking/pendingScheduledCall.d.ts +80 -0
- package/lib/typescript/booking/pendingScheduledCall.d.ts.map +1 -0
- package/lib/typescript/booking/scheduleForm.d.ts +28 -0
- package/lib/typescript/booking/scheduleForm.d.ts.map +1 -0
- package/lib/typescript/booking/scheduledCall.d.ts +52 -0
- package/lib/typescript/booking/scheduledCall.d.ts.map +1 -0
- package/lib/typescript/booking/scheduledCallActions.d.ts +22 -0
- package/lib/typescript/booking/scheduledCallActions.d.ts.map +1 -0
- package/lib/typescript/booking/scheduledCallLink.d.ts +37 -0
- package/lib/typescript/booking/scheduledCallLink.d.ts.map +1 -0
- package/lib/typescript/booking/scheduledCallStore.d.ts +47 -0
- package/lib/typescript/booking/scheduledCallStore.d.ts.map +1 -0
- package/lib/typescript/booking/startRoomCall.d.ts +12 -0
- package/lib/typescript/booking/startRoomCall.d.ts.map +1 -0
- package/lib/typescript/calls/callBusy.d.ts +9 -0
- package/lib/typescript/calls/callBusy.d.ts.map +1 -0
- package/lib/typescript/calls/callDisplay.d.ts +23 -0
- package/lib/typescript/calls/callDisplay.d.ts.map +1 -0
- package/lib/typescript/calls/callMode.d.ts +106 -0
- package/lib/typescript/calls/callMode.d.ts.map +1 -0
- package/lib/typescript/calls/declineReport.d.ts +11 -0
- package/lib/typescript/calls/declineReport.d.ts.map +1 -0
- package/lib/typescript/calls/p2p/P2PCallController.d.ts +150 -0
- package/lib/typescript/calls/p2p/P2PCallController.d.ts.map +1 -0
- package/lib/typescript/calls/p2p/icePolicy.d.ts +37 -0
- package/lib/typescript/calls/p2p/icePolicy.d.ts.map +1 -0
- package/lib/typescript/calls/p2p/negotiator.d.ts +57 -0
- package/lib/typescript/calls/p2p/negotiator.d.ts.map +1 -0
- package/lib/typescript/calls/p2p/p2pRuntime.d.ts +41 -0
- package/lib/typescript/calls/p2p/p2pRuntime.d.ts.map +1 -0
- package/lib/typescript/calls/p2p/signalInbox.d.ts +35 -0
- package/lib/typescript/calls/p2p/signalInbox.d.ts.map +1 -0
- package/lib/typescript/calls/p2p/signalStanza.d.ts +52 -0
- package/lib/typescript/calls/p2p/signalStanza.d.ts.map +1 -0
- package/lib/typescript/calls/p2p/webrtcRuntime.d.ts +32 -0
- package/lib/typescript/calls/p2p/webrtcRuntime.d.ts.map +1 -0
- package/lib/typescript/calls/senderTrust.d.ts +8 -0
- package/lib/typescript/calls/senderTrust.d.ts.map +1 -0
- package/lib/typescript/calls/startCall.d.ts +17 -0
- package/lib/typescript/calls/startCall.d.ts.map +1 -0
- package/lib/typescript/components/Booking/BookingIcons.d.ts +9 -0
- package/lib/typescript/components/Booking/BookingIcons.d.ts.map +1 -0
- package/lib/typescript/components/Booking/ScheduleCallButton.d.ts +11 -0
- package/lib/typescript/components/Booking/ScheduleCallButton.d.ts.map +1 -0
- package/lib/typescript/components/Booking/ScheduleCallSheet.d.ts +4 -0
- package/lib/typescript/components/Booking/ScheduleCallSheet.d.ts.map +1 -0
- package/lib/typescript/components/Booking/ScheduledCallCard.d.ts +14 -0
- package/lib/typescript/components/Booking/ScheduledCallCard.d.ts.map +1 -0
- package/lib/typescript/components/Booking/ScheduledCallHost.d.ts +10 -0
- package/lib/typescript/components/Booking/ScheduledCallHost.d.ts.map +1 -0
- package/lib/typescript/components/Booking/ScheduledCallMessage.d.ts +20 -0
- package/lib/typescript/components/Booking/ScheduledCallMessage.d.ts.map +1 -0
- package/lib/typescript/components/Booking/ScheduledCallPreJoin.d.ts +8 -0
- package/lib/typescript/components/Booking/ScheduledCallPreJoin.d.ts.map +1 -0
- package/lib/typescript/components/Booking/bookingShared.d.ts +199 -0
- package/lib/typescript/components/Booking/bookingShared.d.ts.map +1 -0
- package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/LanguageSelectorButton.d.ts +3 -1
- package/lib/typescript/components/MainComponents/LanguageSelectorButton.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/LanguageSelectorModal.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/MessageContainer.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/ReduxWrapper.d.ts.map +1 -1
- package/lib/typescript/components/Modals/ChatProfileModal/ReportChatModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/UserProfileModal/UserProfileModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/UserSettingsModal/LanguageCard.d.ts.map +1 -1
- package/lib/typescript/components/VideoCalls/ActiveCallBar.d.ts.map +1 -1
- package/lib/typescript/components/VideoCalls/CallButtons.d.ts +2 -1
- package/lib/typescript/components/VideoCalls/CallButtons.d.ts.map +1 -1
- package/lib/typescript/components/VideoCalls/CallControlBar.d.ts +17 -0
- package/lib/typescript/components/VideoCalls/CallControlBar.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/CallVideo.d.ts +23 -0
- package/lib/typescript/components/VideoCalls/CallVideo.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/GroupCallView.d.ts +22 -0
- package/lib/typescript/components/VideoCalls/GroupCallView.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/P2PCallRunner.d.ts +2 -0
- package/lib/typescript/components/VideoCalls/P2PCallRunner.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/VideoCallOverlay.d.ts.map +1 -1
- package/lib/typescript/components/VideoCalls/VideoCallSession.d.ts +10 -12
- package/lib/typescript/components/VideoCalls/VideoCallSession.d.ts.map +1 -1
- package/lib/typescript/components/VideoCalls/callActions.d.ts.map +1 -1
- package/lib/typescript/components/VideoCalls/callElapsed.d.ts +8 -0
- package/lib/typescript/components/VideoCalls/callElapsed.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/callLive.d.ts +9 -0
- package/lib/typescript/components/VideoCalls/callLive.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/callSessionController.d.ts +10 -31
- package/lib/typescript/components/VideoCalls/callSessionController.d.ts.map +1 -1
- package/lib/typescript/components/VideoCalls/callSessionTypes.d.ts +60 -0
- package/lib/typescript/components/VideoCalls/callSessionTypes.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/groupLayout.d.ts +23 -0
- package/lib/typescript/components/VideoCalls/groupLayout.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/p2pCallSession.d.ts +31 -0
- package/lib/typescript/components/VideoCalls/p2pCallSession.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/useCallKeep.d.ts.map +1 -1
- package/lib/typescript/components/VideoCalls/useOngoingCallNotification.d.ts.map +1 -1
- package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
- package/lib/typescript/helpers/callPush.d.ts +11 -1
- package/lib/typescript/helpers/callPush.d.ts.map +1 -1
- package/lib/typescript/helpers/incomingCallFromPush.d.ts.map +1 -1
- package/lib/typescript/helpers/pushPayload.d.ts +1 -1
- package/lib/typescript/helpers/pushPayload.d.ts.map +1 -1
- package/lib/typescript/helpers/reportRoom.d.ts +19 -0
- package/lib/typescript/helpers/reportRoom.d.ts.map +1 -0
- package/lib/typescript/i18n/strings.d.ts.map +1 -1
- package/lib/typescript/main.d.ts +6 -1
- package/lib/typescript/main.d.ts.map +1 -1
- package/lib/typescript/networking/api-requests/callState.api.d.ts +25 -0
- package/lib/typescript/networking/api-requests/callState.api.d.ts.map +1 -0
- package/lib/typescript/networking/api-requests/push.api.d.ts +9 -2
- package/lib/typescript/networking/api-requests/push.api.d.ts.map +1 -1
- package/lib/typescript/networking/api-requests/rooms.api.d.ts +2 -1
- package/lib/typescript/networking/api-requests/rooms.api.d.ts.map +1 -1
- package/lib/typescript/networking/api-requests/scheduledCalls.api.d.ts +86 -0
- package/lib/typescript/networking/api-requests/scheduledCalls.api.d.ts.map +1 -0
- package/lib/typescript/networking/callTokenStanza.d.ts.map +1 -1
- package/lib/typescript/roomStore/callSlice.d.ts +23 -1
- package/lib/typescript/roomStore/callSlice.d.ts.map +1 -1
- package/lib/typescript/services/appBadge.d.ts +3 -0
- package/lib/typescript/services/appBadge.d.ts.map +1 -0
- package/lib/typescript/services/languageSettings.d.ts +7 -0
- package/lib/typescript/services/languageSettings.d.ts.map +1 -1
- package/lib/typescript/services/pushRegistration.d.ts.map +1 -1
- package/lib/typescript/types/types.d.ts +67 -4
- package/lib/typescript/types/types.d.ts.map +1 -1
- package/lib/typescript/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/booking/index.ts +10 -0
- package/src/booking/pendingScheduledCall.ts +272 -0
- package/src/booking/scheduleForm.ts +84 -0
- package/src/booking/scheduledCall.ts +163 -0
- package/src/booking/scheduledCallActions.ts +141 -0
- package/src/booking/scheduledCallLink.ts +145 -0
- package/src/booking/scheduledCallStore.ts +69 -0
- package/src/booking/startRoomCall.ts +103 -0
- package/src/calls/callBusy.ts +10 -0
- package/src/calls/callDisplay.ts +74 -0
- package/src/calls/callMode.ts +286 -0
- package/src/calls/declineReport.ts +16 -0
- package/src/calls/p2p/P2PCallController.ts +813 -0
- package/src/calls/p2p/icePolicy.ts +99 -0
- package/src/calls/p2p/negotiator.ts +186 -0
- package/src/calls/p2p/p2pRuntime.ts +164 -0
- package/src/calls/p2p/signalInbox.ts +117 -0
- package/src/calls/p2p/signalStanza.ts +205 -0
- package/src/calls/p2p/webrtcRuntime.ts +60 -0
- package/src/calls/senderTrust.ts +40 -0
- package/src/calls/startCall.ts +56 -0
- package/src/components/Booking/BookingIcons.tsx +44 -0
- package/src/components/Booking/ScheduleCallButton.tsx +66 -0
- package/src/components/Booking/ScheduleCallSheet.tsx +380 -0
- package/src/components/Booking/ScheduledCallCard.tsx +216 -0
- package/src/components/Booking/ScheduledCallHost.tsx +18 -0
- package/src/components/Booking/ScheduledCallMessage.tsx +62 -0
- package/src/components/Booking/ScheduledCallPreJoin.tsx +230 -0
- package/src/components/Booking/bookingShared.tsx +295 -0
- package/src/components/MainComponents/ChatHeader.tsx +2 -0
- package/src/components/MainComponents/LanguageSelectorButton.tsx +9 -2
- package/src/components/MainComponents/LanguageSelectorModal.tsx +10 -6
- package/src/components/MainComponents/MessageContainer.tsx +15 -0
- package/src/components/MainComponents/ReduxWrapper.tsx +10 -0
- package/src/components/Modals/ChatProfileModal/ReportChatModal.tsx +22 -22
- package/src/components/Modals/UserProfileModal/UserProfileModal.tsx +28 -9
- package/src/components/Modals/UserSettingsModal/LanguageCard.tsx +9 -7
- package/src/components/VideoCalls/ActiveCallBar.tsx +6 -5
- package/src/components/VideoCalls/CallButtons.tsx +23 -12
- package/src/components/VideoCalls/CallControlBar.tsx +131 -0
- package/src/components/VideoCalls/CallVideo.tsx +59 -0
- package/src/components/VideoCalls/GroupCallView.tsx +324 -0
- package/src/components/VideoCalls/P2PCallRunner.tsx +192 -0
- package/src/components/VideoCalls/VideoCallOverlay.tsx +62 -17
- package/src/components/VideoCalls/VideoCallSession.tsx +55 -133
- package/src/components/VideoCalls/callActions.ts +3 -0
- package/src/components/VideoCalls/callElapsed.ts +27 -0
- package/src/components/VideoCalls/callLive.ts +13 -0
- package/src/components/VideoCalls/callSessionController.ts +130 -55
- package/src/components/VideoCalls/callSessionTypes.ts +80 -0
- package/src/components/VideoCalls/groupLayout.ts +48 -0
- package/src/components/VideoCalls/p2pCallSession.ts +291 -0
- package/src/components/VideoCalls/useCallKeep.ts +9 -2
- package/src/components/VideoCalls/useOngoingCallNotification.ts +2 -2
- package/src/context/xmppProvider.tsx +3 -0
- package/src/helpers/callPush.ts +13 -1
- package/src/helpers/incomingCallFromPush.ts +65 -7
- package/src/helpers/pushPayload.ts +9 -1
- package/src/helpers/reportRoom.ts +93 -0
- package/src/i18n/strings.ts +444 -0
- package/src/main.ts +33 -0
- package/src/networking/api-requests/callState.api.ts +83 -0
- package/src/networking/api-requests/push.api.ts +28 -3
- package/src/networking/api-requests/rooms.api.ts +12 -3
- package/src/networking/api-requests/scheduledCalls.api.ts +232 -0
- package/src/networking/callTokenStanza.ts +247 -50
- package/src/roomStore/callSlice.ts +86 -2
- package/src/services/appBadge.ts +85 -0
- package/src/services/languageSettings.ts +43 -8
- package/src/services/pushRegistration.ts +34 -3
- package/src/types/types.ts +70 -4
- package/src/version.ts +1 -1
|
@@ -0,0 +1,813 @@
|
|
|
1
|
+
import type { IceServerConfig } from '../callMode';
|
|
2
|
+
import { IceAction, IceRestartPolicy, P2PConnState } from './icePolicy';
|
|
3
|
+
import { NegotiatorCommand, P2PNegotiator, P2PRole } from './negotiator';
|
|
4
|
+
import type {
|
|
5
|
+
P2PCandidatePayload,
|
|
6
|
+
P2PSdpPayload,
|
|
7
|
+
P2PSignal,
|
|
8
|
+
P2PSignalName,
|
|
9
|
+
P2PSignalPayload,
|
|
10
|
+
} from './signalStanza';
|
|
11
|
+
|
|
12
|
+
// One peer to peer call, from getUserMedia to hang-up. Everything that talks
|
|
13
|
+
// to the outside world is injected (RTCPeerConnection, getUserMedia, the
|
|
14
|
+
// signal transport, ICE credential refresh), so the class is plain TypeScript
|
|
15
|
+
// and runs under a fake connection in unit tests. The decisions live in the
|
|
16
|
+
// small pure modules next to it (negotiator, icePolicy, signalStanza); this
|
|
17
|
+
// class only wires them to a real connection and cleans up after itself.
|
|
18
|
+
//
|
|
19
|
+
// Media encryption: a WebRTC peer connection already encrypts media between
|
|
20
|
+
// the two peers with DTLS-SRTP, so the LiveKit insertable-streams E2EE layer
|
|
21
|
+
// (and its OMEMO key exchange) is deliberately NOT applied on this path.
|
|
22
|
+
|
|
23
|
+
export type P2PStatus =
|
|
24
|
+
| 'preparing'
|
|
25
|
+
| 'waiting'
|
|
26
|
+
| 'connecting'
|
|
27
|
+
| 'connected'
|
|
28
|
+
| 'reconnecting'
|
|
29
|
+
| 'failed'
|
|
30
|
+
| 'ended';
|
|
31
|
+
|
|
32
|
+
export interface P2PSnapshot {
|
|
33
|
+
status: P2PStatus;
|
|
34
|
+
localStream: MediaStream | null;
|
|
35
|
+
remoteStream: MediaStream | null;
|
|
36
|
+
micEnabled: boolean;
|
|
37
|
+
camEnabled: boolean;
|
|
38
|
+
remoteVideoLive: boolean;
|
|
39
|
+
/** Dismissible, non-fatal hint (e.g. the camera could not be re-enabled). */
|
|
40
|
+
notice: string | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface P2PCallParams {
|
|
44
|
+
callId: string;
|
|
45
|
+
role: P2PRole;
|
|
46
|
+
kind: 'audio' | 'video';
|
|
47
|
+
/** Peer's xmpp localpart; signals from anyone else are ignored. */
|
|
48
|
+
peerLocal: string;
|
|
49
|
+
iceServers: IceServerConfig[];
|
|
50
|
+
startWithMicOn?: boolean;
|
|
51
|
+
startWithCameraOn?: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface P2PCallDeps {
|
|
55
|
+
createPeerConnection: (config: RTCConfiguration) => RTCPeerConnection;
|
|
56
|
+
getUserMedia: (constraints: MediaStreamConstraints) => Promise<MediaStream>;
|
|
57
|
+
createMediaStream: () => MediaStream;
|
|
58
|
+
/** Fresh credentials; null/empty keeps the servers already in use. */
|
|
59
|
+
fetchIceServers: () => Promise<IceServerConfig[] | null>;
|
|
60
|
+
sendSignal: (signal: P2PSignalName, payload: P2PSignalPayload) => void;
|
|
61
|
+
log?: (message: string, detail?: unknown) => void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface P2PCallEvents {
|
|
65
|
+
/** The offer/answer exchange completed: media is now negotiating. */
|
|
66
|
+
onNegotiated?: () => void;
|
|
67
|
+
/** Media is flowing for the first time. */
|
|
68
|
+
onConnected?: () => void;
|
|
69
|
+
/** The peer ended the call (hang up, decline, their own failure). */
|
|
70
|
+
onRemoteEnd?: (reason: string) => void;
|
|
71
|
+
/** The call failed locally; the message is meant for the user. */
|
|
72
|
+
onFailed?: (message: string) => void;
|
|
73
|
+
/**
|
|
74
|
+
* Called exactly once when the call's resources are released. `started` is
|
|
75
|
+
* false for a callee that never accepted (nothing was negotiated).
|
|
76
|
+
*/
|
|
77
|
+
onDisposed?: (info: { started: boolean }) => void;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface P2PTimings {
|
|
81
|
+
connectTimeoutMs: number;
|
|
82
|
+
offerResendMs: number[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const DEFAULT_TIMINGS: P2PTimings = {
|
|
86
|
+
connectTimeoutMs: 25_000,
|
|
87
|
+
// While nobody has answered, the offer and candidates are sent again: a
|
|
88
|
+
// callee that was still reconnecting (woken by push) missed the first copy,
|
|
89
|
+
// and the stanzas are not stored for later. Duplicates are no-ops there.
|
|
90
|
+
offerResendMs: [4_000, 10_000],
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const describeMediaError = (error: unknown): string => {
|
|
94
|
+
const name = (error as { name?: string })?.name || '';
|
|
95
|
+
const text = String((error as Error)?.message || '').toLowerCase();
|
|
96
|
+
if (
|
|
97
|
+
name === 'NotAllowedError' ||
|
|
98
|
+
name === 'SecurityError' ||
|
|
99
|
+
text.includes('permission') ||
|
|
100
|
+
text.includes('denied')
|
|
101
|
+
) {
|
|
102
|
+
return 'Camera or microphone permission denied';
|
|
103
|
+
}
|
|
104
|
+
if (name === 'NotFoundError' || name === 'OverconstrainedError') {
|
|
105
|
+
return 'No camera or microphone found on this device';
|
|
106
|
+
}
|
|
107
|
+
if (name === 'NotReadableError' || name === 'AbortError') {
|
|
108
|
+
return 'Your camera or microphone is already in use by another app';
|
|
109
|
+
}
|
|
110
|
+
return 'Could not access your camera or microphone';
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* What to show when the PEER ended the call for a reason that is a failure
|
|
115
|
+
* rather than a normal hang up / decline. Null means "just close the call".
|
|
116
|
+
*/
|
|
117
|
+
export const remoteEndNotice = (reason: string): string | null => {
|
|
118
|
+
switch (reason) {
|
|
119
|
+
case 'media-error':
|
|
120
|
+
return 'The other person could not access their camera or microphone';
|
|
121
|
+
case 'connect-timeout':
|
|
122
|
+
case 'connection-failed':
|
|
123
|
+
case 'negotiation-error':
|
|
124
|
+
case 'setup-error':
|
|
125
|
+
return 'The call could not be connected';
|
|
126
|
+
default:
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const AUDIO_CONSTRAINTS: MediaTrackConstraints = {
|
|
132
|
+
echoCancellation: true,
|
|
133
|
+
noiseSuppression: true,
|
|
134
|
+
autoGainControl: true,
|
|
135
|
+
};
|
|
136
|
+
const VIDEO_CONSTRAINTS: MediaTrackConstraints = {
|
|
137
|
+
width: { ideal: 1280 },
|
|
138
|
+
height: { ideal: 720 },
|
|
139
|
+
facingMode: 'user',
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export class P2PCallController {
|
|
143
|
+
readonly callId: string;
|
|
144
|
+
readonly role: P2PRole;
|
|
145
|
+
readonly kind: 'audio' | 'video';
|
|
146
|
+
|
|
147
|
+
private readonly params: P2PCallParams;
|
|
148
|
+
private readonly deps: P2PCallDeps;
|
|
149
|
+
private readonly events: P2PCallEvents;
|
|
150
|
+
private readonly timings: P2PTimings;
|
|
151
|
+
private readonly negotiator: P2PNegotiator;
|
|
152
|
+
private readonly policy = new IceRestartPolicy();
|
|
153
|
+
|
|
154
|
+
private snapshot: P2PSnapshot;
|
|
155
|
+
private readonly listeners = new Set<() => void>();
|
|
156
|
+
|
|
157
|
+
private pc: RTCPeerConnection | null = null;
|
|
158
|
+
private localStream: MediaStream | null = null;
|
|
159
|
+
private audioTrack: MediaStreamTrack | null = null;
|
|
160
|
+
private videoTrack: MediaStreamTrack | null = null;
|
|
161
|
+
private videoSender: RTCRtpSender | null = null;
|
|
162
|
+
private ownedTracks: MediaStreamTrack[] = [];
|
|
163
|
+
private remoteStream: MediaStream | null = null;
|
|
164
|
+
private remoteCleanups: Array<() => void> = [];
|
|
165
|
+
|
|
166
|
+
private queue: Promise<void> = Promise.resolve();
|
|
167
|
+
private timers = new Set<ReturnType<typeof setTimeout>>();
|
|
168
|
+
private graceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
169
|
+
private deadlineTimer: ReturnType<typeof setTimeout> | null = null;
|
|
170
|
+
private connectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
171
|
+
|
|
172
|
+
private started = false;
|
|
173
|
+
private disposed = false;
|
|
174
|
+
private endSent = false;
|
|
175
|
+
private connectedOnce = false;
|
|
176
|
+
private negotiated = false;
|
|
177
|
+
private cameraBusy = false;
|
|
178
|
+
private lastConn: P2PConnState | null = null;
|
|
179
|
+
|
|
180
|
+
// Outgoing candidates wait until the offer/answer they belong to is out, so
|
|
181
|
+
// the peer never sees a candidate before its description.
|
|
182
|
+
private candidateGateOpen = false;
|
|
183
|
+
private heldOutgoingCandidates: P2PCandidatePayload[] = [];
|
|
184
|
+
private sentCandidates: P2PCandidatePayload[] = [];
|
|
185
|
+
private lastOffer: P2PSdpPayload | null = null;
|
|
186
|
+
|
|
187
|
+
constructor(
|
|
188
|
+
params: P2PCallParams,
|
|
189
|
+
deps: P2PCallDeps,
|
|
190
|
+
events: P2PCallEvents = {},
|
|
191
|
+
timings: Partial<P2PTimings> = {}
|
|
192
|
+
) {
|
|
193
|
+
this.params = params;
|
|
194
|
+
this.deps = deps;
|
|
195
|
+
this.events = events;
|
|
196
|
+
this.timings = { ...DEFAULT_TIMINGS, ...timings };
|
|
197
|
+
this.callId = params.callId;
|
|
198
|
+
this.role = params.role;
|
|
199
|
+
this.kind = params.kind;
|
|
200
|
+
this.negotiator = new P2PNegotiator({
|
|
201
|
+
callId: params.callId,
|
|
202
|
+
role: params.role,
|
|
203
|
+
peerLocal: params.peerLocal,
|
|
204
|
+
});
|
|
205
|
+
this.snapshot = {
|
|
206
|
+
status: 'preparing',
|
|
207
|
+
localStream: null,
|
|
208
|
+
remoteStream: null,
|
|
209
|
+
micEnabled: params.startWithMicOn !== false,
|
|
210
|
+
camEnabled: params.kind === 'video' && params.startWithCameraOn !== false,
|
|
211
|
+
remoteVideoLive: false,
|
|
212
|
+
notice: null,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ---- observable state (useSyncExternalStore friendly) -------------------
|
|
217
|
+
|
|
218
|
+
subscribe = (listener: () => void): (() => void) => {
|
|
219
|
+
this.listeners.add(listener);
|
|
220
|
+
return () => {
|
|
221
|
+
this.listeners.delete(listener);
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
getSnapshot = (): P2PSnapshot => this.snapshot;
|
|
226
|
+
|
|
227
|
+
private update(patch: Partial<P2PSnapshot>): void {
|
|
228
|
+
this.snapshot = { ...this.snapshot, ...patch };
|
|
229
|
+
for (const listener of Array.from(this.listeners)) listener();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private log(message: string, detail?: unknown): void {
|
|
233
|
+
this.deps.log?.(`[p2p ${this.callId}] ${message}`, detail);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ---- timers -------------------------------------------------------------
|
|
237
|
+
|
|
238
|
+
private later(fn: () => void, ms: number): ReturnType<typeof setTimeout> {
|
|
239
|
+
const id = setTimeout(() => {
|
|
240
|
+
this.timers.delete(id);
|
|
241
|
+
if (!this.disposed) fn();
|
|
242
|
+
}, ms);
|
|
243
|
+
this.timers.add(id);
|
|
244
|
+
return id;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private cancel(id: ReturnType<typeof setTimeout> | null): void {
|
|
248
|
+
if (id === null) return;
|
|
249
|
+
clearTimeout(id);
|
|
250
|
+
this.timers.delete(id);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ---- lifecycle ----------------------------------------------------------
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Caller: get media, build the connection, offer. Callee: get media, build
|
|
257
|
+
* the connection, then apply the offer that may already be waiting. Safe to
|
|
258
|
+
* call once; every failure ends the call with a message instead of throwing.
|
|
259
|
+
*/
|
|
260
|
+
async start(): Promise<void> {
|
|
261
|
+
if (this.started || this.disposed) return;
|
|
262
|
+
this.started = true;
|
|
263
|
+
this.update({ status: 'preparing' });
|
|
264
|
+
|
|
265
|
+
// Credentials are fetched while the permission prompt is open.
|
|
266
|
+
const icePromise: Promise<IceServerConfig[]> =
|
|
267
|
+
this.params.iceServers.length > 0
|
|
268
|
+
? Promise.resolve(this.params.iceServers)
|
|
269
|
+
: this.deps
|
|
270
|
+
.fetchIceServers()
|
|
271
|
+
.then((list) => list || [])
|
|
272
|
+
.catch(() => []);
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
await this.acquireMedia();
|
|
276
|
+
} catch (error) {
|
|
277
|
+
this.log('getUserMedia failed', error);
|
|
278
|
+
this.fail(describeMediaError(error), 'media-error');
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (this.disposed) {
|
|
282
|
+
this.releaseMedia();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const servers = await icePromise;
|
|
287
|
+
if (this.disposed) return;
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
this.createConnection(servers);
|
|
291
|
+
} catch (error) {
|
|
292
|
+
this.log('peer connection setup failed', error);
|
|
293
|
+
this.fail('Could not start the call', 'setup-error');
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
this.enqueue(async () => {
|
|
298
|
+
await this.runCommands(this.negotiator.markReady());
|
|
299
|
+
if (this.role === 'caller' && !this.disposed) {
|
|
300
|
+
await this.sendOffer(false);
|
|
301
|
+
this.update({ status: 'waiting' });
|
|
302
|
+
this.scheduleOfferResends();
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Feed a signal from the peer (already routed by callId). */
|
|
308
|
+
receive(signal: P2PSignal): void {
|
|
309
|
+
if (this.disposed) return;
|
|
310
|
+
const commands = this.negotiator.receive(signal);
|
|
311
|
+
// An end must act immediately: it can arrive while the permission prompt
|
|
312
|
+
// is still open, before the connection (and so the job queue) is running.
|
|
313
|
+
const end = commands.find((c) => c.type === 'remote-end');
|
|
314
|
+
if (end && end.type === 'remote-end') {
|
|
315
|
+
this.handleRemoteEnd(end.reason);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (commands.length > 0) this.enqueue(() => this.runCommands(commands));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Local user hung up (or cancelled): tell the peer and release everything. */
|
|
322
|
+
hangup(reason = 'hangup'): void {
|
|
323
|
+
if (this.disposed) return;
|
|
324
|
+
this.sendEnd(reason);
|
|
325
|
+
this.dispose();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Release every resource: timers, listeners, media tracks, the connection.
|
|
330
|
+
* Idempotent, safe from any exit path. Tells the peer unless it is already
|
|
331
|
+
* known to be over.
|
|
332
|
+
*/
|
|
333
|
+
dispose(): void {
|
|
334
|
+
if (this.disposed) return;
|
|
335
|
+
this.disposed = true;
|
|
336
|
+
if (this.started && !this.negotiator.isEnded) this.sendEnd('hangup');
|
|
337
|
+
|
|
338
|
+
for (const id of this.timers) clearTimeout(id);
|
|
339
|
+
this.timers.clear();
|
|
340
|
+
this.negotiator.stop();
|
|
341
|
+
|
|
342
|
+
for (const cleanup of this.remoteCleanups) {
|
|
343
|
+
try {
|
|
344
|
+
cleanup();
|
|
345
|
+
} catch {
|
|
346
|
+
/* listener already gone */
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
this.remoteCleanups = [];
|
|
350
|
+
|
|
351
|
+
const pc = this.pc;
|
|
352
|
+
this.pc = null;
|
|
353
|
+
if (pc) {
|
|
354
|
+
pc.onicecandidate = null;
|
|
355
|
+
pc.ontrack = null;
|
|
356
|
+
pc.onconnectionstatechange = null;
|
|
357
|
+
pc.oniceconnectionstatechange = null;
|
|
358
|
+
try {
|
|
359
|
+
pc.close();
|
|
360
|
+
} catch {
|
|
361
|
+
/* already closed */
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
this.releaseMedia();
|
|
365
|
+
|
|
366
|
+
this.snapshot = {
|
|
367
|
+
...this.snapshot,
|
|
368
|
+
status: this.snapshot.status === 'failed' ? 'failed' : 'ended',
|
|
369
|
+
localStream: null,
|
|
370
|
+
remoteStream: null,
|
|
371
|
+
remoteVideoLive: false,
|
|
372
|
+
};
|
|
373
|
+
for (const listener of Array.from(this.listeners)) listener();
|
|
374
|
+
this.listeners.clear();
|
|
375
|
+
this.events.onDisposed?.({ started: this.started });
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
private sendEnd(reason: string): void {
|
|
379
|
+
if (this.endSent) return;
|
|
380
|
+
this.endSent = true;
|
|
381
|
+
try {
|
|
382
|
+
this.deps.sendSignal('end', { reason });
|
|
383
|
+
} catch (error) {
|
|
384
|
+
this.log('end signal failed', error);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private fail(message: string, reason: string): void {
|
|
389
|
+
if (this.disposed) return;
|
|
390
|
+
this.sendEnd(reason);
|
|
391
|
+
this.snapshot = { ...this.snapshot, status: 'failed', notice: message };
|
|
392
|
+
this.events.onFailed?.(message);
|
|
393
|
+
this.dispose();
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
private handleRemoteEnd(reason: string): void {
|
|
397
|
+
if (this.disposed) return;
|
|
398
|
+
// The peer already knows; do not echo an end back.
|
|
399
|
+
this.endSent = true;
|
|
400
|
+
this.snapshot = { ...this.snapshot, status: 'ended' };
|
|
401
|
+
this.events.onRemoteEnd?.(reason);
|
|
402
|
+
this.dispose();
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// ---- media --------------------------------------------------------------
|
|
406
|
+
|
|
407
|
+
private async acquireMedia(): Promise<void> {
|
|
408
|
+
const wantVideo =
|
|
409
|
+
this.kind === 'video' && this.params.startWithCameraOn !== false;
|
|
410
|
+
const stream = await this.deps.getUserMedia({
|
|
411
|
+
audio: AUDIO_CONSTRAINTS,
|
|
412
|
+
video: wantVideo ? VIDEO_CONSTRAINTS : false,
|
|
413
|
+
});
|
|
414
|
+
this.localStream = stream;
|
|
415
|
+
this.ownedTracks = stream.getTracks();
|
|
416
|
+
this.audioTrack = stream.getAudioTracks()[0] || null;
|
|
417
|
+
this.videoTrack = stream.getVideoTracks()[0] || null;
|
|
418
|
+
if (this.audioTrack && this.params.startWithMicOn === false) {
|
|
419
|
+
this.audioTrack.enabled = false;
|
|
420
|
+
}
|
|
421
|
+
this.update({
|
|
422
|
+
localStream: stream,
|
|
423
|
+
micEnabled: !!this.audioTrack && this.audioTrack.enabled,
|
|
424
|
+
camEnabled: !!this.videoTrack,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
private releaseMedia(): void {
|
|
429
|
+
const tracks = new Set<MediaStreamTrack>(this.ownedTracks);
|
|
430
|
+
this.localStream?.getTracks().forEach((t) => tracks.add(t));
|
|
431
|
+
for (const track of tracks) {
|
|
432
|
+
try {
|
|
433
|
+
track.stop();
|
|
434
|
+
} catch {
|
|
435
|
+
/* already stopped */
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
this.ownedTracks = [];
|
|
439
|
+
this.audioTrack = null;
|
|
440
|
+
this.videoTrack = null;
|
|
441
|
+
this.videoSender = null;
|
|
442
|
+
this.localStream = null;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
toggleMic(): void {
|
|
446
|
+
if (this.disposed || !this.audioTrack) return;
|
|
447
|
+
this.audioTrack.enabled = !this.audioTrack.enabled;
|
|
448
|
+
this.update({ micEnabled: this.audioTrack.enabled });
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Turning the camera off stops sending video and releases the device (the
|
|
453
|
+
* camera light goes out); turning it on opens the camera again and swaps it
|
|
454
|
+
* into the existing sender, so no renegotiation is needed.
|
|
455
|
+
*/
|
|
456
|
+
async toggleCamera(): Promise<void> {
|
|
457
|
+
if (
|
|
458
|
+
this.disposed ||
|
|
459
|
+
this.kind !== 'video' ||
|
|
460
|
+
!this.videoSender ||
|
|
461
|
+
!this.localStream ||
|
|
462
|
+
this.cameraBusy
|
|
463
|
+
) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
this.cameraBusy = true;
|
|
467
|
+
try {
|
|
468
|
+
if (this.videoTrack) {
|
|
469
|
+
const track = this.videoTrack;
|
|
470
|
+
this.videoTrack = null;
|
|
471
|
+
try {
|
|
472
|
+
await this.videoSender.replaceTrack(null);
|
|
473
|
+
} catch (error) {
|
|
474
|
+
this.log('replaceTrack(null) failed', error);
|
|
475
|
+
}
|
|
476
|
+
try {
|
|
477
|
+
this.localStream.removeTrack(track);
|
|
478
|
+
} catch {
|
|
479
|
+
/* not in the stream */
|
|
480
|
+
}
|
|
481
|
+
track.stop();
|
|
482
|
+
this.ownedTracks = this.ownedTracks.filter((t) => t !== track);
|
|
483
|
+
this.update({ camEnabled: false, notice: null });
|
|
484
|
+
} else {
|
|
485
|
+
const media = await this.deps.getUserMedia({
|
|
486
|
+
video: VIDEO_CONSTRAINTS,
|
|
487
|
+
});
|
|
488
|
+
const track = media.getVideoTracks()[0];
|
|
489
|
+
if (!track) return;
|
|
490
|
+
if (this.disposed || !this.videoSender || !this.localStream) {
|
|
491
|
+
track.stop();
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
await this.videoSender.replaceTrack(track);
|
|
495
|
+
this.localStream.addTrack(track);
|
|
496
|
+
this.videoTrack = track;
|
|
497
|
+
this.ownedTracks.push(track);
|
|
498
|
+
this.update({ camEnabled: true, notice: null });
|
|
499
|
+
}
|
|
500
|
+
} catch (error) {
|
|
501
|
+
this.log('camera toggle failed', error);
|
|
502
|
+
this.update({ notice: describeMediaError(error) });
|
|
503
|
+
} finally {
|
|
504
|
+
this.cameraBusy = false;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
dismissNotice(): void {
|
|
509
|
+
if (this.snapshot.notice) this.update({ notice: null });
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// ---- peer connection ----------------------------------------------------
|
|
513
|
+
|
|
514
|
+
private createConnection(servers: IceServerConfig[]): void {
|
|
515
|
+
const pc = this.deps.createPeerConnection({
|
|
516
|
+
iceServers: servers as RTCIceServer[],
|
|
517
|
+
iceCandidatePoolSize: 2,
|
|
518
|
+
});
|
|
519
|
+
this.pc = pc;
|
|
520
|
+
|
|
521
|
+
pc.onicecandidate = (event) => {
|
|
522
|
+
const c = event.candidate;
|
|
523
|
+
if (!c || !c.candidate) return;
|
|
524
|
+
const json: P2PCandidatePayload =
|
|
525
|
+
typeof c.toJSON === 'function'
|
|
526
|
+
? (c.toJSON() as P2PCandidatePayload)
|
|
527
|
+
: {
|
|
528
|
+
candidate: c.candidate,
|
|
529
|
+
sdpMid: c.sdpMid,
|
|
530
|
+
sdpMLineIndex: c.sdpMLineIndex,
|
|
531
|
+
};
|
|
532
|
+
if (this.candidateGateOpen) this.emitCandidate(json);
|
|
533
|
+
else this.heldOutgoingCandidates.push(json);
|
|
534
|
+
};
|
|
535
|
+
pc.ontrack = (event) => this.onRemoteTrack(event.track);
|
|
536
|
+
pc.onconnectionstatechange = () => this.onConnectionState();
|
|
537
|
+
pc.oniceconnectionstatechange = () => this.onConnectionState();
|
|
538
|
+
|
|
539
|
+
const stream = this.localStream;
|
|
540
|
+
if (stream) {
|
|
541
|
+
if (this.audioTrack) pc.addTrack(this.audioTrack, stream);
|
|
542
|
+
if (this.kind === 'video') {
|
|
543
|
+
if (this.videoTrack) {
|
|
544
|
+
this.videoSender = pc.addTrack(this.videoTrack, stream);
|
|
545
|
+
} else {
|
|
546
|
+
// Camera starts off: keep a video section so it can be turned on
|
|
547
|
+
// later without renegotiating.
|
|
548
|
+
this.videoSender = pc.addTransceiver('video', {
|
|
549
|
+
direction: 'sendrecv',
|
|
550
|
+
}).sender;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
private emitCandidate(candidate: P2PCandidatePayload): void {
|
|
557
|
+
this.sentCandidates.push(candidate);
|
|
558
|
+
this.deps.sendSignal('candidate', candidate);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
private openCandidateGate(): void {
|
|
562
|
+
this.candidateGateOpen = true;
|
|
563
|
+
const held = this.heldOutgoingCandidates;
|
|
564
|
+
this.heldOutgoingCandidates = [];
|
|
565
|
+
for (const candidate of held) this.emitCandidate(candidate);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
private onRemoteTrack(track: MediaStreamTrack): void {
|
|
569
|
+
if (!this.remoteStream) this.remoteStream = this.deps.createMediaStream();
|
|
570
|
+
const stream = this.remoteStream;
|
|
571
|
+
if (!stream.getTracks().includes(track)) stream.addTrack(track);
|
|
572
|
+
|
|
573
|
+
const refresh = () => this.update({ remoteVideoLive: this.remoteVideoLive() });
|
|
574
|
+
track.addEventListener('mute', refresh);
|
|
575
|
+
track.addEventListener('unmute', refresh);
|
|
576
|
+
track.addEventListener('ended', refresh);
|
|
577
|
+
this.remoteCleanups.push(() => {
|
|
578
|
+
track.removeEventListener('mute', refresh);
|
|
579
|
+
track.removeEventListener('unmute', refresh);
|
|
580
|
+
track.removeEventListener('ended', refresh);
|
|
581
|
+
});
|
|
582
|
+
this.update({
|
|
583
|
+
remoteStream: stream,
|
|
584
|
+
remoteVideoLive: this.remoteVideoLive(),
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
private remoteVideoLive(): boolean {
|
|
589
|
+
return (this.remoteStream?.getVideoTracks() || []).some(
|
|
590
|
+
(t) => t.readyState === 'live' && !t.muted
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// ---- negotiation --------------------------------------------------------
|
|
595
|
+
|
|
596
|
+
private enqueue(job: () => Promise<void>): void {
|
|
597
|
+
this.queue = this.queue
|
|
598
|
+
.then(() => (this.disposed ? undefined : job()))
|
|
599
|
+
.catch((error) => {
|
|
600
|
+
this.log('negotiation step failed', error);
|
|
601
|
+
// A broken negotiation cannot recover on its own.
|
|
602
|
+
this.fail('Could not set up the call', 'negotiation-error');
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
private async sendOffer(iceRestart: boolean): Promise<void> {
|
|
607
|
+
const pc = this.pc;
|
|
608
|
+
if (!pc || this.disposed) return;
|
|
609
|
+
this.candidateGateOpen = false;
|
|
610
|
+
const offer = await pc.createOffer(iceRestart ? { iceRestart: true } : undefined);
|
|
611
|
+
await pc.setLocalDescription(offer);
|
|
612
|
+
if (this.disposed) return;
|
|
613
|
+
const sdp = pc.localDescription?.sdp || offer.sdp || '';
|
|
614
|
+
this.negotiator.markLocalOffer();
|
|
615
|
+
this.lastOffer = { type: 'offer', sdp };
|
|
616
|
+
this.sentCandidates = [];
|
|
617
|
+
this.deps.sendSignal('offer', this.lastOffer);
|
|
618
|
+
this.openCandidateGate();
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
private scheduleOfferResends(): void {
|
|
622
|
+
for (const ms of this.timings.offerResendMs) {
|
|
623
|
+
this.later(() => {
|
|
624
|
+
if (!this.negotiator.awaitingAnswer || !this.lastOffer) return;
|
|
625
|
+
this.log('re-sending offer');
|
|
626
|
+
this.deps.sendSignal('offer', this.lastOffer);
|
|
627
|
+
for (const candidate of this.sentCandidates) {
|
|
628
|
+
this.deps.sendSignal('candidate', candidate);
|
|
629
|
+
}
|
|
630
|
+
}, ms);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
private async runCommands(commands: NegotiatorCommand[]): Promise<void> {
|
|
635
|
+
for (const command of commands) {
|
|
636
|
+
if (this.disposed || !this.pc) return;
|
|
637
|
+
switch (command.type) {
|
|
638
|
+
case 'apply-offer':
|
|
639
|
+
await this.applyOffer(command);
|
|
640
|
+
break;
|
|
641
|
+
case 'apply-answer':
|
|
642
|
+
await this.pc.setRemoteDescription({
|
|
643
|
+
type: 'answer',
|
|
644
|
+
sdp: command.sdp,
|
|
645
|
+
});
|
|
646
|
+
await this.runCommands(this.negotiator.markRemoteApplied());
|
|
647
|
+
this.onNegotiated();
|
|
648
|
+
break;
|
|
649
|
+
case 'add-candidate':
|
|
650
|
+
try {
|
|
651
|
+
await this.pc.addIceCandidate(command.candidate);
|
|
652
|
+
} catch (error) {
|
|
653
|
+
// A candidate from a superseded ICE session; harmless.
|
|
654
|
+
this.log('addIceCandidate ignored', error);
|
|
655
|
+
}
|
|
656
|
+
break;
|
|
657
|
+
case 'remote-end':
|
|
658
|
+
// Handled synchronously in receive(); never queued.
|
|
659
|
+
break;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
private async applyOffer(
|
|
665
|
+
command: Extract<NegotiatorCommand, { type: 'apply-offer' }>
|
|
666
|
+
): Promise<void> {
|
|
667
|
+
const pc = this.pc;
|
|
668
|
+
if (!pc) return;
|
|
669
|
+
if (command.rollback && pc.signalingState === 'have-local-offer') {
|
|
670
|
+
await pc.setLocalDescription({ type: 'rollback' });
|
|
671
|
+
}
|
|
672
|
+
await pc.setRemoteDescription({ type: 'offer', sdp: command.sdp });
|
|
673
|
+
await this.runCommands(this.negotiator.markRemoteApplied());
|
|
674
|
+
if (this.disposed || !this.pc) return;
|
|
675
|
+
|
|
676
|
+
this.candidateGateOpen = false;
|
|
677
|
+
const answer = await pc.createAnswer();
|
|
678
|
+
await pc.setLocalDescription(answer);
|
|
679
|
+
if (this.disposed) return;
|
|
680
|
+
this.deps.sendSignal('answer', {
|
|
681
|
+
type: 'answer',
|
|
682
|
+
sdp: pc.localDescription?.sdp || answer.sdp || '',
|
|
683
|
+
});
|
|
684
|
+
this.openCandidateGate();
|
|
685
|
+
if (command.initial) this.onNegotiated();
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
private onNegotiated(): void {
|
|
689
|
+
if (this.negotiated || this.disposed) return;
|
|
690
|
+
this.negotiated = true;
|
|
691
|
+
if (!this.connectedOnce) {
|
|
692
|
+
this.update({ status: 'connecting' });
|
|
693
|
+
this.connectTimer = this.later(() => {
|
|
694
|
+
if (!this.connectedOnce) {
|
|
695
|
+
this.fail('Could not connect the call', 'connect-timeout');
|
|
696
|
+
}
|
|
697
|
+
}, this.timings.connectTimeoutMs);
|
|
698
|
+
}
|
|
699
|
+
this.events.onNegotiated?.();
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// ---- connection health --------------------------------------------------
|
|
703
|
+
|
|
704
|
+
private connState(): P2PConnState {
|
|
705
|
+
const pc = this.pc;
|
|
706
|
+
if (!pc) return 'closed';
|
|
707
|
+
const cs = pc.connectionState as P2PConnState | undefined;
|
|
708
|
+
if (cs) return cs;
|
|
709
|
+
// Browsers without connectionState: map the ICE state.
|
|
710
|
+
switch (pc.iceConnectionState) {
|
|
711
|
+
case 'connected':
|
|
712
|
+
case 'completed':
|
|
713
|
+
return 'connected';
|
|
714
|
+
case 'disconnected':
|
|
715
|
+
return 'disconnected';
|
|
716
|
+
case 'failed':
|
|
717
|
+
return 'failed';
|
|
718
|
+
case 'closed':
|
|
719
|
+
return 'closed';
|
|
720
|
+
case 'checking':
|
|
721
|
+
return 'connecting';
|
|
722
|
+
default:
|
|
723
|
+
return 'new';
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
private onConnectionState(): void {
|
|
728
|
+
if (this.disposed) return;
|
|
729
|
+
const state = this.connState();
|
|
730
|
+
if (state === this.lastConn) return;
|
|
731
|
+
this.lastConn = state;
|
|
732
|
+
this.log(`connection ${state}`);
|
|
733
|
+
|
|
734
|
+
if (state === 'connected') {
|
|
735
|
+
this.cancel(this.graceTimer);
|
|
736
|
+
this.cancel(this.deadlineTimer);
|
|
737
|
+
this.cancel(this.connectTimer);
|
|
738
|
+
this.graceTimer = this.deadlineTimer = this.connectTimer = null;
|
|
739
|
+
this.runIceAction(this.policy.onState(state));
|
|
740
|
+
this.update({ status: 'connected' });
|
|
741
|
+
if (!this.connectedOnce) {
|
|
742
|
+
this.connectedOnce = true;
|
|
743
|
+
this.events.onConnected?.();
|
|
744
|
+
}
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
if (state === 'connecting') {
|
|
748
|
+
if (this.connectedOnce) this.update({ status: 'reconnecting' });
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
if (state === 'disconnected' || state === 'failed') {
|
|
752
|
+
if (this.connectedOnce) this.update({ status: 'reconnecting' });
|
|
753
|
+
this.runIceAction(this.policy.onState(state));
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
private runIceAction(action: IceAction): void {
|
|
758
|
+
switch (action.type) {
|
|
759
|
+
case 'wait':
|
|
760
|
+
this.cancel(this.graceTimer);
|
|
761
|
+
this.graceTimer = this.later(() => {
|
|
762
|
+
this.graceTimer = null;
|
|
763
|
+
this.runIceAction(this.policy.onGraceElapsed());
|
|
764
|
+
}, action.ms);
|
|
765
|
+
break;
|
|
766
|
+
case 'restart':
|
|
767
|
+
this.cancel(this.graceTimer);
|
|
768
|
+
this.graceTimer = null;
|
|
769
|
+
this.cancel(this.deadlineTimer);
|
|
770
|
+
this.deadlineTimer = this.later(() => {
|
|
771
|
+
this.deadlineTimer = null;
|
|
772
|
+
const down = this.lastConn !== 'connected';
|
|
773
|
+
this.runIceAction(this.policy.onRestartDeadline(down));
|
|
774
|
+
}, action.deadlineMs);
|
|
775
|
+
this.enqueue(() => this.restartIce());
|
|
776
|
+
break;
|
|
777
|
+
case 'give-up':
|
|
778
|
+
this.fail('Connection lost', 'connection-failed');
|
|
779
|
+
break;
|
|
780
|
+
case 'recovered':
|
|
781
|
+
case 'none':
|
|
782
|
+
break;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/** ICE restart with fresh TURN credentials; the caller of this is the policy. */
|
|
787
|
+
private async restartIce(): Promise<void> {
|
|
788
|
+
const pc = this.pc;
|
|
789
|
+
if (!pc || this.disposed) return;
|
|
790
|
+
let fresh: IceServerConfig[] | null = null;
|
|
791
|
+
try {
|
|
792
|
+
fresh = await this.deps.fetchIceServers();
|
|
793
|
+
} catch {
|
|
794
|
+
fresh = null;
|
|
795
|
+
}
|
|
796
|
+
if (this.disposed || !this.pc) return;
|
|
797
|
+
if (fresh && fresh.length > 0) {
|
|
798
|
+
try {
|
|
799
|
+
pc.setConfiguration({
|
|
800
|
+
...pc.getConfiguration(),
|
|
801
|
+
iceServers: fresh as RTCIceServer[],
|
|
802
|
+
});
|
|
803
|
+
} catch (error) {
|
|
804
|
+
this.log('setConfiguration failed', error);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
// A negotiation is already in flight (e.g. the peer is restarting too);
|
|
808
|
+
// the restart deadline still guards the outcome.
|
|
809
|
+
if (pc.signalingState !== 'stable') return;
|
|
810
|
+
if (typeof pc.restartIce === 'function') pc.restartIce();
|
|
811
|
+
await this.sendOffer(true);
|
|
812
|
+
}
|
|
813
|
+
}
|