diff --git a/web/src/billing/helpers.ts b/web/src/billing/helpers.ts index f4c641c3e3..d3cccdc6de 100644 --- a/web/src/billing/helpers.ts +++ b/web/src/billing/helpers.ts @@ -80,7 +80,9 @@ export function create_ajax_request( }, error(xhr) { $(form_loading).hide(); - $(form_error).show().text(xhr.responseJSON.msg); + if (xhr.responseJSON?.msg) { + $(form_error).show().text(xhr.responseJSON.msg); + } $(form_input_section).show(); $(zulip_limited_section).show(); $(free_trial_alert_message).show(); diff --git a/web/src/channel.js b/web/src/channel.js index 2024ed8e99..8cb3cfecc9 100644 --- a/web/src/channel.js +++ b/web/src/channel.js @@ -105,19 +105,16 @@ function call(args) { return; } } else if (xhr.status === 403) { - try { - if ( - xhr.responseJSON.code === "CSRF_FAILED" && - reload_state.csrf_failed_handler !== undefined - ) { - reload_state.csrf_failed_handler(); - } - } catch (error) { - blueslip.error( - "Unexpected 403 response from server", - {xhr: xhr.responseText, args}, - error, - ); + if (xhr.responseJSON === undefined) { + blueslip.error("Unexpected 403 response from server", { + xhr: xhr.responseText, + args, + }); + } else if ( + xhr.responseJSON.code === "CSRF_FAILED" && + reload_state.csrf_failed_handler !== undefined + ) { + reload_state.csrf_failed_handler(); } } orig_error(xhr, error_type, xhn); @@ -189,7 +186,7 @@ export function patch(options) { } export function xhr_error_message(message, xhr) { - if (xhr.status.toString().charAt(0) === "4") { + if (xhr.status.toString().charAt(0) === "4" && xhr.responseJSON?.msg) { // Only display the error response for 4XX, where we've crafted // a nice response. const server_response_html = _.escape(xhr.responseJSON.msg); diff --git a/web/src/compose.js b/web/src/compose.js index a203df2472..9707af1a44 100644 --- a/web/src/compose.js +++ b/web/src/compose.js @@ -583,10 +583,14 @@ export function initialize() { $invite_row.remove(); } - function failure(error_msg) { + function xhr_failure(xhr) { + let error_message = "Failed to subscribe user!"; + if (xhr.responseJSON?.msg) { + error_message = xhr.responseJSON.msg; + } clear_invites(); compose_banner.show_error_message( - error_msg, + error_message, compose_banner.CLASSNAMES.generic_compose_error, $banner_container, $("#compose-textarea"), @@ -594,11 +598,6 @@ export function initialize() { $(event.target).prop("disabled", true); } - function xhr_failure(xhr) { - const error = xhr.responseJSON; - failure(error.msg); - } - const sub = sub_store.get(stream_id); subscriber_api.add_user_ids_to_stream([user_id], sub, success, xhr_failure); diff --git a/web/src/hotspots.js b/web/src/hotspots.js index 489852e86e..1abd092021 100644 --- a/web/src/hotspots.js +++ b/web/src/hotspots.js @@ -66,10 +66,13 @@ export function post_hotspot_as_read(hotspot_name) { url: "/json/users/me/hotspots", data: {hotspot: hotspot_name}, error(err) { - blueslip.error("Failed to fetch hotspots", { - status: err.status, - body: err.responseText, - }); + if (err.readyState !== 0) { + blueslip.error("Failed to fetch hotspots", { + readyState: err.readyState, + status: err.status, + body: err.responseText, + }); + } }, }); } diff --git a/web/src/invite.js b/web/src/invite.js index 9c5048f041..4ad3289f92 100644 --- a/web/src/invite.js +++ b/web/src/invite.js @@ -114,7 +114,7 @@ function submit_invitation_form() { } }, error(xhr) { - if (xhr.responseJSON.errors === undefined) { + if (xhr.responseJSON?.errors === undefined) { // There was a fatal error, no partial processing occurred. ui_report.error("", xhr, $invite_status); } else { diff --git a/web/src/message_edit.js b/web/src/message_edit.js index f74c3b62ec..b2df13d345 100644 --- a/web/src/message_edit.js +++ b/web/src/message_edit.js @@ -693,13 +693,15 @@ export function toggle_resolve_topic(message_id, old_topic_name, report_errors_i url: "/json/messages/" + message_id, data: request, error(xhr) { - if (xhr.responseJSON.code === "MOVE_MESSAGES_TIME_LIMIT_EXCEEDED") { - handle_resolve_topic_failure_due_to_time_limit(topic_is_resolved); - return; - } + if (xhr.responseJSON) { + if (xhr.responseJSON.code === "MOVE_MESSAGES_TIME_LIMIT_EXCEEDED") { + handle_resolve_topic_failure_due_to_time_limit(topic_is_resolved); + return; + } - if (report_errors_in_global_banner) { - ui_report.generic_embed_error(xhr.responseJSON.msg, 3500); + if (report_errors_in_global_banner) { + ui_report.generic_embed_error(xhr.responseJSON.msg, 3500); + } } }, }); @@ -842,7 +844,7 @@ export function save_inline_topic_edit($row) { }, error(xhr) { const $spinner = $row.find(".topic_edit_spinner"); - if (xhr.responseJSON.code === "MOVE_MESSAGES_TIME_LIMIT_EXCEEDED") { + if (xhr.responseJSON?.code === "MOVE_MESSAGES_TIME_LIMIT_EXCEEDED") { const allowed_message_id = xhr.responseJSON.first_message_id_allowed_to_move; const send_notification_to_old_thread = false; const send_notification_to_new_thread = false; @@ -1223,7 +1225,7 @@ export function move_topic_containing_message_to_stream( }, error(xhr) { reset_modal_ui(); - if (xhr.responseJSON.code === "MOVE_MESSAGES_TIME_LIMIT_EXCEEDED") { + if (xhr.responseJSON?.code === "MOVE_MESSAGES_TIME_LIMIT_EXCEEDED") { const allowed_message_id = xhr.responseJSON.first_message_id_allowed_to_move; function handle_confirm() { move_topic_containing_message_to_stream( diff --git a/web/src/portico/integrations.js b/web/src/portico/integrations.js index 41b4ff07dc..74fc1bf357 100644 --- a/web/src/portico/integrations.js +++ b/web/src/portico/integrations.js @@ -196,11 +196,13 @@ function hide_catalog_show_integration() { dataType: "html", success: hide_catalog, error(err) { - blueslip.error(`Integration documentation for '${state.integration}' not found.`, { - readyState: err.readyState, - status: err.status, - responseText: err.responseText, - }); + if (err.readyState !== 0) { + blueslip.error(`Integration documentation for '${state.integration}' not found.`, { + readyState: err.readyState, + status: err.status, + responseText: err.responseText, + }); + } }, }); } diff --git a/web/src/portico/integrations_dev_panel.js b/web/src/portico/integrations_dev_panel.js index 4e9684e0fd..8526cbb7c3 100644 --- a/web/src/portico/integrations_dev_panel.js +++ b/web/src/portico/integrations_dev_panel.js @@ -180,11 +180,10 @@ function update_url() { // API callers: These methods handle communicating with the Python backend API. function handle_unsuccessful_response(response) { - try { + if (response.responseJSON?.msg) { const status_code = response.statusCode().status; - response = JSON.parse(response.responseText); - set_results_notice(`Result: (${status_code}) ${response.msg}`, "warning"); - } catch { + set_results_notice(`Result: (${status_code}) ${response.responseJSON.msg}`, "warning"); + } else { // If the response is not a JSON response, then it is probably // Django returning an HTML response containing a stack trace // with useful debugging information regarding the backend diff --git a/web/src/reactions.js b/web/src/reactions.js index e3402949e4..256322a2cd 100644 --- a/web/src/reactions.js +++ b/web/src/reactions.js @@ -85,14 +85,16 @@ function update_ui_and_send_reaction_ajax(message_id, reaction_info) { }, error(xhr) { view.waiting_for_server_request_ids.delete(reaction_request_id); - if ( - xhr.responseJSON?.code === "REACTION_ALREADY_EXISTS" || - xhr.responseJSON?.code === "REACTION_DOES_NOT_EXIST" - ) { - // Don't send error report for simple precondition failures caused by race - // conditions; the user already got what they wanted - } else { - blueslip.error(channel.xhr_error_message("Error sending reaction", xhr)); + if (xhr.readyState !== 0) { + if ( + xhr.responseJSON?.code === "REACTION_ALREADY_EXISTS" || + xhr.responseJSON?.code === "REACTION_DOES_NOT_EXIST" + ) { + // Don't send error report for simple precondition failures caused by race + // conditions; the user already got what they wanted + } else { + blueslip.error(channel.xhr_error_message("Error sending reaction", xhr)); + } } }, }; diff --git a/web/src/server_events.js b/web/src/server_events.js index a9b8a29875..f1a58e09bf 100644 --- a/web/src/server_events.js +++ b/web/src/server_events.js @@ -207,7 +207,7 @@ function get_events({dont_block = false} = {}) { get_events_xhr = undefined; // If we're old enough that our message queue has been // garbage collected, immediately reload. - if (xhr.status === 400 && xhr.responseJSON.code === "BAD_EVENT_QUEUE_ID") { + if (xhr.status === 400 && xhr.responseJSON?.code === "BAD_EVENT_QUEUE_ID") { page_params.event_queue_expired = true; reload.initiate({ immediate: true, diff --git a/web/src/settings_account.js b/web/src/settings_account.js index c36ae4c389..a5db61271f 100644 --- a/web/src/settings_account.js +++ b/web/src/settings_account.js @@ -118,9 +118,11 @@ function upload_avatar($file_input) { if (page_params.avatar_source === "G") { $("#user-avatar-source").show(); } - const $error = $("#user-avatar-upload-widget .image_file_input_error"); - $error.text(xhr.responseJSON.msg); - $error.show(); + if (xhr.responseJSON?.msg) { + const $error = $("#user-avatar-upload-widget .image_file_input_error"); + $error.text(xhr.responseJSON.msg); + $error.show(); + } }, }); } @@ -490,7 +492,9 @@ export function set_up() { $("#api_key_value").text(data.api_key); }, error(xhr) { - $("#user_api_key_error").text(xhr.responseJSON.msg).show(); + if (xhr.responseJSON?.msg) { + $("#user_api_key_error").text(xhr.responseJSON.msg).show(); + } }, }); e.preventDefault(); @@ -798,7 +802,7 @@ export function set_up() { }, ); let rendered_error_msg; - if (xhr.responseJSON.code === "CANNOT_DEACTIVATE_LAST_USER") { + if (xhr.responseJSON?.code === "CANNOT_DEACTIVATE_LAST_USER") { if (xhr.responseJSON.is_last_owner) { rendered_error_msg = error_last_owner; } else { diff --git a/web/src/settings_bots.js b/web/src/settings_bots.js index b6286e0745..fe58463ca7 100644 --- a/web/src/settings_bots.js +++ b/web/src/settings_bots.js @@ -580,8 +580,10 @@ export function set_up() { $row.find("api_key_error").hide(); }, error(xhr) { - const $row = $(e.currentTarget).closest("li"); - $row.find(".api_key_error").text(xhr.responseJSON.msg).show(); + if (xhr.responseJSON?.msg) { + const $row = $(e.currentTarget).closest("li"); + $row.find(".api_key_error").text(xhr.responseJSON.msg).show(); + } }, }); }); diff --git a/web/src/settings_linkifiers.js b/web/src/settings_linkifiers.js index 1c58a388ee..242cad2406 100644 --- a/web/src/settings_linkifiers.js +++ b/web/src/settings_linkifiers.js @@ -73,8 +73,7 @@ function open_linkifier_edit_form(linkifier_id) { }, error_continuation(xhr) { $change_linkifier_button.prop("disabled", false); - const response_text = xhr.responseJSON; - if (response_text.errors !== undefined) { + if (xhr.responseJSON?.errors) { handle_linkifier_api_error( xhr, $pattern_status, @@ -239,12 +238,14 @@ export function build_page() { }, error(xhr) { $add_linkifier_button.prop("disabled", false); - handle_linkifier_api_error( - xhr, - $pattern_status, - $template_status, - $linkifier_status, - ); + if (xhr.responseJSON?.errors) { + handle_linkifier_api_error( + xhr, + $pattern_status, + $template_status, + $linkifier_status, + ); + } }, }); }); diff --git a/web/src/stats/stats.js b/web/src/stats/stats.js index 579d453541..8b60d0555a 100644 --- a/web/src/stats/stats.js +++ b/web/src/stats/stats.js @@ -1101,7 +1101,9 @@ function get_chart_data(data, callback) { update_last_full_update(data.end_times); }, error(xhr) { - $("#id_stats_errors").show().text(xhr.responseJSON.msg); + if (xhr.responseJSON?.msg) { + $("#id_stats_errors").show().text(xhr.responseJSON.msg); + } }, }); } diff --git a/web/src/stream_create.js b/web/src/stream_create.js index 5d9e636ce2..0efdb64eab 100644 --- a/web/src/stream_create.js +++ b/web/src/stream_create.js @@ -280,8 +280,7 @@ function create_stream() { // The rest of the work is done via the subscribe event we will get }, error(xhr) { - const msg = xhr.responseJSON.msg; - if (msg.includes("access")) { + if (xhr.responseJSON?.msg?.includes("access")) { // If we can't access the stream, we can safely // assume it's a duplicate stream that we are not invited to. // diff --git a/web/src/stream_edit_subscribers.js b/web/src/stream_edit_subscribers.js index 0b4d448588..7ce3b6bc52 100644 --- a/web/src/stream_edit_subscribers.js +++ b/web/src/stream_edit_subscribers.js @@ -196,9 +196,12 @@ function subscribe_new_users({pill_user_ids}) { } function invite_failure(xhr) { - const error = xhr.responseJSON; + let message = "Failed to subscribe user!"; + if (xhr.responseJSON?.msg) { + message = xhr.responseJSON.msg; + } show_stream_subscription_request_result({ - message: error.msg, + message, add_class: "text-error", remove_class: "text-success", }); diff --git a/web/src/typing.js b/web/src/typing.js index da01b99944..c9f7a14ece 100644 --- a/web/src/typing.js +++ b/web/src/typing.js @@ -24,7 +24,9 @@ function send_typing_notification_ajax(user_ids_array, operation) { }, success() {}, error(xhr) { - blueslip.warn("Failed to send typing event: " + xhr.responseText); + if (xhr.readyState !== 0) { + blueslip.warn("Failed to send typing event: " + xhr.responseText); + } }, }); } diff --git a/web/src/ui_report.ts b/web/src/ui_report.ts index da21819833..a3e938768a 100644 --- a/web/src/ui_report.ts +++ b/web/src/ui_report.ts @@ -38,7 +38,7 @@ export function error( status_box: JQuery, remove_after?: number, ): void { - if (xhr && xhr.status >= 400 && xhr.status < 500) { + if (xhr && xhr.status >= 400 && xhr.status < 500 && xhr.responseJSON?.msg) { // Only display the error response for 4XX, where we've crafted // a nice response. const server_response_html = _.escape(xhr.responseJSON.msg); @@ -78,7 +78,7 @@ export function generic_embed_error(error_html: string, remove_after: number): v } export function generic_row_button_error(xhr: JQuery.jqXHR, $btn: JQuery): void { - if (xhr.status >= 400 && xhr.status < 500) { + if (xhr.status >= 400 && xhr.status < 500 && xhr.responseJSON?.msg) { const $error = $("
").addClass("text-error").text(xhr.responseJSON.msg); $btn.closest("td").empty().append($error); } else { diff --git a/web/src/unread_ops.js b/web/src/unread_ops.js index ec979f34ca..b1d88e85f3 100644 --- a/web/src/unread_ops.js +++ b/web/src/unread_ops.js @@ -142,8 +142,10 @@ export function mark_all_as_read(args = {}) { dialog_widget.close_modal(); }, error(xhr) { - // If we hit the rate limit, just continue without showing any error. - if (xhr.responseJSON.code === "RATE_LIMIT_HIT") { + if (xhr.readyState === 0) { + // client cancelled the request + } else if (xhr.responseJSON?.code === "RATE_LIMIT_HIT") { + // If we hit the rate limit, just continue without showing any error. const milliseconds_to_wait = 1000 * xhr.responseJSON["retry-after"]; setTimeout(() => mark_all_as_read(args), milliseconds_to_wait); } else { @@ -241,8 +243,10 @@ export function mark_as_unread_from_here( } }, error(xhr) { - // If we hit the rate limit, just continue without showing any error. - if (xhr.responseJSON.code === "RATE_LIMIT_HIT") { + if (xhr.readyState === 0) { + // client cancelled the request + } else if (xhr.responseJSON?.code === "RATE_LIMIT_HIT") { + // If we hit the rate limit, just continue without showing any error. const milliseconds_to_wait = 1000 * xhr.responseJSON["retry-after"]; setTimeout( () => diff --git a/web/src/user_group_edit_members.js b/web/src/user_group_edit_members.js index 5b836f960a..9e52f9ddfc 100644 --- a/web/src/user_group_edit_members.js +++ b/web/src/user_group_edit_members.js @@ -223,9 +223,12 @@ function add_new_members({pill_user_ids}) { } function invite_failure(xhr) { - const error = xhr.responseJSON; + let message = "Failed to subscribe user!"; + if (xhr.responseJSON?.msg) { + message = xhr.responseJSON.msg; + } show_user_group_membership_request_result({ - message: error.msg, + message, add_class: "text-error", remove_class: "text-success", }); diff --git a/web/tests/reactions.test.js b/web/tests/reactions.test.js index 1ebc54ae22..bdf54195df 100644 --- a/web/tests/reactions.test.js +++ b/web/tests/reactions.test.js @@ -268,7 +268,7 @@ test("sending", ({override, override_rewire}) => { // Since this path calls blueslip.warn, we need to handle it. blueslip.expect("error", "XHR error message."); channel.xhr_error_message = () => "XHR error message."; - args.error({responseJSON: {msg: "Some error message"}}); + args.error({readyState: 4, responseJSON: {msg: "Some error message"}}); } emoji_name = "alien"; // not set yet {