This repository has been archived on 2023-03-18. You can view files and clone it, but cannot push or open issues or pull requests.
osr-discourse-src/app/assets/javascripts/discourse/app/lib/ajax.js
Dan Ungureanu 03ad88f2c2
FIX: Add errors field if group update confirmation (#16260)
* FIX: Redirect if Discourse-Xhr-Redirect is present

`handleRedirect` was passed an wrong argument type (a string) instead of
a jqXHR object and missed the fields checked in condition, thus always
evaluating to `false`.

* FIX: Add `errors` field if group update confirmation

An explicit confirmation about the effect of the group update is
required if the default notification level changes. Previously, if the
confirmation was missing the API endpoint failed silently returning
a 200 response code and a `user_count` field. This change ensures that
a proper error code is returned (422), a descriptive error message and
the additional information in the `user_count` field.

This commit also refactors the API endpoint to use the
`Discourse-Xhr-Redirect` header to redirect the user if the group is
no longer visible.
2022-03-24 14:50:44 +02:00

196 lines
4.7 KiB
JavaScript

import { Promise } from "rsvp";
import Session from "discourse/models/session";
import Site from "discourse/models/site";
import User from "discourse/models/user";
import getURL from "discourse-common/lib/get-url";
import { isTesting } from "discourse-common/config/environment";
import { run } from "@ember/runloop";
import userPresent from "discourse/lib/user-presence";
let _trackView = false;
let _transientHeader = null;
let _logoffCallback;
export function setTransientHeader(key, value) {
_transientHeader = { key, value };
}
export function viewTrackingRequired() {
_trackView = true;
}
export function setLogoffCallback(cb) {
_logoffCallback = cb;
}
export function handleLogoff(xhr) {
if (xhr && xhr.getResponseHeader("Discourse-Logged-Out") && _logoffCallback) {
_logoffCallback();
}
}
function handleRedirect(xhr) {
if (xhr && xhr.getResponseHeader("Discourse-Xhr-Redirect")) {
window.location = xhr.responseText;
}
}
export function updateCsrfToken() {
return ajax("/session/csrf").then((result) => {
Session.currentProp("csrfToken", result.csrf);
});
}
/**
Our own $.ajax method. Makes sure the .then method executes in an Ember runloop
for performance reasons. Also automatically adjusts the URL to support installs
in subfolders.
**/
export function ajax() {
let url, args;
let ajaxObj;
if (arguments.length === 1) {
if (typeof arguments[0] === "string") {
url = arguments[0];
args = {};
} else {
args = arguments[0];
url = args.url;
delete args.url;
}
} else if (arguments.length === 2) {
url = arguments[0];
args = arguments[1];
}
let ignoreUnsent = true;
if (args.ignoreUnsent !== undefined) {
ignoreUnsent = args.ignoreUnsent;
delete args.ignoreUnsent;
}
function performAjax(resolve, reject) {
args.headers = args.headers || {};
if (User.current()) {
args.headers["Discourse-Logged-In"] = "true";
}
if (_transientHeader) {
args.headers[_transientHeader.key] = _transientHeader.value;
_transientHeader = null;
}
if (_trackView && (!args.type || args.type === "GET")) {
_trackView = false;
// DON'T CHANGE: rack is prepending "HTTP_" in the header's name
args.headers["Discourse-Track-View"] = "true";
}
if (userPresent()) {
args.headers["Discourse-Present"] = "true";
}
args.success = (data, textStatus, xhr) => {
handleRedirect(xhr);
handleLogoff(xhr);
run(() => {
Site.currentProp(
"isReadOnly",
!!(xhr && xhr.getResponseHeader("Discourse-Readonly"))
);
});
if (args.returnXHR) {
data = { result: data, xhr };
}
run(null, resolve, data);
};
args.error = (xhr, textStatus, errorThrown) => {
// 0 represents the `UNSENT` state
if (ignoreUnsent && xhr.readyState === 0) {
// Make sure we log pretender errors in test mode
if (textStatus === "error" && isTesting()) {
throw errorThrown;
}
return;
}
handleLogoff(xhr);
// note: for bad CSRF we don't loop an extra request right away.
// this allows us to eliminate the possibility of having a loop.
if (xhr.status === 403 && xhr.responseText === '["BAD CSRF"]') {
Session.current().set("csrfToken", null);
}
// If it's a parser error, don't reject
if (xhr.status === 200) {
return args.success(xhr);
}
// Fill in some extra info
xhr.jqTextStatus = textStatus;
xhr.requestedUrl = url;
run(null, reject, {
jqXHR: xhr,
textStatus,
errorThrown,
});
};
if (args.method) {
args.type = args.method;
delete args.method;
}
// We default to JSON on GET. If we don't, sometimes if the server doesn't return the proper header
// it will not be parsed as an object.
if (!args.type) {
args.type = "GET";
}
if (!args.dataType && args.type.toUpperCase() === "GET") {
args.dataType = "json";
}
if (args.dataType === "script") {
args.headers["Discourse-Script"] = true;
}
ajaxObj = $.ajax(getURL(url), args);
}
let promise;
// For cached pages we strip out CSRF tokens, need to round trip to server prior to sending the
// request (bypass for GET, not needed)
if (
args.type &&
args.type.toUpperCase() !== "GET" &&
url !== getURL("/clicks/track") &&
!Session.currentProp("csrfToken")
) {
promise = new Promise((resolve, reject) => {
ajaxObj = updateCsrfToken().then(() => {
performAjax(resolve, reject);
});
});
} else {
promise = new Promise(performAjax);
}
promise.abort = () => {
if (ajaxObj) {
ajaxObj.abort();
}
};
return promise;
}