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/lib/ajax.js
Sam Saffron 25f1f23288
FEATURE: Stricter rules for user presence
Previously we would consider a user "present" and "last seen" if the
browser window was visible.

This has many edge cases, you could be considered present and around for
days just by having a window open and no screensaver on.

Instead we now also check that you either clicked, transitioned around app
or scrolled the page in the last minute in combination with window
visibility

This will lead to more reliable notifications via email and reduce load of
message bus for cases where a user walks away from the terminal
2020-03-26 17:36:52 +11:00

186 lines
4.7 KiB
JavaScript

import { run } from "@ember/runloop";
import userPresent from "discourse/lib/user-presence";
import logout from "discourse/lib/logout";
import Session from "discourse/models/session";
import { Promise } from "rsvp";
import Site from "discourse/models/site";
let _trackView = false;
let _transientHeader = null;
let _showingLogout = false;
export function setTransientHeader(key, value) {
_transientHeader = { key, value };
}
export function viewTrackingRequired() {
_trackView = true;
}
export function handleLogoff(xhr) {
if (xhr && xhr.getResponseHeader("Discourse-Logged-Out") && !_showingLogout) {
_showingLogout = true;
const messageBus = Discourse.__container__.lookup("message-bus:main");
messageBus.stop();
bootbox.dialog(
I18n.t("logout"),
{ label: I18n.t("refresh"), callback: logout },
{
onEscape: () => logout(),
backdrop: "static"
}
);
}
}
function handleRedirect(data) {
if (
data &&
data.getResponseHeader &&
data.getResponseHeader("Discourse-Xhr-Redirect")
) {
window.location.replace(data.responseText);
window.location.reload();
}
}
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];
}
function performAjax(resolve, reject) {
args.headers = args.headers || {};
if (Discourse.__container__.lookup("current-user:main")) {
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(data);
handleLogoff(xhr);
run(() => {
Site.currentProp(
"isReadOnly",
!!(xhr && xhr.getResponseHeader("Discourse-Readonly"))
);
});
if (args.returnXHR) {
data = { result: data, xhr: xhr };
}
run(null, resolve, data);
};
args.error = (xhr, textStatus, errorThrown) => {
// 0 represents the `UNSENT` state
if (xhr.readyState === 0) 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 parsererror, 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: textStatus,
errorThrown: errorThrown
});
};
// 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;
}
if (args.type === "GET" && args.cache !== true) {
args.cache = true; // Disable JQuery cache busting param, which was created to deal with IE8
}
ajaxObj = $.ajax(Discourse.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 !== Discourse.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;
}