perf(realtime): debounce typing start/stop events
Some checks failed
CI / test (push) Failing after 55s
Some checks failed
CI / test (push) Failing after 55s
This commit is contained in:
@@ -109,6 +109,8 @@ export function MessageComposer() {
|
|||||||
const recordingStateRef = useRef<RecordingState>("idle");
|
const recordingStateRef = useRef<RecordingState>("idle");
|
||||||
const lastEditingMessageIdRef = useRef<number | null>(null);
|
const lastEditingMessageIdRef = useRef<number | null>(null);
|
||||||
const prevActiveChatIdRef = 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 [isUploading, setIsUploading] = useState(false);
|
||||||
const [uploadProgress, setUploadProgress] = useState(0);
|
const [uploadProgress, setUploadProgress] = useState(0);
|
||||||
@@ -247,6 +249,9 @@ export function MessageComposer() {
|
|||||||
if (pointerUpHandlerRef.current) {
|
if (pointerUpHandlerRef.current) {
|
||||||
window.removeEventListener("pointerup", pointerUpHandlerRef.current);
|
window.removeEventListener("pointerup", pointerUpHandlerRef.current);
|
||||||
}
|
}
|
||||||
|
if (typingStopTimerRef.current !== null) {
|
||||||
|
window.clearTimeout(typingStopTimerRef.current);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, [previewUrl]);
|
}, [previewUrl]);
|
||||||
|
|
||||||
@@ -261,6 +266,11 @@ export function MessageComposer() {
|
|||||||
if (prev && prev !== activeChatId) {
|
if (prev && prev !== activeChatId) {
|
||||||
const ws = getWs();
|
const ws = getWs();
|
||||||
ws?.send(JSON.stringify({ event: "typing_stop", payload: { chat_id: prev } }));
|
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;
|
prevActiveChatIdRef.current = activeChatId;
|
||||||
}, [activeChatId]);
|
}, [activeChatId]);
|
||||||
@@ -333,6 +343,46 @@ export function MessageComposer() {
|
|||||||
ws?.send(JSON.stringify({ event: eventName, payload: { chat_id: activeChatId } }));
|
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() {
|
async function handleSend() {
|
||||||
if (!activeChatId || !text.trim() || !me || !canSendInActiveChat) {
|
if (!activeChatId || !text.trim() || !me || !canSendInActiveChat) {
|
||||||
return;
|
return;
|
||||||
@@ -373,7 +423,7 @@ export function MessageComposer() {
|
|||||||
setText("");
|
setText("");
|
||||||
clearDraft(activeChatId);
|
clearDraft(activeChatId);
|
||||||
setReplyToMessage(activeChatId, null);
|
setReplyToMessage(activeChatId, null);
|
||||||
sendRealtimeChatEvent("typing_stop");
|
emitTypingStopIfActive();
|
||||||
} catch {
|
} catch {
|
||||||
removeOptimisticMessage(activeChatId, clientMessageId);
|
removeOptimisticMessage(activeChatId, clientMessageId);
|
||||||
setUploadError("Message send failed. Please try again.");
|
setUploadError("Message send failed. Please try again.");
|
||||||
@@ -644,7 +694,7 @@ export function MessageComposer() {
|
|||||||
recordingStartedAtRef.current = Date.now();
|
recordingStartedAtRef.current = Date.now();
|
||||||
setRecordSeconds(0);
|
setRecordSeconds(0);
|
||||||
setRecordingState("recording");
|
setRecordingState("recording");
|
||||||
sendRealtimeChatEvent("typing_stop");
|
emitTypingStopIfActive();
|
||||||
sendRealtimeChatEvent("recording_voice_start");
|
sendRealtimeChatEvent("recording_voice_start");
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
@@ -1304,13 +1354,11 @@ export function MessageComposer() {
|
|||||||
setText(next);
|
setText(next);
|
||||||
if (activeChatId) {
|
if (activeChatId) {
|
||||||
setDraft(activeChatId, next);
|
setDraft(activeChatId, next);
|
||||||
sendRealtimeChatEvent(next.trim().length > 0 ? "typing_start" : "typing_stop");
|
syncTypingForText(next);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
if (activeChatId) {
|
emitTypingStopIfActive();
|
||||||
sendRealtimeChatEvent("typing_stop");
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user