修复通话发起后立即退出登录导致应用整体白屏崩溃
handleLogout()先断开WS连接、再重置界面状态,界面重置卸载 MessagesView时其componentWillUnmount会尝试发一条挂断信令,vendored SDK的Connection.sendText()在已关闭的socket上同步抛错,componentWillUnmount 里没有error boundary兜底,导致整个应用被React卸载成白屏。 5处几乎相同的通话信令发送方法(ringing/hang-up/offer/ice-candidate/ answer)合并成一个sendCallSignal()辅助方法,发送前检查连接状态、 发送本身包一层try/catch。 第十二轮最小粒度测试 Ch8 发现(点击通话按钮后立即退出登录复现)。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
4a140e6c79
commit
5b836d50b2
+35
-27
@@ -2511,48 +2511,56 @@ class TinodeWeb extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCallRinging(callTopic, callSeq) {
|
// All of the call-signaling senders below (ringing/hang-up/offer/ice-candidate/answer) go through
|
||||||
|
// this one helper rather than calling topic.videoCall() directly. handleCallHangup in particular is
|
||||||
|
// called from several unmount/teardown paths outside the normal call flow (messages-view.jsx's
|
||||||
|
// componentWillUnmount and its topic-switch handler in componentSetup both call it via the
|
||||||
|
// onCallHangup prop, on top of the ring-timeout path and CallInvite's own cleanup here) -- including
|
||||||
|
// ones that fire *after* a logout has already torn down the connection: handleLogout() calls
|
||||||
|
// this.tinode.disconnect() and only afterwards resets state to the blank/logged-out screen, and that
|
||||||
|
// state reset is what unmounts messages-view.jsx and trips its componentWillUnmount -> handleCallHangup
|
||||||
|
// call. Real repro: click a call button (real or scripted handleStartVideoCall/handleStartAudioCall),
|
||||||
|
// then immediately log out -- topic.videoCall() ends up calling the vendored SDK's
|
||||||
|
// Connection.sendText() on an already-closed socket, which throws synchronously ("Websocket is not
|
||||||
|
// connected"). Thrown from componentWillUnmount, React has no error boundary for it and unmounts the
|
||||||
|
// entire app to a blank white page. None of these are anything the peer can still receive over a dead
|
||||||
|
// connection anyway (and for hang-up specifically, the server's own establishment-timeout already
|
||||||
|
// cleans up an abandoned call-in-progress independently), so skip sending entirely once disconnected
|
||||||
|
// instead of letting it throw -- the other four signals are currently unreachable in practice (no real
|
||||||
|
// call can get established while "call invite always 400s" -- see handleCallInvite -- is unfixed) but
|
||||||
|
// share the identical topic.videoCall() shape and would hit the exact same crash the moment that's
|
||||||
|
// fixed and real in-progress calls (offer/ice-candidate/answer exchange, or a mid-call logout) become
|
||||||
|
// possible.
|
||||||
|
sendCallSignal(callTopic, evt, callSeq, payload) {
|
||||||
const topic = this.tinode.getTopic(callTopic);
|
const topic = this.tinode.getTopic(callTopic);
|
||||||
if (!topic) {
|
if (!topic || !this.tinode.isConnected()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
topic.videoCall(evt, callSeq, payload);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`Failed to send call signal '${evt}'`, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
topic.videoCall('ringing', callSeq);
|
handleCallRinging(callTopic, callSeq) {
|
||||||
|
this.sendCallSignal(callTopic, 'ringing', callSeq);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCallHangup(callTopic, callSeq) {
|
handleCallHangup(callTopic, callSeq) {
|
||||||
const topic = this.tinode.getTopic(callTopic);
|
this.sendCallSignal(callTopic, 'hang-up', callSeq);
|
||||||
if (!topic) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
topic.videoCall('hang-up', callSeq);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCallSendOffer(callTopic, callSeq, sdp) {
|
handleCallSendOffer(callTopic, callSeq, sdp) {
|
||||||
const topic = this.tinode.getTopic(callTopic);
|
this.sendCallSignal(callTopic, 'offer', callSeq, sdp);
|
||||||
if (!topic) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
topic.videoCall('offer', callSeq, sdp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCallIceCandidate(callTopic, callSeq, candidate) {
|
handleCallIceCandidate(callTopic, callSeq, candidate) {
|
||||||
const topic = this.tinode.getTopic(callTopic);
|
this.sendCallSignal(callTopic, 'ice-candidate', callSeq, candidate);
|
||||||
if (!topic) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
topic.videoCall('ice-candidate', callSeq, candidate);
|
|
||||||
}
|
}
|
||||||
handleCallSendAnswer(callTopic, callSeq, sdp) {
|
|
||||||
const topic = this.tinode.getTopic(callTopic);
|
|
||||||
if (!topic) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
topic.videoCall('answer', callSeq, sdp);
|
handleCallSendAnswer(callTopic, callSeq, sdp) {
|
||||||
|
this.sendCallSignal(callTopic, 'answer', callSeq, sdp);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCallClose() {
|
handleCallClose() {
|
||||||
|
|||||||
Reference in New Issue
Block a user