blob: 038c4c3326404e8408e5a62bf48d187cab64207a [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001/* 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
14var 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 */
23function 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 */
43function 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 */
76function 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 */
94function 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 */
112function 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 */
123var 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 */
130function 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}