blob: 038c4c3326404e8408e5a62bf48d187cab64207a [file] [log] [blame]
/* Copyright 2016 The Chromium Authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file or at
* https://developers.google.com/open-source/licenses/bsd
*/
/**
* @fileoverview AJAX-related helper functions.
*/
var DEBOUNCE_THRESH_MS = 2000;
/**
* Simple debouncer to handle text input. Don't try to hit the server
* until the user has stopped typing for a few seconds. E.g.,
* var debouncedKeyHandler = debounce(keyHandler);
* el.addEventListener('keyup', debouncedKeyHandler);
*/
function debounce(func, opt_threshold_ms) {
let timeout;
return function() {
let context = this, args = arguments;
let later = function() {
timeout = null;
func.apply(context, args);
};
clearTimeout(timeout);
timeout = setTimeout(later, opt_threshold_ms || DEBOUNCE_THRESH_MS);
};
}
/**
* Builds a POST string from a parameter dictionary.
* @param {Array|Object} args: parameters to encode. Either an object
* mapping names to values or an Array of doubles containing [key, value].
* @return {string} encoded POST data.
*/
function CS_postData(args) {
let params = [];
if (args instanceof Array) {
for (var key in args) {
let inputValue = args[key];
let name = inputValue[0];
let value = inputValue[1];
if (value !== undefined) {
params.push(name + '=' + encodeURIComponent(String(value)));
}
}
} else {
for (var key in args) {
params.push(key + '=' + encodeURIComponent(String(args[key])));
}
}
params.push('token=' + encodeURIComponent(window.prpcClient.token));
return params.join('&');
}
/**
* Helper for an extremely common kind of XHR: a POST with an XHRF token
* where we silently ignore server or connectivity errors. If the token
* has expired, get a new one and retry the original request with the new
* token.
* @param {string} url request destination.
* @param {function(event)} callback function to be called
* upon successful completion of the request.
* @param {Object} args parameters to encode as POST data.
*/
function CS_doPost(url, callback, args) {
window.prpcClient.ensureTokenIsValid().then(() => {
let xh = XH_XmlHttpCreate();
XH_XmlHttpPOST(xh, url, CS_postData(args), callback);
});
}
/**
* Helper function to strip leading junk characters from a JSON response
* and then parse it into a JS constant.
*
* The reason that "}])'\n" is prepended to the response text is that
* it makes it impossible for a hacker to hit one of our JSON servlets
* via a <script src="..."> tag and do anything with the result. Even
* though a JSON response is just a constant, it could be passed into
* hacker code by tricks such as overriding the array constructor.
*/
function CS_parseJSON(xhr) {
return JSON.parse(xhr.responseText.substr(5));
}
/**
* Promise-based version of CS_parseJSON using the fetch API.
*
* Sends a GET request to a JSON endpoint then strips the XSSI prefix off
* of the response before resolving the promise.
*
* Args:
* url (string): The URL to fetch.
* Returns:
* A promise, resolved when the request returns. Also be sure to call
* .catch() on the promise (or wrap in a try/catch if using async/await)
* if you don't want errors to halt script execution.
*/
function CS_fetch(url) {
return fetch(url, {credentials: 'same-origin'})
.then((res) => res.text())
.then((rawResponse) => JSON.parse(rawResponse.substr(5)));
}
/**
* After we refresh the form token, we need to actually submit the form.
* formToSubmit keeps track of which form the user was trying to submit.
*/
var formToSubmit = null;
/**
* If the form token that was generated when the page was served has
* now expired, then request a refreshed token from the server, and
* don't submit the form until after it arrives.
*/
function refreshTokens(event, formToken, formTokenPath, tokenExpiresSec) {
if (!window.prpcClient.constructor.isTokenExpired(tokenExpiresSec)) {
return;
}
formToSubmit = event.target;
event.preventDefault();
const message = {
token: formToken,
tokenPath: formTokenPath,
};
const refreshTokenPromise = window.prpcClient.call(
'monorail.Sitewide', 'RefreshToken', message);
refreshTokenPromise.then((freshToken) => {
let tokenFields = document.querySelectorAll('input[name=token]');
for (let i = 0; i < tokenFields.length; ++i) {
tokenFields[i].value = freshToken.token;
}
if (formToSubmit) {
formToSubmit.submit();
}
});
}