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
186 lines
4.7 KiB
JavaScript
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;
|
|
}
|