summaryrefslogtreecommitdiffstats
path: root/remoting
diff options
context:
space:
mode:
authorjrw <jrw@chromium.org>2015-03-25 20:45:11 -0700
committerCommit bot <commit-bot@chromium.org>2015-03-26 03:46:36 +0000
commitd81682810727f6ae3947f259e7f4400a9df4da76 (patch)
treece52e2711c14ff0e6a79a29a1339aec85ad346f2 /remoting
parent76f34770c06ecb7ed15bfa0ff9078d25a1fdec29 (diff)
downloadchromium_src-d81682810727f6ae3947f259e7f4400a9df4da76.zip
chromium_src-d81682810727f6ae3947f259e7f4400a9df4da76.tar.gz
chromium_src-d81682810727f6ae3947f259e7f4400a9df4da76.tar.bz2
Added better error and OAuth support in xhr.js.
The Xhr class can now use the remoting.Identity API to request OAuth tokens, and it can convert HTTP errors into rejected promises. Also replaced the responseType parameter with the simpler acceptJson parameter. Review URL: https://codereview.chromium.org/1028683004 Cr-Commit-Position: refs/heads/master@{#322307}
Diffstat (limited to 'remoting')
-rw-r--r--remoting/app_remoting_webapp_files.gypi1
-rw-r--r--remoting/webapp/crd/js/dns_blackhole_checker.js11
-rw-r--r--remoting/webapp/crd/js/host_list_api_impl.js64
-rw-r--r--remoting/webapp/crd/js/xhr.js263
-rw-r--r--remoting/webapp/crd/js/xhr_unittest.js328
-rw-r--r--remoting/webapp/js_proto/sinon_proto.js18
6 files changed, 450 insertions, 235 deletions
diff --git a/remoting/app_remoting_webapp_files.gypi b/remoting/app_remoting_webapp_files.gypi
index 69c8baf..2b263be 100644
--- a/remoting/app_remoting_webapp_files.gypi
+++ b/remoting/app_remoting_webapp_files.gypi
@@ -27,6 +27,7 @@
'webapp/app_remoting/js/feedback_consent.js',
'webapp/base/js/base.js',
'webapp/crd/js/error.js',
+ 'webapp/crd/js/identity.js',
'webapp/crd/js/oauth2_api.js',
'webapp/crd/js/oauth2_api_impl.js',
'webapp/crd/js/plugin_settings.js',
diff --git a/remoting/webapp/crd/js/dns_blackhole_checker.js b/remoting/webapp/crd/js/dns_blackhole_checker.js
index 95b342c..77f588c 100644
--- a/remoting/webapp/crd/js/dns_blackhole_checker.js
+++ b/remoting/webapp/crd/js/dns_blackhole_checker.js
@@ -110,10 +110,7 @@ remoting.DnsBlackholeChecker.prototype.getJid = function() {
};
remoting.DnsBlackholeChecker.prototype.dispose = function() {
- if (this.xhr_) {
- this.xhr_.abort();
- this.xhr_ = null;
- }
+ this.xhr_ = null;
base.dispose(this.signalStrategy_);
this.setState_(remoting.SignalStrategy.State.CLOSED);
};
@@ -157,6 +154,12 @@ remoting.DnsBlackholeChecker.prototype.onWrappedSignalStrategyStateChanged_ =
* @private
*/
remoting.DnsBlackholeChecker.prototype.onHttpRequestDone_ = function(response) {
+ if (this.xhr_ == null) {
+ // This happens when the dispose() method is called while a
+ // request is pending.
+ return;
+ }
+
this.xhr_ = null;
if (response.status >= 200 && response.status <= 299) {
console.log("DNS blackhole check succeeded.");
diff --git a/remoting/webapp/crd/js/host_list_api_impl.js b/remoting/webapp/crd/js/host_list_api_impl.js
index 52f9377..757c439 100644
--- a/remoting/webapp/crd/js/host_list_api_impl.js
+++ b/remoting/webapp/crd/js/host_list_api_impl.js
@@ -53,23 +53,18 @@ remoting.HostListApiImpl.prototype.get = function(onDone, onError) {
*/
remoting.HostListApiImpl.prototype.put =
function(hostId, hostName, hostPublicKey, onDone, onError) {
- /** @param {string} token */
- var onToken = function(token) {
- var newHostDetails = {
+ new remoting.Xhr({
+ method: 'PUT',
+ url: remoting.settings.DIRECTORY_API_BASE_URL + '/@me/hosts/' + hostId,
+ jsonContent: {
'data': {
'hostId': hostId,
'hostName': hostName,
'publicKey': hostPublicKey
}
- };
- new remoting.Xhr({
- method: 'PUT',
- url: remoting.settings.DIRECTORY_API_BASE_URL + '/@me/hosts/' + hostId,
- jsonContent: newHostDetails,
- oauthToken: token
- }).start().then(remoting.Xhr.defaultResponse(onDone, onError));
- };
- remoting.identity.getToken().then(onToken, remoting.Error.handler(onError));
+ },
+ useIdentity: true
+ }).start().then(remoting.HostListApiImpl.defaultResponse_(onDone, onError));
};
/**
@@ -80,16 +75,12 @@ remoting.HostListApiImpl.prototype.put =
* @param {string} hostId
*/
remoting.HostListApiImpl.prototype.remove = function(hostId, onDone, onError) {
- /** @param {string} token */
- var onToken = function(token) {
- new remoting.Xhr({
- method: 'DELETE',
- url: remoting.settings.DIRECTORY_API_BASE_URL + '/@me/hosts/' + hostId,
- oauthToken: token
- }).start().then(remoting.Xhr.defaultResponse(
- onDone, onError, [remoting.Error.Tag.NOT_FOUND]));
- };
- remoting.identity.getToken().then(onToken, remoting.Error.handler(onError));
+ new remoting.Xhr({
+ method: 'DELETE',
+ url: remoting.settings.DIRECTORY_API_BASE_URL + '/@me/hosts/' + hostId,
+ useIdentity: true
+ }).start().then(remoting.HostListApiImpl.defaultResponse_(
+ onDone, onError, [remoting.Error.Tag.NOT_FOUND]));
};
/**
@@ -135,6 +126,35 @@ remoting.HostListApiImpl.prototype.parseHostListResponse_ =
}
};
+/**
+ * Generic success/failure response proxy.
+ *
+ * @param {function():void} onDone
+ * @param {function(!remoting.Error):void} onError
+ * @param {Array<remoting.Error.Tag>=} opt_ignoreErrors
+ * @return {function(!remoting.Xhr.Response):void}
+ * @private
+ */
+remoting.HostListApiImpl.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;
+};
+
/** @type {remoting.HostListApi} */
remoting.hostListApi = new remoting.HostListApiImpl();
diff --git a/remoting/webapp/crd/js/xhr.js b/remoting/webapp/crd/js/xhr.js
index cdefb0aa..cc50b27 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;
-};
diff --git a/remoting/webapp/crd/js/xhr_unittest.js b/remoting/webapp/crd/js/xhr_unittest.js
index b02987e..c8e96e0 100644
--- a/remoting/webapp/crd/js/xhr_unittest.js
+++ b/remoting/webapp/crd/js/xhr_unittest.js
@@ -10,16 +10,27 @@
'use strict';
+/** @type {sinon.FakeXhrCtrl} */
+var fakeXhrCtrl;
+
/** @type {sinon.FakeXhr} */
var fakeXhr;
QUnit.module('xhr', {
beforeEach: function() {
fakeXhr = null;
- sinon.useFakeXMLHttpRequest().onCreate =
+ fakeXhrCtrl = sinon.useFakeXMLHttpRequest();
+ fakeXhrCtrl.onCreate =
function(/** sinon.FakeXhr */ xhr) {
fakeXhr = xhr;
};
+ remoting.identity = new remoting.Identity();
+ chromeMocks.activate(['identity']);
+ chromeMocks.identity.mock$setToken('my_token');
+ },
+ afterEach: function() {
+ chromeMocks.restore();
+ remoting.identity = null;
}
});
@@ -38,14 +49,140 @@ QUnit.test('urlencodeParamHash', function(assert) {
'k1=v1&k2=v2');
});
-QUnit.test('basic GET', function(assert) {
+//
+// Test for what happens when the parameters are specified
+// incorrectly.
+//
+
+QUnit.test('invalid *content parameters', function(assert) {
+ assert.throws(function() {
+ new remoting.Xhr({
+ method: 'POST',
+ url: 'http://foo.com',
+ jsonContent: {},
+ formContent: {}
+ });
+ });
+
+ assert.throws(function() {
+ new remoting.Xhr({
+ method: 'POST',
+ url: 'http://foo.com',
+ textContent: '',
+ formContent: {}
+ });
+ });
+
+ assert.throws(function() {
+ new remoting.Xhr({
+ method: 'POST',
+ url: 'http://foo.com',
+ textContent: '',
+ jsonContent: {}
+ });
+ });
+});
+
+
+QUnit.test('invalid URL parameters', function(assert) {
+ assert.throws(function() {
+ new remoting.Xhr({
+ method: 'POST',
+ url: 'http://foo.com?',
+ urlParams: {}
+ });
+ });
+
+ assert.throws(function() {
+ new remoting.Xhr({
+ method: 'POST',
+ url: 'http://foo.com#',
+ urlParams: {}
+ });
+ });
+});
+
+QUnit.test('invalid auth parameters', function(assert) {
+ assert.throws(function() {
+ new remoting.Xhr({
+ method: 'POST',
+ url: 'http://foo.com',
+ useIdentity: false,
+ oauthToken: '',
+ headers: {
+ 'Authorization': ''
+ }
+ });
+ });
+
+ assert.throws(function() {
+ new remoting.Xhr({
+ method: 'POST',
+ url: 'http://foo.com',
+ useIdentity: true,
+ headers: {
+ 'Authorization': ''
+ }
+ });
+ });
+
+ assert.throws(function() {
+ new remoting.Xhr({
+ method: 'POST',
+ url: 'http://foo.com',
+ useIdentity: true,
+ oauthToken: '',
+ headers: {}
+ });
+ });
+});
+
+QUnit.test('invalid auth parameters', function(assert) {
+ assert.throws(function() {
+ new remoting.Xhr({
+ method: 'POST',
+ url: 'http://foo.com',
+ useIdentity: false,
+ oauthToken: '',
+ headers: {
+ 'Authorization': ''
+ }
+ });
+ });
+
+ assert.throws(function() {
+ new remoting.Xhr({
+ method: 'POST',
+ url: 'http://foo.com',
+ useIdentity: true,
+ headers: {
+ 'Authorization': ''
+ }
+ });
+ });
+
+ assert.throws(function() {
+ new remoting.Xhr({
+ method: 'POST',
+ url: 'http://foo.com',
+ useIdentity: true,
+ oauthToken: '',
+ headers: {}
+ });
+ });
+});
+
+//
+// The typical case.
+//
+
+QUnit.test('successful GET', function(assert) {
var promise = new remoting.Xhr({
method: 'GET',
- url: 'http://foo.com',
- responseType: remoting.Xhr.ResponseType.TEXT
+ url: 'http://foo.com'
}).start().then(function(response) {
- assert.equal(response.status, 200);
- assert.equal(response.getText(), 'body');
+ assert.equal(response.status, 200);
+ assert.equal(response.getText(), 'body');
});
assert.equal(fakeXhr.method, 'GET');
assert.equal(fakeXhr.url, 'http://foo.com');
@@ -56,96 +193,105 @@ QUnit.test('basic GET', function(assert) {
return promise;
});
-QUnit.test('GET with param string', function(assert) {
+//
+// Tests for the effect of acceptJson.
+//
+
+QUnit.test('acceptJson required', function(assert) {
var promise = new remoting.Xhr({
method: 'GET',
- url: 'http://foo.com',
- responseType: remoting.Xhr.ResponseType.TEXT,
- urlParams: 'the_param_string'
+ url: 'http://foo.com'
}).start().then(function(response) {
- assert.equal(response.status, 200);
- assert.equal(response.getText(), 'body');
+ assert.throws(response.getJson);
});
- assert.equal(fakeXhr.method, 'GET');
- assert.equal(fakeXhr.url, 'http://foo.com?the_param_string');
- assert.equal(fakeXhr.withCredentials, false);
- assert.equal(fakeXhr.requestBody, null);
- assert.ok(!('Content-type' in fakeXhr.requestHeaders));
- fakeXhr.respond(200, {}, 'body');
+ fakeXhr.respond(200, {}, '{}');
return promise;
});
-QUnit.test('GET with param object', function(assert) {
+QUnit.test('JSON response', function(assert) {
+ var responseJson = {
+ 'myJsonData': [true]
+ };
+ var responseText = JSON.stringify(responseJson);
var promise = new remoting.Xhr({
method: 'GET',
url: 'http://foo.com',
- responseType: remoting.Xhr.ResponseType.TEXT,
- urlParams: {'a': 'b', 'c': 'd'}
+ acceptJson: true
}).start().then(function(response) {
- assert.equal(response.status, 200);
- assert.equal(response.getText(), 'body');
+ // Calling getText is still OK even when a JSON response is
+ // requested.
+ assert.equal(
+ response.getText(),
+ responseText);
+ // Check that getJson works as advertised.
+ assert.deepEqual(
+ response.getJson(),
+ responseJson);
+ // Calling getJson multiple times doesn't re-parse the response.
+ assert.strictEqual(
+ response.getJson(),
+ response.getJson());
});
- assert.equal(fakeXhr.method, 'GET');
- assert.equal(fakeXhr.url, 'http://foo.com?a=b&c=d');
- assert.equal(fakeXhr.withCredentials, false);
- assert.equal(fakeXhr.requestBody, null);
- assert.ok(!('Content-type' in fakeXhr.requestHeaders));
- fakeXhr.respond(200, {}, 'body');
+ fakeXhr.respond(200, {}, responseText);
return promise;
});
+//
+// Tests for various parameters that modify the HTTP request.
+//
+
+QUnit.test('GET with param string', function(assert) {
+ new remoting.Xhr({
+ method: 'GET',
+ url: 'http://foo.com',
+ urlParams: 'the_param_string'
+ }).start();
+ assert.equal(fakeXhr.url, 'http://foo.com?the_param_string');
+});
+
+QUnit.test('GET with param object', function(assert) {
+ new remoting.Xhr({
+ method: 'GET',
+ url: 'http://foo.com',
+ urlParams: {'a': 'b', 'c': 'd'}
+ }).start();
+ assert.equal(fakeXhr.url, 'http://foo.com?a=b&c=d');
+});
+
QUnit.test('GET with headers', function(assert) {
- var promise = new remoting.Xhr({
+ new remoting.Xhr({
method: 'GET',
url: 'http://foo.com',
- responseType: remoting.Xhr.ResponseType.TEXT,
headers: {'Header1': 'headerValue1', 'Header2': 'headerValue2'}
- }).start().then(function(response) {
- assert.equal(response.status, 200);
- assert.equal(response.getText(), 'body');
- });
- assert.equal(fakeXhr.method, 'GET');
- assert.equal(fakeXhr.url, 'http://foo.com');
- assert.equal(fakeXhr.withCredentials, false);
- assert.equal(fakeXhr.requestBody, null);
+ }).start();
assert.equal(
fakeXhr.requestHeaders['Header1'],
'headerValue1');
assert.equal(
fakeXhr.requestHeaders['Header2'],
'headerValue2');
- assert.ok(!('Content-type' in fakeXhr.requestHeaders));
- fakeXhr.respond(200, {}, 'body');
- return promise;
});
QUnit.test('GET with credentials', function(assert) {
- var promise = new remoting.Xhr({
+ new remoting.Xhr({
method: 'GET',
url: 'http://foo.com',
- responseType: remoting.Xhr.ResponseType.TEXT,
withCredentials: true
- }).start().then(function(response) {
- assert.equal(response.status, 200);
- assert.equal(response.getText(), 'body');
- });
- assert.equal(fakeXhr.method, 'GET');
- assert.equal(fakeXhr.url, 'http://foo.com');
+ }).start();
assert.equal(fakeXhr.withCredentials, true);
- assert.equal(fakeXhr.requestBody, null);
- assert.ok(!('Content-type' in fakeXhr.requestHeaders));
- fakeXhr.respond(200, {}, 'body');
- return promise;
});
+//
+// Checking that typical POST requests work.
+//
+
QUnit.test('POST with text content', function(assert) {
var done = assert.async();
var promise = new remoting.Xhr({
method: 'POST',
url: 'http://foo.com',
- responseType: remoting.Xhr.ResponseType.TEXT,
textContent: 'the_content_string'
}).start().then(function(response) {
assert.equal(response.status, 200);
@@ -156,71 +302,53 @@ QUnit.test('POST with text content', function(assert) {
assert.equal(fakeXhr.url, 'http://foo.com');
assert.equal(fakeXhr.withCredentials, false);
assert.equal(fakeXhr.requestBody, 'the_content_string');
- assert.ok(!('Content-type' in fakeXhr.requestHeaders));
+ assert.equal(fakeXhr.requestHeaders['Content-type'],
+ 'text/plain; charset=UTF-8');
fakeXhr.respond(200, {}, 'body');
return promise;
});
QUnit.test('POST with form content', function(assert) {
- var promise = new remoting.Xhr({
+ new remoting.Xhr({
method: 'POST',
url: 'http://foo.com',
- responseType: remoting.Xhr.ResponseType.TEXT,
formContent: {'a': 'b', 'c': 'd'}
- }).start().then(function(response) {
- assert.equal(response.status, 200);
- assert.equal(response.getText(), 'body');
- });
- assert.equal(fakeXhr.method, 'POST');
- assert.equal(fakeXhr.url, 'http://foo.com');
- assert.equal(fakeXhr.withCredentials, false);
+ }).start();
assert.equal(fakeXhr.requestBody, 'a=b&c=d');
assert.equal(
fakeXhr.requestHeaders['Content-type'],
- 'application/x-www-form-urlencoded');
- fakeXhr.respond(200, {}, 'body');
- return promise;
+ 'application/x-www-form-urlencoded; charset=UTF-8');
});
-QUnit.test('defaultResponse 200', function(assert) {
- var done = assert.async();
-
- var onDone = function() {
- assert.ok(true);
- done();
- };
-
- var onError = function(error) {
- assert.ok(false);
- done();
- };
+//
+// Tests for authentication-related options.
+//
+QUnit.test('GET with auth token', function(assert) {
new remoting.Xhr({
- method: 'POST',
- url: 'http://foo.com'
- }).start().then(remoting.Xhr.defaultResponse(onDone, onError));
- fakeXhr.respond(200, {}, '');
+ method: 'GET',
+ url: 'http://foo.com',
+ oauthToken: 'my_token'
+ }).start();
+ assert.equal(fakeXhr.requestHeaders['Authorization'],
+ 'Bearer my_token');
});
+QUnit.test('GET with useIdentity', function(assert) {
+ var xhr = new remoting.Xhr({
+ method: 'GET',
+ url: 'http://foo.com',
+ useIdentity: true
+ });
-QUnit.test('defaultResponse 404', function(assert) {
- var done = assert.async();
+ xhr.start();
- var onDone = function() {
- assert.ok(false);
- done();
- };
-
- var onError = function(error) {
- assert.ok(true);
+ var done = assert.async();
+ fakeXhr.addEventListener('loadstart', function() {
+ assert.equal(fakeXhr.requestHeaders['Authorization'],
+ 'Bearer my_token');
done();
- };
-
- new remoting.Xhr({
- method: 'POST',
- url: 'http://foo.com'
- }).start().then(remoting.Xhr.defaultResponse(onDone, onError));
- fakeXhr.respond(404, {}, '');
+ });
});
})();
diff --git a/remoting/webapp/js_proto/sinon_proto.js b/remoting/webapp/js_proto/sinon_proto.js
index 9e223dc..a34d76a 100644
--- a/remoting/webapp/js_proto/sinon_proto.js
+++ b/remoting/webapp/js_proto/sinon_proto.js
@@ -127,12 +127,23 @@ sinon.TestStub.prototype.onFirstCall = function() {};
/** @returns {Object} */
sinon.createStubInstance = function (/** * */ constructor) {};
-/** @return {sinon.FakeXhr} */
+/** @interface */
+sinon.FakeXhrCtrl = function() {};
+
+/**
+ * @type {?function(!sinon.FakeXhr)}
+ */
+sinon.FakeXhrCtrl.prototype.onCreate;
+
+/** @return {sinon.FakeXhrCtrl} */
sinon.useFakeXMLHttpRequest = function() {};
/** @interface */
sinon.FakeXhr = function() {};
+/** @type {number} */
+sinon.FakeXhr.prototype.readyState;
+
/** @type {string} */
sinon.FakeXhr.prototype.method;
@@ -156,6 +167,7 @@ sinon.FakeXhr.prototype.requestHeaders;
sinon.FakeXhr.prototype.respond;
/**
- * @type {?function(!sinon.FakeXhr)}
+ * @param {string} event
+ * @param {Function} handler
*/
-sinon.FakeXhr.prototype.onCreate; \ No newline at end of file
+sinon.FakeXhr.prototype.addEventListener;