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