fix(composer): guard websocket and recorder race on chat switch
Some checks failed
CI / test (push) Failing after 1m32s
Some checks failed
CI / test (push) Failing after 1m32s
This commit is contained in:
@@ -23,7 +23,7 @@ Legend:
|
||||
14. Delivery Status - `DONE` (sent/delivered/read + reconnect reconciliation after backend restarts)
|
||||
15. Typing Realtime - `DONE` (typing start/stop + recording voice start/stop + recording video start/stop in circle-video send flow)
|
||||
16. Media & Attachments - `DONE` (upload/preview/download/gallery)
|
||||
17. Voice Messages - `PARTIAL` (record/send/play/seek + global speed 1x/1.5x/2x; recorder now uses mime fallback + chunked capture; UX still being polished)
|
||||
17. Voice Messages - `PARTIAL` (record/send/play/seek + global speed 1x/1.5x/2x; recorder uses mime fallback + chunked capture; websocket send/recorder stop race on fast chat switch is guarded; UX still being polished)
|
||||
18. Circle Video Messages - `PARTIAL` (send/play present, recording UX basic)
|
||||
19. Stickers - `PARTIAL` (web sticker picker with preset pack + favorites)
|
||||
20. GIF - `PARTIAL` (web GIF picker with Tenor search + preset fallback + favorites)
|
||||
|
||||
@@ -264,8 +264,7 @@ export function MessageComposer() {
|
||||
useEffect(() => {
|
||||
const prev = prevActiveChatIdRef.current;
|
||||
if (prev && prev !== activeChatId) {
|
||||
const ws = getWs();
|
||||
ws?.send(JSON.stringify({ event: "typing_stop", payload: { chat_id: prev } }));
|
||||
sendWsEvent("typing_stop", { chat_id: prev });
|
||||
typingActiveRef.current = false;
|
||||
if (typingStopTimerRef.current !== null) {
|
||||
window.clearTimeout(typingStopTimerRef.current);
|
||||
@@ -327,6 +326,18 @@ export function MessageComposer() {
|
||||
return wsRef.current;
|
||||
}
|
||||
|
||||
function sendWsEvent(eventName: string, payload: Record<string, unknown>) {
|
||||
const ws = getWs();
|
||||
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
ws.send(JSON.stringify({ event: eventName, payload }));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function sendRealtimeChatEvent(
|
||||
eventName:
|
||||
| "typing_start"
|
||||
@@ -339,8 +350,7 @@ export function MessageComposer() {
|
||||
if (!activeChatId) {
|
||||
return;
|
||||
}
|
||||
const ws = getWs();
|
||||
ws?.send(JSON.stringify({ event: eventName, payload: { chat_id: activeChatId } }));
|
||||
sendWsEvent(eventName, { chat_id: activeChatId });
|
||||
}
|
||||
|
||||
function emitTypingStopIfActive() {
|
||||
@@ -711,7 +721,11 @@ export function MessageComposer() {
|
||||
pointerCancelArmedRef.current = false;
|
||||
setDragHint("idle");
|
||||
if (recorderRef.current && recorderRef.current.state !== "inactive") {
|
||||
recorderRef.current.stop();
|
||||
try {
|
||||
recorderRef.current.stop();
|
||||
} catch {
|
||||
// ignore recorder lifecycle race during quick chat switches/unmount
|
||||
}
|
||||
}
|
||||
recorderRef.current = null;
|
||||
setRecordingState("idle");
|
||||
|
||||
Reference in New Issue
Block a user