From a13058223d202cf260045295d21010b5a8fb6056 Mon Sep 17 00:00:00 2001 From: Aman Agrawal Date: Thu, 28 Jul 2022 07:31:57 +0000 Subject: [PATCH] compose: Don't allow same message to be sent twice. It is possible to send the message twice by clicking send button very quickly twice or by pressing enter and send button simultaneously. This can be easily reproduced for large messages sent in a stream narrow. Hard to reproduce for small messages or in PM narrows. I was not able to reproduce locally, but was able to reproduce on chat.zulip.org. So, this is an untested bug fix. Fixes #22562 --- frontend_tests/node_tests/compose.js | 4 ++++ static/js/compose.js | 10 ++++++++++ static/js/compose_ui.js | 3 +++ 3 files changed, 17 insertions(+) diff --git a/frontend_tests/node_tests/compose.js b/frontend_tests/node_tests/compose.js index 1b44c3f435..9efe078088 100644 --- a/frontend_tests/node_tests/compose.js +++ b/frontend_tests/node_tests/compose.js @@ -43,6 +43,7 @@ const server_events = mock_esm("../../static/js/server_events"); const transmit = mock_esm("../../static/js/transmit"); const upload = mock_esm("../../static/js/upload"); +const compose_ui = zrequire("compose_ui"); const compose_closed_ui = zrequire("compose_closed_ui"); const compose_state = zrequire("compose_state"); const compose = zrequire("compose"); @@ -361,6 +362,7 @@ test_ui("finish", ({override, override_rewire, mock_template}) => { $("#compose-send-button .loader").hide(); $("#compose-textarea").off("select"); $("#compose-textarea").val(""); + compose_ui.compose_spinner_visible = false; const res = compose.finish(); assert.equal(res, false); assert.ok(!$("#compose_banners .recipient_not_subscribed").visible()); @@ -374,6 +376,7 @@ test_ui("finish", ({override, override_rewire, mock_template}) => { $("#compose .preview_message_area").show(); $("#compose .markdown_preview").hide(); $("#compose-textarea").val("foobarfoobar"); + compose_ui.compose_spinner_visible = false; compose_state.set_message_type("private"); override(compose_pm_pill, "get_emails", () => "bob@example.com"); override(compose_pm_pill, "get_user_ids", () => []); @@ -399,6 +402,7 @@ test_ui("finish", ({override, override_rewire, mock_template}) => { $("#compose .preview_message_area").show(); $("#compose .markdown_preview").hide(); $("#compose-textarea").val("foobarfoobar"); + compose_ui.compose_spinner_visible = false; compose_state.set_message_type("stream"); compose_state.set_stream_name("social"); override_rewire(people, "get_by_user_id", () => []); diff --git a/static/js/compose.js b/static/js/compose.js index 13fd1f4def..e61e597ea3 100644 --- a/static/js/compose.js +++ b/static/js/compose.js @@ -290,7 +290,17 @@ export function enter_with_preview_open(ctrl_pressed = false) { } } +// Common entrypoint for asking the server to send the message +// currently drafted in the compose box, including for scheduled +// messages. export function finish() { + if (compose_ui.compose_spinner_visible) { + // Avoid sending a message twice in parallel in races where + // the user clicks the `Send` button very quickly twice or + // presses enter and the send button simultaneously. + return undefined; + } + clear_preview_area(); clear_invites(); clear_private_stream_alert(); diff --git a/static/js/compose_ui.js b/static/js/compose_ui.js index 22661ab314..8e7ca68cb5 100644 --- a/static/js/compose_ui.js +++ b/static/js/compose_ui.js @@ -10,6 +10,7 @@ import * as popover_menus from "./popover_menus"; import * as rtl from "./rtl"; import * as user_status from "./user_status"; +export let compose_spinner_visible = false; let full_size_status = false; // true or false // Some functions to handle the full size status explicitly @@ -404,12 +405,14 @@ export function format_text($textarea, type) { } export function hide_compose_spinner() { + compose_spinner_visible = false; $("#compose-send-button .loader").hide(); $("#compose-send-button span").show(); $("#compose-send-button").removeClass("disable-btn"); } export function show_compose_spinner() { + compose_spinner_visible = true; // Always use white spinner. loading.show_button_spinner($("#compose-send-button .loader"), true); $("#compose-send-button span").hide();