perf(realtime): debounce typing start/stop events
Some checks failed
CI / test (push) Failing after 55s

This commit is contained in:
2026-03-08 20:06:04 +03:00
parent c5b90bc91c
commit 9ffcf7b3ef

View File

@@ -109,6 +109,8 @@ export function MessageComposer() {
const recordingStateRef = useRef<RecordingState>("idle");
const lastEditingMessageIdRef = useRef<number | null>(null);
const prevActiveChatIdRef = useRef<number | null>(null);
const typingActiveRef = useRef(false);
const typingStopTimerRef = useRef<number | null>(null);
const [isUploading, setIsUploading] = useState(false);
const [uploadProgress, setUploadProgress] = useState(0);
@@ -247,6 +249,9 @@ export function MessageComposer() {
if (pointerUpHandlerRef.current) {
window.removeEventListener("pointerup", pointerUpHandlerRef.current);
}
if (typingStopTimerRef.current !== null) {
window.clearTimeout(typingStopTimerRef.current);
}
};
}, [previewUrl]);
@@ -261,6 +266,11 @@ export function MessageComposer() {
if (prev && prev !== activeChatId) {
const ws = getWs();
ws?.send(JSON.stringify({ event: "typing_stop", payload: { chat_id: prev } }));
typingActiveRef.current = false;
if (typingStopTimerRef.current !== null) {
window.clearTimeout(typingStopTimerRef.current);
typingStopTimerRef.current = null;
}
}
prevActiveChatIdRef.current = activeChatId;
}, [activeChatId]);
@@ -333,6 +343,46 @@ export function MessageComposer() {
ws?.send(JSON.stringify({ event: eventName, payload: { chat_id: activeChatId } }));
}
function emitTypingStopIfActive() {
if (!activeChatId) {
return;
}
if (typingStopTimerRef.current !== null) {
window.clearTimeout(typingStopTimerRef.current);
typingStopTimerRef.current = null;
}
if (typingActiveRef.current) {
sendRealtimeChatEvent("typing_stop");
typingActiveRef.current = false;
}
}
function syncTypingForText(nextText: string) {
if (!activeChatId) {
return;
}
const hasText = nextText.trim().length > 0;
if (!hasText) {
emitTypingStopIfActive();
return;
}
if (!typingActiveRef.current) {
sendRealtimeChatEvent("typing_start");
typingActiveRef.current = true;
}
if (typingStopTimerRef.current !== null) {
window.clearTimeout(typingStopTimerRef.current);
}
typingStopTimerRef.current = window.setTimeout(() => {
if (!typingActiveRef.current) {
return;
}
sendRealtimeChatEvent("typing_stop");
typingActiveRef.current = false;
typingStopTimerRef.current = null;
}, 2500);
}
async function handleSend() {
if (!activeChatId || !text.trim() || !me || !canSendInActiveChat) {
return;
@@ -373,7 +423,7 @@ export function MessageComposer() {
setText("");
clearDraft(activeChatId);
setReplyToMessage(activeChatId, null);
sendRealtimeChatEvent("typing_stop");
emitTypingStopIfActive();
} catch {
removeOptimisticMessage(activeChatId, clientMessageId);
setUploadError("Message send failed. Please try again.");
@@ -644,7 +694,7 @@ export function MessageComposer() {
recordingStartedAtRef.current = Date.now();
setRecordSeconds(0);
setRecordingState("recording");
sendRealtimeChatEvent("typing_stop");
emitTypingStopIfActive();
sendRealtimeChatEvent("recording_voice_start");
return true;
} catch {
@@ -1304,13 +1354,11 @@ export function MessageComposer() {
setText(next);
if (activeChatId) {
setDraft(activeChatId, next);
sendRealtimeChatEvent(next.trim().length > 0 ? "typing_start" : "typing_stop");
syncTypingForText(next);
}
}}
onBlur={() => {
if (activeChatId) {
sendRealtimeChatEvent("typing_stop");
}
emitTypingStopIfActive();
}}
/>