2021-03-11 05:43:45 +01:00
|
|
|
import $ from "jquery";
|
|
|
|
|
2021-03-16 23:38:59 +01:00
|
|
|
import * as blueslip from "./blueslip";
|
2021-09-28 12:11:03 +02:00
|
|
|
import {page_params} from "./page_params";
|
2021-02-28 00:48:19 +01:00
|
|
|
import * as reload_state from "./reload_state";
|
2021-09-28 12:20:14 +02:00
|
|
|
import * as spectators from "./spectators";
|
2021-02-28 00:48:19 +01:00
|
|
|
|
2021-10-05 03:01:45 +02:00
|
|
|
let password_change_in_progress = false;
|
|
|
|
export let password_changes = 0;
|
|
|
|
|
|
|
|
export function set_password_change_in_progress(value) {
|
|
|
|
password_change_in_progress = value;
|
|
|
|
if (!value) {
|
|
|
|
password_changes += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
export const xhr_password_changes = new WeakMap();
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const pending_requests = [];
|
2014-02-07 04:27:16 +01:00
|
|
|
|
2021-03-08 15:14:43 +01:00
|
|
|
export function clear_for_tests() {
|
|
|
|
pending_requests.length = 0;
|
|
|
|
}
|
|
|
|
|
2016-12-05 07:02:18 +01:00
|
|
|
function add_pending_request(jqXHR) {
|
2014-02-07 04:27:16 +01:00
|
|
|
pending_requests.push(jqXHR);
|
|
|
|
if (pending_requests.length > 50) {
|
2020-07-15 00:34:28 +02:00
|
|
|
blueslip.warn(
|
|
|
|
"The length of pending_requests is over 50. Most likely " +
|
|
|
|
"they are not being correctly removed.",
|
|
|
|
);
|
2014-02-07 04:27:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-05 07:02:18 +01:00
|
|
|
function remove_pending_request(jqXHR) {
|
2020-02-08 05:48:46 +01:00
|
|
|
const pending_request_index = pending_requests.indexOf(jqXHR);
|
2015-11-10 19:01:34 +01:00
|
|
|
if (pending_request_index !== -1) {
|
2014-02-07 04:27:16 +01:00
|
|
|
pending_requests.splice(pending_request_index, 1);
|
|
|
|
}
|
|
|
|
}
|
2013-12-18 19:55:18 +01:00
|
|
|
|
2014-01-07 23:40:31 +01:00
|
|
|
function call(args, idempotent) {
|
2020-02-14 00:26:34 +01:00
|
|
|
if (reload_state.is_in_progress() && !args.ignore_reload) {
|
|
|
|
// If we're in the process of reloading, most HTTP requests
|
|
|
|
// are useless, with exceptions like cleaning up our event
|
|
|
|
// queue and blueslip (Which doesn't use channel.js).
|
2020-09-24 07:50:36 +02:00
|
|
|
return undefined;
|
2020-02-14 00:26:34 +01:00
|
|
|
}
|
|
|
|
|
2013-12-17 22:18:13 +01:00
|
|
|
// Wrap the error handlers to reload the page if we get a CSRF error
|
|
|
|
// (What probably happened is that the user logged out in another tab).
|
2019-11-02 00:06:25 +01:00
|
|
|
let orig_error = args.error;
|
2013-12-17 22:18:13 +01:00
|
|
|
if (orig_error === undefined) {
|
|
|
|
orig_error = function () {};
|
|
|
|
}
|
|
|
|
args.error = function wrapped_error(xhr, error_type, xhn) {
|
2014-02-07 04:27:16 +01:00
|
|
|
remove_pending_request(xhr);
|
|
|
|
|
2020-02-26 00:19:57 +01:00
|
|
|
if (reload_state.is_in_progress()) {
|
|
|
|
// If we're in the process of reloading the browser,
|
2021-03-18 12:41:16 +01:00
|
|
|
// there's no point in running the error handler,
|
2020-02-26 00:19:57 +01:00
|
|
|
// because all of our state is about to be discarded
|
|
|
|
// anyway.
|
|
|
|
blueslip.log(`Ignoring ${args.type} ${args.url} error response while reloading`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-09-28 12:11:03 +02:00
|
|
|
if (xhr.status === 401) {
|
2021-10-05 03:01:45 +02:00
|
|
|
if (password_change_in_progress || xhr.password_changes !== password_changes) {
|
2021-09-28 12:11:03 +02:00
|
|
|
// The backend for handling password change API requests
|
|
|
|
// will replace the user's session; this results in a
|
|
|
|
// brief race where any API request will fail with a 401
|
|
|
|
// error after the old session is deactivated but before
|
|
|
|
// the new one has been propagated to the browser. So we
|
|
|
|
// skip our normal HTTP 401 error handling if we're in the
|
|
|
|
// process of executing a password change.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-09-28 12:20:14 +02:00
|
|
|
if (page_params.is_spectator) {
|
2022-02-08 00:13:33 +01:00
|
|
|
// In theory, the spectator implementation should be
|
2021-09-28 12:20:14 +02:00
|
|
|
// designed to prevent accessing widgets that would
|
|
|
|
// make network requests not available to spectators.
|
|
|
|
//
|
|
|
|
// In the case that we have a bug in that logic, we
|
|
|
|
// prefer the user experience of offering the
|
|
|
|
// login_to_access widget over reloading the page.
|
|
|
|
spectators.login_to_access();
|
|
|
|
} else {
|
|
|
|
// We got logged out somehow, perhaps from another window
|
|
|
|
// changing the user's password, or a session timeout. We
|
|
|
|
// could display an error message, but jumping right to
|
|
|
|
// the login page conveys the same information with a
|
|
|
|
// smoother relogin experience.
|
|
|
|
window.location.replace(page_params.login_page);
|
|
|
|
}
|
2021-09-28 12:11:03 +02:00
|
|
|
} else if (xhr.status === 403) {
|
2014-02-10 22:54:55 +01:00
|
|
|
try {
|
2021-03-10 02:39:50 +01:00
|
|
|
if (
|
|
|
|
JSON.parse(xhr.responseText).code === "CSRF_FAILED" &&
|
|
|
|
reload_state.csrf_failed_handler !== undefined
|
|
|
|
) {
|
|
|
|
reload_state.csrf_failed_handler();
|
2014-02-10 22:54:55 +01:00
|
|
|
}
|
2020-10-07 10:20:41 +02:00
|
|
|
} catch (error) {
|
2020-07-15 00:34:28 +02:00
|
|
|
blueslip.error(
|
|
|
|
"Unexpected 403 response from server",
|
2020-07-20 22:18:43 +02:00
|
|
|
{xhr: xhr.responseText, args},
|
2020-10-07 10:20:41 +02:00
|
|
|
error.stack,
|
2020-07-15 00:34:28 +02:00
|
|
|
);
|
2014-02-10 22:54:55 +01:00
|
|
|
}
|
2013-12-17 22:18:13 +01:00
|
|
|
}
|
2020-09-24 07:50:36 +02:00
|
|
|
orig_error(xhr, error_type, xhn);
|
2013-12-17 22:18:13 +01:00
|
|
|
};
|
2019-11-02 00:06:25 +01:00
|
|
|
let orig_success = args.success;
|
2014-01-07 23:40:31 +01:00
|
|
|
if (orig_success === undefined) {
|
|
|
|
orig_success = function () {};
|
|
|
|
}
|
|
|
|
args.success = function wrapped_success(data, textStatus, jqXHR) {
|
2014-02-07 04:27:16 +01:00
|
|
|
remove_pending_request(jqXHR);
|
|
|
|
|
2020-02-13 21:34:54 +01:00
|
|
|
if (reload_state.is_in_progress()) {
|
|
|
|
// If we're in the process of reloading the browser,
|
|
|
|
// there's no point in running the success handler,
|
|
|
|
// because all of our state is about to be discarded
|
|
|
|
// anyway.
|
|
|
|
blueslip.log(`Ignoring ${args.type} ${args.url} response while reloading`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-01-07 23:40:31 +01:00
|
|
|
if (!data && idempotent) {
|
|
|
|
// If idempotent, retry
|
|
|
|
blueslip.log("Retrying idempotent" + args);
|
2020-07-02 01:45:54 +02:00
|
|
|
setTimeout(() => {
|
2019-11-02 00:06:25 +01:00
|
|
|
const jqXHR = $.ajax(args);
|
2014-02-07 04:27:16 +01:00
|
|
|
add_pending_request(jqXHR);
|
2014-01-07 23:40:31 +01:00
|
|
|
}, 0);
|
|
|
|
return;
|
|
|
|
}
|
2020-09-24 07:50:36 +02:00
|
|
|
orig_success(data, textStatus, jqXHR);
|
2014-01-07 23:40:31 +01:00
|
|
|
};
|
2013-12-17 22:18:13 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const jqXHR = $.ajax(args);
|
2014-02-07 04:27:16 +01:00
|
|
|
add_pending_request(jqXHR);
|
2021-10-05 03:01:45 +02:00
|
|
|
|
|
|
|
// Remember the number of completed password changes when the
|
|
|
|
// request was initiated. This allows us to detect race
|
|
|
|
// situations where a password change occurred before we got a
|
|
|
|
// response that failed due to the ongoing password change.
|
|
|
|
jqXHR.password_changes = password_changes;
|
|
|
|
|
2014-02-07 04:27:16 +01:00
|
|
|
return jqXHR;
|
2013-12-18 19:55:18 +01:00
|
|
|
}
|
|
|
|
|
2021-02-28 00:39:51 +01:00
|
|
|
export function get(options) {
|
2020-07-16 22:40:18 +02:00
|
|
|
const args = {type: "GET", dataType: "json", ...options};
|
2014-01-07 23:40:31 +01:00
|
|
|
return call(args, options.idempotent);
|
2021-02-28 00:39:51 +01:00
|
|
|
}
|
2013-12-18 19:55:18 +01:00
|
|
|
|
2021-02-28 00:39:51 +01:00
|
|
|
export function post(options) {
|
2020-07-16 22:40:18 +02:00
|
|
|
const args = {type: "POST", dataType: "json", ...options};
|
2014-01-07 23:40:31 +01:00
|
|
|
return call(args, options.idempotent);
|
2021-02-28 00:39:51 +01:00
|
|
|
}
|
2013-12-18 19:55:18 +01:00
|
|
|
|
2021-02-28 00:39:51 +01:00
|
|
|
export function put(options) {
|
2020-07-16 22:40:18 +02:00
|
|
|
const args = {type: "PUT", dataType: "json", ...options};
|
2014-01-07 23:40:31 +01:00
|
|
|
return call(args, options.idempotent);
|
2021-02-28 00:39:51 +01:00
|
|
|
}
|
2013-12-18 19:55:18 +01:00
|
|
|
|
|
|
|
// Not called exports.delete because delete is a reserved word in JS
|
2021-02-28 00:39:51 +01:00
|
|
|
export function del(options) {
|
2020-07-16 22:40:18 +02:00
|
|
|
const args = {type: "DELETE", dataType: "json", ...options};
|
2014-01-07 23:40:31 +01:00
|
|
|
return call(args, options.idempotent);
|
2021-02-28 00:39:51 +01:00
|
|
|
}
|
2013-12-18 19:55:18 +01:00
|
|
|
|
2021-02-28 00:39:51 +01:00
|
|
|
export function patch(options) {
|
2013-12-18 19:55:18 +01:00
|
|
|
// Send a PATCH as a POST in order to work around QtWebkit
|
|
|
|
// (Linux/Windows desktop app) not supporting PATCH body.
|
|
|
|
if (options.processData === false) {
|
|
|
|
// If we're submitting a FormData object, we need to add the
|
|
|
|
// method this way
|
|
|
|
options.data.append("method", "PATCH");
|
2014-01-14 23:24:39 +01:00
|
|
|
} else {
|
2020-07-16 22:40:18 +02:00
|
|
|
options.data = {...options.data, method: "PATCH"};
|
2013-12-18 19:55:18 +01:00
|
|
|
}
|
2021-02-28 00:39:51 +01:00
|
|
|
return post(options, options.idempotent);
|
|
|
|
}
|
2013-12-18 19:55:18 +01:00
|
|
|
|
2021-02-28 00:39:51 +01:00
|
|
|
export function xhr_error_message(message, xhr) {
|
2014-03-13 15:32:44 +01:00
|
|
|
if (xhr.status.toString().charAt(0) === "4") {
|
|
|
|
// Only display the error response for 4XX, where we've crafted
|
|
|
|
// a nice response.
|
2016-08-24 23:56:23 +02:00
|
|
|
message += ": " + JSON.parse(xhr.responseText).msg;
|
2014-03-13 15:32:44 +01:00
|
|
|
}
|
|
|
|
return message;
|
2021-02-28 00:39:51 +01:00
|
|
|
}
|