Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 1 | /* Copyright 2016 The Chromium Authors. All Rights Reserved. |
| 2 | * |
| 3 | * Use of this source code is governed by a BSD-style |
| 4 | * license that can be found in the LICENSE file or at |
| 5 | * https://developers.google.com/open-source/licenses/bsd |
| 6 | */ |
| 7 | |
| 8 | |
| 9 | /** |
| 10 | * @fileoverview AJAX-related helper functions. |
| 11 | */ |
| 12 | |
| 13 | |
| 14 | var DEBOUNCE_THRESH_MS = 2000; |
| 15 | |
| 16 | |
| 17 | /** |
| 18 | * Simple debouncer to handle text input. Don't try to hit the server |
| 19 | * until the user has stopped typing for a few seconds. E.g., |
| 20 | * var debouncedKeyHandler = debounce(keyHandler); |
| 21 | * el.addEventListener('keyup', debouncedKeyHandler); |
| 22 | */ |
| 23 | function debounce(func, opt_threshold_ms) { |
| 24 | let timeout; |
| 25 | return function() { |
| 26 | let context = this, args = arguments; |
| 27 | let later = function() { |
| 28 | timeout = null; |
| 29 | func.apply(context, args); |
| 30 | }; |
| 31 | clearTimeout(timeout); |
| 32 | timeout = setTimeout(later, opt_threshold_ms || DEBOUNCE_THRESH_MS); |
| 33 | }; |
| 34 | } |
| 35 | |
| 36 | |
| 37 | /** |
| 38 | * Builds a POST string from a parameter dictionary. |
| 39 | * @param {Array|Object} args: parameters to encode. Either an object |
| 40 | * mapping names to values or an Array of doubles containing [key, value]. |
| 41 | * @return {string} encoded POST data. |
| 42 | */ |
| 43 | function CS_postData(args) { |
| 44 | let params = []; |
| 45 | |
| 46 | if (args instanceof Array) { |
| 47 | for (var key in args) { |
| 48 | let inputValue = args[key]; |
| 49 | let name = inputValue[0]; |
| 50 | let value = inputValue[1]; |
| 51 | if (value !== undefined) { |
| 52 | params.push(name + '=' + encodeURIComponent(String(value))); |
| 53 | } |
| 54 | } |
| 55 | } else { |
| 56 | for (var key in args) { |
| 57 | params.push(key + '=' + encodeURIComponent(String(args[key]))); |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | params.push('token=' + encodeURIComponent(window.prpcClient.token)); |
| 62 | |
| 63 | return params.join('&'); |
| 64 | } |
| 65 | |
| 66 | /** |
| 67 | * Helper for an extremely common kind of XHR: a POST with an XHRF token |
| 68 | * where we silently ignore server or connectivity errors. If the token |
| 69 | * has expired, get a new one and retry the original request with the new |
| 70 | * token. |
| 71 | * @param {string} url request destination. |
| 72 | * @param {function(event)} callback function to be called |
| 73 | * upon successful completion of the request. |
| 74 | * @param {Object} args parameters to encode as POST data. |
| 75 | */ |
| 76 | function CS_doPost(url, callback, args) { |
| 77 | window.prpcClient.ensureTokenIsValid().then(() => { |
| 78 | let xh = XH_XmlHttpCreate(); |
| 79 | XH_XmlHttpPOST(xh, url, CS_postData(args), callback); |
| 80 | }); |
| 81 | } |
| 82 | |
| 83 | |
| 84 | /** |
| 85 | * Helper function to strip leading junk characters from a JSON response |
| 86 | * and then parse it into a JS constant. |
| 87 | * |
| 88 | * The reason that "}])'\n" is prepended to the response text is that |
| 89 | * it makes it impossible for a hacker to hit one of our JSON servlets |
| 90 | * via a <script src="..."> tag and do anything with the result. Even |
| 91 | * though a JSON response is just a constant, it could be passed into |
| 92 | * hacker code by tricks such as overriding the array constructor. |
| 93 | */ |
| 94 | function CS_parseJSON(xhr) { |
| 95 | return JSON.parse(xhr.responseText.substr(5)); |
| 96 | } |
| 97 | |
| 98 | |
| 99 | /** |
| 100 | * Promise-based version of CS_parseJSON using the fetch API. |
| 101 | * |
| 102 | * Sends a GET request to a JSON endpoint then strips the XSSI prefix off |
| 103 | * of the response before resolving the promise. |
| 104 | * |
| 105 | * Args: |
| 106 | * url (string): The URL to fetch. |
| 107 | * Returns: |
| 108 | * A promise, resolved when the request returns. Also be sure to call |
| 109 | * .catch() on the promise (or wrap in a try/catch if using async/await) |
| 110 | * if you don't want errors to halt script execution. |
| 111 | */ |
| 112 | function CS_fetch(url) { |
| 113 | return fetch(url, {credentials: 'same-origin'}) |
| 114 | .then((res) => res.text()) |
| 115 | .then((rawResponse) => JSON.parse(rawResponse.substr(5))); |
| 116 | } |
| 117 | |
| 118 | |
| 119 | /** |
| 120 | * After we refresh the form token, we need to actually submit the form. |
| 121 | * formToSubmit keeps track of which form the user was trying to submit. |
| 122 | */ |
| 123 | var formToSubmit = null; |
| 124 | |
| 125 | /** |
| 126 | * If the form token that was generated when the page was served has |
| 127 | * now expired, then request a refreshed token from the server, and |
| 128 | * don't submit the form until after it arrives. |
| 129 | */ |
| 130 | function refreshTokens(event, formToken, formTokenPath, tokenExpiresSec) { |
| 131 | if (!window.prpcClient.constructor.isTokenExpired(tokenExpiresSec)) { |
| 132 | return; |
| 133 | } |
| 134 | |
| 135 | formToSubmit = event.target; |
| 136 | event.preventDefault(); |
| 137 | const message = { |
| 138 | token: formToken, |
| 139 | tokenPath: formTokenPath, |
| 140 | }; |
| 141 | const refreshTokenPromise = window.prpcClient.call( |
| 142 | 'monorail.Sitewide', 'RefreshToken', message); |
| 143 | |
| 144 | refreshTokenPromise.then((freshToken) => { |
| 145 | let tokenFields = document.querySelectorAll('input[name=token]'); |
| 146 | for (let i = 0; i < tokenFields.length; ++i) { |
| 147 | tokenFields[i].value = freshToken.token; |
| 148 | } |
| 149 | if (formToSubmit) { |
| 150 | formToSubmit.submit(); |
| 151 | } |
| 152 | }); |
| 153 | } |