Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(433)

Unified Diff: remoting/webapp/crd/js/xhr.js

Issue 1028683004: Added better error and OAuth support in xhr.js. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « remoting/webapp/crd/js/host_list_api_impl.js ('k') | remoting/webapp/crd/js/xhr_unittest.js » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: remoting/webapp/crd/js/xhr.js
diff --git a/remoting/webapp/crd/js/xhr.js b/remoting/webapp/crd/js/xhr.js
index cdefb0aa4d81d45d2dee8763a2d4f19c3404c651..cc50b27745e4fce258f1715982795f436b6dd915 100644
--- a/remoting/webapp/crd/js/xhr.js
+++ b/remoting/webapp/crd/js/xhr.js
@@ -17,13 +17,7 @@ var remoting = remoting || {};
* @param {remoting.Xhr.Params} params
*/
remoting.Xhr = function(params) {
- /** @private @const {!XMLHttpRequest} */
- this.nativeXhr_ = new XMLHttpRequest();
- this.nativeXhr_.onreadystatechange = this.onReadyStateChange_.bind(this);
- this.nativeXhr_.withCredentials = params.withCredentials || false;
-
- /** @private @const */
- this.responseType_ = params.responseType || remoting.Xhr.ResponseType.TEXT;
+ remoting.Xhr.checkParams_(params);
// Apply URL parameters.
var url = params.url;
@@ -35,92 +29,83 @@ remoting.Xhr = function(params) {
remoting.Xhr.removeNullFields_(params.urlParams));
}
if (parameterString) {
- base.debug.assert(url.indexOf('?') == -1);
url += '?' + parameterString;
}
- // Check that the content spec is consistent.
- if ((Number(params.textContent !== undefined) +
- Number(params.formContent !== undefined) +
- Number(params.jsonContent !== undefined)) > 1) {
- throw new Error(
- 'may only specify one of textContent, formContent, and jsonContent');
- }
-
// Prepare the build modified headers.
- var headers = remoting.Xhr.removeNullFields_(params.headers);
+ /** @const */
+ this.headers_ = remoting.Xhr.removeNullFields_(params.headers);
// Convert the content fields to a single text content variable.
/** @private {?string} */
this.content_ = null;
if (params.textContent !== undefined) {
+ this.maybeSetContentType_('text/plain');
this.content_ = params.textContent;
} else if (params.formContent !== undefined) {
- if (!('Content-type' in headers)) {
- headers['Content-type'] = 'application/x-www-form-urlencoded';
- }
+ this.maybeSetContentType_('application/x-www-form-urlencoded');
this.content_ = remoting.Xhr.urlencodeParamHash(params.formContent);
} else if (params.jsonContent !== undefined) {
- if (!('Content-type' in headers)) {
- headers['Content-type'] = 'application/json; charset=UTF-8';
- }
+ this.maybeSetContentType_('application/json');
this.content_ = JSON.stringify(params.jsonContent);
}
// Apply the oauthToken field.
if (params.oauthToken !== undefined) {
- base.debug.assert(!('Authorization' in headers));
- headers['Authorization'] = 'Bearer ' + params.oauthToken;
+ this.setAuthToken_(params.oauthToken);
}
- this.nativeXhr_.open(params.method, url, true);
- for (var key in headers) {
- this.nativeXhr_.setRequestHeader(key, headers[key]);
+ /** @private @const {boolean} */
+ this.acceptJson_ = params.acceptJson || false;
+ if (this.acceptJson_) {
+ this.maybeSetHeader_('Accept', 'application/json');
}
+ // Apply useIdentity field.
+ /** @const {boolean} */
+ this.useIdentity_ = params.useIdentity || false;
+
+ /** @private @const {!XMLHttpRequest} */
+ this.nativeXhr_ = new XMLHttpRequest();
+ this.nativeXhr_.onreadystatechange = this.onReadyStateChange_.bind(this);
+ this.nativeXhr_.withCredentials = params.withCredentials || false;
+ this.nativeXhr_.open(params.method, url, true);
+
/** @private {base.Deferred<!remoting.Xhr.Response>} */
this.deferred_ = null;
};
/**
- * @enum {string}
- */
-remoting.Xhr.ResponseType = {
- TEXT: 'TEXT', // Request a plain text response (default).
- JSON: 'JSON', // Request a JSON response.
- NONE: 'NONE' // Don't request any response.
-};
-
-/**
- * Parameters for the 'start' function.
+ * Parameters for the 'start' function. Unless otherwise noted, all
+ * parameters are optional.
+ *
+ * method: (required) The HTTP method to use.
*
- * method: The HTTP method to use.
+ * url: (required) The URL to request.
*
- * url: The URL to request.
+ * urlParams: Parameters to be appended to the URL. Null-valued
+ * parameters are omitted.
*
- * urlParams: (optional) Parameters to be appended to the URL.
- * Null-valued parameters are omitted.
+ * textContent: Text to be sent as the request body.
*
- * textContent: (optional) Text to be sent as the request body.
+ * formContent: Data to be URL-encoded and sent as the request body.
+ * Causes Content-type header to be set appropriately.
*
- * formContent: (optional) Data to be URL-encoded and sent as the
- * request body. Causes Content-type header to be set
- * appropriately.
+ * jsonContent: Data to be JSON-encoded and sent as the request body.
+ * Causes Content-type header to be set appropriately.
*
- * jsonContent: (optional) Data to be JSON-encoded and sent as the
- * request body. Causes Content-type header to be set
- * appropriately.
+ * headers: Additional request headers to be sent. Null-valued
+ * headers are omitted.
*
- * headers: (optional) Additional request headers to be sent.
- * Null-valued headers are omitted.
+ * withCredentials: Value of the XHR's withCredentials field.
*
- * withCredentials: (optional) Value of the XHR's withCredentials field.
+ * oauthToken: An OAuth2 token used to construct an Authentication
+ * header.
*
- * oauthToken: (optional) An OAuth2 token used to construct an
- * Authentication header.
+ * useIdentity: Use identity API to get an OAuth2 token.
*
- * responseType: (optional) Request a response of a specific
- * type. Default: TEXT.
+ * acceptJson: If true, send an Accept header indicating that a JSON
+ * response is expected.
*
* @typedef {{
* method: string,
@@ -132,25 +117,18 @@ remoting.Xhr.ResponseType = {
* headers:(Object<string,?string>|undefined),
* withCredentials:(boolean|undefined),
* oauthToken:(string|undefined),
- * responseType:(remoting.Xhr.ResponseType|undefined)
+ * useIdentity:(boolean|undefined),
+ * acceptJson:(boolean|undefined)
* }}
*/
remoting.Xhr.Params;
/**
- * Aborts the HTTP request. Does nothing is the request has finished
- * already.
- */
-remoting.Xhr.prototype.abort = function() {
- this.nativeXhr_.abort();
-};
-
-/**
* Starts and HTTP request and gets a promise that is resolved when
* the request completes.
*
- * Any error that prevents receiving an HTTP status
- * code causes this promise to be rejected.
+ * Any error that prevents sending the request causes the promise to
+ * be rejected.
*
* NOTE: Calling this method more than once will return the same
* promise and not start a new request, despite what the name
@@ -160,22 +138,117 @@ remoting.Xhr.prototype.abort = function() {
*/
remoting.Xhr.prototype.start = function() {
if (this.deferred_ == null) {
- var xhr = this.nativeXhr_;
- xhr.send(this.content_);
- this.content_ = null; // for gc
this.deferred_ = new base.Deferred();
+
+ // Send the XHR, possibly after getting an OAuth token.
+ var that = this;
+ if (this.useIdentity_) {
+ remoting.identity.getToken().then(function(token) {
+ base.debug.assert(that.nativeXhr_.readyState == 1);
+ that.setAuthToken_(token);
+ that.sendXhr_();
+ }).catch(function(error) {
+ that.deferred_.reject(error);
+ });
+ } else {
+ this.sendXhr_();
+ }
}
return this.deferred_.promise();
};
/**
+ * @param {remoting.Xhr.Params} params
+ * @throws {Error} if params are invalid
+ */
+remoting.Xhr.checkParams_ = function(params) {
+ if (params.urlParams) {
+ if (params.url.indexOf('?') != -1) {
+ throw new Error('URL may not contain "?" when urlParams is set');
+ }
+ if (params.url.indexOf('#') != -1) {
+ throw new Error('URL may not contain "#" when urlParams is set');
+ }
+ }
+
+ if ((Number(params.textContent !== undefined) +
+ Number(params.formContent !== undefined) +
+ Number(params.jsonContent !== undefined)) > 1) {
+ throw new Error(
+ 'may only specify one of textContent, formContent, and jsonContent');
+ }
+
+ if (params.useIdentity && params.oauthToken !== undefined) {
+ throw new Error('may not specify both useIdentity and oauthToken');
+ }
+
+ if ((params.useIdentity || params.oauthToken !== undefined) &&
+ params.headers &&
+ params.headers['Authorization'] != null) {
+ throw new Error(
+ 'may not specify useIdentity or oauthToken ' +
+ 'with an Authorization header');
+ }
+};
+
+/**
+ * @param {string} token
+ * @private
+ */
+remoting.Xhr.prototype.setAuthToken_ = function(token) {
+ this.setHeader_('Authorization', 'Bearer ' + token);
+};
+
+/**
+ * @param {string} type
+ * @private
+ */
+remoting.Xhr.prototype.maybeSetContentType_ = function(type) {
+ this.maybeSetHeader_('Content-type', type + '; charset=UTF-8');
+};
+
+/**
+ * @param {string} key
+ * @param {string} value
+ * @private
+ */
+remoting.Xhr.prototype.setHeader_ = function(key, value) {
+ var wasSet = this.maybeSetHeader_(key, value);
+ base.debug.assert(wasSet);
+};
+
+/**
+ * @param {string} key
+ * @param {string} value
+ * @return {boolean}
+ * @private
+ */
+remoting.Xhr.prototype.maybeSetHeader_ = function(key, value) {
+ if (!(key in this.headers_)) {
+ this.headers_[key] = value;
+ return true;
+ }
+ return false;
+};
+
+/** @private */
+remoting.Xhr.prototype.sendXhr_ = function() {
+ for (var key in this.headers_) {
+ this.nativeXhr_.setRequestHeader(key, this.headers_[key]);
+ }
+ this.nativeXhr_.send(this.content_);
+ this.content_ = null; // for gc
+};
+
+/**
* @private
*/
remoting.Xhr.prototype.onReadyStateChange_ = function() {
var xhr = this.nativeXhr_;
if (xhr.readyState == 4) {
// See comments at remoting.Xhr.Response.
- this.deferred_.resolve(new remoting.Xhr.Response(xhr, this.responseType_));
+ this.deferred_.resolve(new remoting.Xhr.Response(
+ xhr, this.acceptJson_));
}
};
@@ -189,11 +262,11 @@ remoting.Xhr.prototype.onReadyStateChange_ = function() {
*
* @constructor
* @param {!XMLHttpRequest} xhr
- * @param {remoting.Xhr.ResponseType} type
+ * @param {boolean} allowJson
*/
-remoting.Xhr.Response = function(xhr, type) {
+remoting.Xhr.Response = function(xhr, allowJson) {
/** @private @const */
- this.type_ = type;
+ this.allowJson_ = allowJson;
/**
* The HTTP status code.
@@ -215,6 +288,9 @@ remoting.Xhr.Response = function(xhr, type) {
/** @private {string} */
this.text_ = xhr.responseText || '';
+
+ /** @private {*|undefined} */
+ this.json_ = undefined;
};
/**
@@ -225,11 +301,16 @@ remoting.Xhr.Response.prototype.getText = function() {
};
/**
+ * Get the JSON content of the response. Requires acceptJson to have
+ * been true in the request.
* @return {*} The parsed JSON content of the response.
*/
remoting.Xhr.Response.prototype.getJson = function() {
- base.debug.assert(this.type_ == remoting.Xhr.ResponseType.JSON);
- return JSON.parse(this.text_);
+ base.debug.assert(this.allowJson_);
+ if (this.json_ === undefined) {
+ this.json_ = JSON.parse(this.text_);
+ }
+ return this.json_;
};
/**
@@ -271,33 +352,3 @@ remoting.Xhr.urlencodeParamHash = function(paramHash) {
}
return '';
};
-
-/**
- * Generic success/failure response proxy.
- *
- * TODO(jrw): Stop using this and move default error handling directly
- * into Xhr class.
- *
- * @param {function():void} onDone
- * @param {function(!remoting.Error):void} onError
- * @param {Array<remoting.Error.Tag>=} opt_ignoreErrors
- * @return {function(!remoting.Xhr.Response):void}
- */
-remoting.Xhr.defaultResponse = function(onDone, onError, opt_ignoreErrors) {
- /** @param {!remoting.Xhr.Response} response */
- var result = function(response) {
- var error = remoting.Error.fromHttpStatus(response.status);
- if (error.isNone()) {
- onDone();
- return;
- }
-
- if (opt_ignoreErrors && error.hasTag.apply(error, opt_ignoreErrors)) {
- onDone();
- return;
- }
-
- onError(error);
- };
- return result;
-};
« no previous file with comments | « remoting/webapp/crd/js/host_list_api_impl.js ('k') | remoting/webapp/crd/js/xhr_unittest.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698