Index: appengine/monorail/static/js/framework/framework-ajax.js |
diff --git a/appengine/monorail/static/js/framework/framework-ajax.js b/appengine/monorail/static/js/framework/framework-ajax.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..6b1136b1965b7939c5952e2c98a0f1ff0134d897 |
--- /dev/null |
+++ b/appengine/monorail/static/js/framework/framework-ajax.js |
@@ -0,0 +1,136 @@ |
+/* 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. |
+ */ |
+ |
+ |
+/** |
+ * Builds a POST string from a parameter dictionary. |
+ * @param {Object} args parameters to encode. |
+ * @param {boolean} opt_includeToken whether to include an XSRF token. |
+ * If unspecified, defaults to true. Requires the user to be logged in. |
+ * @return {string} encoded POST data. |
+ */ |
+function CS_postData(args, opt_token) { |
+ var params = []; |
+ for (var key in args) { |
+ params.push(key + "=" + encodeURIComponent(String(args[key]))); |
+ } |
+ if (opt_token) { |
+ params.push('token=' + encodeURIComponent(opt_token)); |
+ } else if (opt_token !== false) { |
+ params.push('token=' + encodeURIComponent(CS_env.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, opt_token, opt_tokenPath) { |
+ if (isTokenExpired()) { |
+ var refreshXHR = XH_XmlHttpCreate(); |
+ var refreshURL = '/hosting/tokenRefresh.do'; |
+ var refreshArgs = { |
+ form_token: opt_token || CS_env.token, |
+ form_token_path: opt_tokenPath || 'xhr' |
+ }; |
+ var refreshCallback = function(event) { |
+ var xhr = event.target; |
+ if (xhr.readyState != 4 || xhr.status != 200) |
+ return; |
+ var resp = CS_parseJSON(xhr); |
+ if (opt_tokenPath) |
+ CS_env[opt_tokenPath] = resp.form_token; |
+ CS_env.tokenExpiresSec = Number(resp.token_expires_sec); |
+ var retryXh = XH_XmlHttpCreate(); |
+ XH_XmlHttpPOST( |
+ retryXh, url, CS_postData(args, resp.form_token), callback); |
+ }; |
+ XH_XmlHttpPOST( |
+ refreshXHR, refreshURL, CS_postData(refreshArgs), refreshCallback); |
+ } else { |
+ var xh = XH_XmlHttpCreate(); |
+ XH_XmlHttpPOST( |
+ xh, url, |
+ CS_postData(args, CS_env[opt_tokenPath] || opt_token), |
+ 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)); |
+} |
+ |
+ |
+function isTokenExpired(opt_tokenExpiresSec) { |
+ var expiresSec = opt_tokenExpiresSec || CS_env.tokenExpiresSec; |
+ var tokenExpiresDate = new Date(expiresSec * 1000); |
+ return tokenExpiresDate <= new Date(); |
+} |
+ |
+/** |
+ * 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 (!isTokenExpired(tokenExpiresSec)) |
+ return; |
+ |
+ formToSubmit = event.target; |
+ event.preventDefault(); |
+ CS_doPost("/hosting/tokenRefresh.do", gotXSRFToken, |
+ {form_token: formToken, |
+ form_token_path: formTokenPath}); |
+} |
+ |
+/** |
+ * If we got a new XSRF token from the server, use it to actually |
+ * submit the form that the user wanted to submit. |
+ */ |
+function gotXSRFToken(event) { |
+ var xhr = event.target; |
+ if (xhr.readyState != 4 || xhr.status != 200) |
+ return; |
+ var resp = CS_parseJSON(xhr); |
+ var freshFormToken = resp["form_token"]; |
+ var tokenFields = document.querySelectorAll("input[name=token]"); |
+ for (var i = 0; i < tokenFields.length; ++i) { |
+ tokenFields[i].value = freshFormToken; |
+ } |
+ if (formToSubmit) { |
+ formToSubmit.submit(); |
+ } |
+} |