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)
|
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)
|
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)
|
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)
|
18. Circle Video Messages - `PARTIAL` (send/play present, recording UX basic)
|
||||||
19. Stickers - `PARTIAL` (web sticker picker with preset pack + favorites)
|
19. Stickers - `PARTIAL` (web sticker picker with preset pack + favorites)
|
||||||
20. GIF - `PARTIAL` (web GIF picker with Tenor search + preset fallback + favorites)
|
20. GIF - `PARTIAL` (web GIF picker with Tenor search + preset fallback + favorites)
|
||||||
|
|||||||
@@ -264,8 +264,7 @@ export function MessageComposer() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const prev = prevActiveChatIdRef.current;
|
const prev = prevActiveChatIdRef.current;
|
||||||
if (prev && prev !== activeChatId) {
|
if (prev && prev !== activeChatId) {
|
||||||
const ws = getWs();
|
sendWsEvent("typing_stop", { chat_id: prev });
|
||||||
ws?.send(JSON.stringify({ event: "typing_stop", payload: { chat_id: prev } }));
|
|
||||||
typingActiveRef.current = false;
|
typingActiveRef.current = false;
|
||||||
if (typingStopTimerRef.current !== null) {
|
if (typingStopTimerRef.current !== null) {
|
||||||
window.clearTimeout(typingStopTimerRef.current);
|
window.clearTimeout(typingStopTimerRef.current);
|
||||||
@@ -327,6 +326,18 @@ export function MessageComposer() {
|
|||||||
return wsRef.current;
|
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(
|
function sendRealtimeChatEvent(
|
||||||
eventName:
|
eventName:
|
||||||
| "typing_start"
|
| "typing_start"
|
||||||
@@ -339,8 +350,7 @@ export function MessageComposer() {
|
|||||||
if (!activeChatId) {
|
if (!activeChatId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ws = getWs();
|
sendWsEvent(eventName, { chat_id: activeChatId });
|
||||||
ws?.send(JSON.stringify({ event: eventName, payload: { chat_id: activeChatId } }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function emitTypingStopIfActive() {
|
function emitTypingStopIfActive() {
|
||||||
@@ -711,7 +721,11 @@ export function MessageComposer() {
|
|||||||
pointerCancelArmedRef.current = false;
|
pointerCancelArmedRef.current = false;
|
||||||
setDragHint("idle");
|
setDragHint("idle");
|
||||||
if (recorderRef.current && recorderRef.current.state !== "inactive") {
|
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;
|
recorderRef.current = null;
|
||||||
setRecordingState("idle");
|
setRecordingState("idle");
|
||||||
|
|||||||
Reference in New Issue
Block a user