Skip to content

Instantly share code, notes, and snippets.

@andrewnicols
Last active March 10, 2020 01:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andrewnicols/fd0054e03e9b905737dd8b049c80b139 to your computer and use it in GitHub Desktop.
Save andrewnicols/fd0054e03e9b905737dd8b049c80b139 to your computer and use it in GitHub Desktop.
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* XHR Processing for Moodle to call standard AJAX-enabled Moodle Web Services.
*
* @module core/request
* @package core
* @copyright 2020 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.9
*/
import Config from 'core/config';
import Log from 'core/log';
import Pending from 'core/pending';
import Url from 'core/url';
let pageIsUnloading = false;
/**
* Register the unload events.
*
* These are used to prevent unload XHR failures.
*
* The presence of a beforeunload event prevents caching of the page content, therefore it should be removed during the
* unload.
*/
let registerUnloadEvents = () => {
const beforeUnloadHandler = () => {
pageIsUnloading = true;
window.removeEventListener('beforeunload', beforeUnloadHandler);
};
window.addEventListener("beforeunload", beforeUnloadHandler);
registerUnloadEvents = () => {
return;
};
};
/**
* Get the script configuration, including the URL and type.
*
* @param {Bool} requireLogin
* @param {String[]} methods List of methods
* @param {String} cacheKey
* @returns {Object}
*/
const getScriptConfig = (requireLogin, methods, cacheKey) => {
const script = requireLogin ? 'service.php' : 'service-nologin.php';
const urlParams = {};
if (methods.length <= 5) {
urlParams.info = methods.sort().join();
} else {
urlParams.info = `${methods.length}-method-calls`;
}
let type = 'POST';
if (requireLogin) {
urlParams.sesskey = Config.sesskey;
} else if (cacheKey) {
type = 'GET';
}
const urlData = Object.entries(urlParams).sort().map(data => `${data[0]}=${data[1]}`).join('&');
return {
url: `${Config.wwwroot}/lib/ajax/${script}?${urlData}`,
type,
};
};
/**
* Perform a single XHR.
*
* @param {String} methodName
* @param {Object} methodArgs
* @param {Bool} requireLogin
* @param {Object} Additional parameters
* @param {Object}.{String} cacheKey
* @param {Object}.{Number} timeout
* @param {Object}.{Bool} updateSession
* @returns {Promise}
*/
export const fetch = (
methodName,
methodArgs = {},
requireLogin = true,
{
cacheKey = null,
timeout = 0,
updateSession = true,
} = {}
) => {
// Register unload events.
// This prevents promises from being rejected, and notifications therefore being shown, because the page is navigated away.
registerUnloadEvents();
const pendingPromise = new Pending(`core/request:fetch:${methodName}`);
return new Promise((resolve, reject) => {
const {
url,
type,
} = getScriptConfig(
requireLogin,
[methodName],
cacheKey = Math.max(parseInt(cacheKey), 0) || null
);
const xhr = new XMLHttpRequest();
xhr.open(type, url);
xhr.timeout = timeout;
// Configure the load listener which is triggered when the XHR completes.
xhr.addEventListener('load', () => {
// The response is placed into the `xhr.responseText` variable.
const responses = JSON.parse(xhr.responseText);
if (!responses || !responses.length) {
return reject(new Error('No response data provided'));
}
const response = responses[0];
if (response.error) {
return reject(response.exception);
} else {
return resolve(response.data);
}
});
// Configure the error listener, which is triggered when the XHR fails.
xhr.addEventListener('error', () => {
if (pageIsUnloading) {
// No need to trigger an error because we are already navigating.
Log.error("Page unloaded.");
Log.error(xhr.statusText);
} else {
reject(xhr.statusText);
}
});
// Perform the request.
xhr.send(JSON.stringify([{
args: methodArgs,
index: 0,
methodname: methodName,
nosessionupdate: !updateSession,
}]));
})
.then(result => {
pendingPromise.resolve();
return result;
})
.catch(failure => {
pendingPromise.resolve();
if (failure && failure.errorcode) {
if (failure.errorcode === "servicerequireslogin" && updateSession) {
// Redirect to the login page.
window.location = Url.relativeUrl("/login/index.php");
}
}
return Promise.reject(failure);
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment