fix(composer): guard websocket and recorder race on chat switch
Some checks failed
CI / test (push) Failing after 1m32s

This commit is contained in:
2026-03-08 20:42:19 +03:00
parent 20f31cd15e
commit f0582bf4ab
2 changed files with 20 additions and 6 deletions

View File

@@ -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)

View File

@@ -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");