| // Copyright 2016 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| |
| /** |
| * @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(); |
| } |
| }); |
| } |