summaryrefslogtreecommitdiffstats
path: root/remoting
diff options
context:
space:
mode:
authorjrw <jrw@chromium.org>2015-03-20 12:01:15 -0700
committerCommit bot <commit-bot@chromium.org>2015-03-20 19:02:06 +0000
commit5b7c9e08d4e50f577eb7e058c21ef2f897cfbef1 (patch)
tree24866dbed947d33708ad4ae6769d5213d9b238b5 /remoting
parentc29919ce9a98620c21e6e7750a3bb7ed2afebbbe (diff)
downloadchromium_src-5b7c9e08d4e50f577eb7e058c21ef2f897cfbef1.zip
chromium_src-5b7c9e08d4e50f577eb7e058c21ef2f897cfbef1.tar.gz
chromium_src-5b7c9e08d4e50f577eb7e058c21ef2f897cfbef1.tar.bz2
Updated remoting.xhr API to use promises.
Removed access to the native XHR object used by the API. This is a larger change than one might expect for two reasons: First, because the native XHR object only allows the response content to be retrieved while the onreadystatechange handler is executing, and second, because the unit test for dns_blackhole_checker.js relied on synchronous semantics which cannot be duplicated with promises because when a promise is resolved, its "then" handlers are not called until the next event cycle. Review URL: https://codereview.chromium.org/1003433002 Cr-Commit-Position: refs/heads/master@{#321608}
Diffstat (limited to 'remoting')
-rw-r--r--remoting/webapp/app_remoting/js/app_remoting.js21
-rw-r--r--remoting/webapp/app_remoting/js/feedback_consent.js10
-rw-r--r--remoting/webapp/crd/js/dns_blackhole_checker.js23
-rw-r--r--remoting/webapp/crd/js/dns_blackhole_checker_unittest.js157
-rw-r--r--remoting/webapp/crd/js/host_controller.js17
-rw-r--r--remoting/webapp/crd/js/host_list_api_impl.js36
-rw-r--r--remoting/webapp/crd/js/it2me_connect_flow.js33
-rw-r--r--remoting/webapp/crd/js/oauth2.js2
-rw-r--r--remoting/webapp/crd/js/oauth2_api_impl.js76
-rw-r--r--remoting/webapp/crd/js/session_connector_impl.js5
-rw-r--r--remoting/webapp/crd/js/third_party_token_fetcher.js2
-rw-r--r--remoting/webapp/crd/js/xhr.js311
-rw-r--r--remoting/webapp/crd/js/xhr_unittest.js238
-rw-r--r--remoting/webapp/js_proto/qunit_proto.js2
-rw-r--r--remoting/webapp/js_proto/sinon_proto.js33
15 files changed, 541 insertions, 425 deletions
diff --git a/remoting/webapp/app_remoting/js/app_remoting.js b/remoting/webapp/app_remoting/js/app_remoting.js
index 82983eb..b3fd07c 100644
--- a/remoting/webapp/app_remoting/js/app_remoting.js
+++ b/remoting/webapp/app_remoting/js/app_remoting.js
@@ -105,11 +105,11 @@ remoting.AppRemoting.prototype.start = function(connector, token) {
/** @type {remoting.AppRemoting} */
var that = this;
- /** @param {XMLHttpRequest} xhr */
- var parseAppHostResponse = function(xhr) {
- if (xhr.status == 200) {
+ /** @param {!remoting.Xhr.Response} xhrResponse */
+ var parseAppHostResponse = function(xhrResponse) {
+ if (xhrResponse.status == 200) {
var response = /** @type {remoting.AppRemoting.AppHostResponse} */
- (base.jsonParseSafe(xhr.responseText));
+ (base.jsonParseSafe(xhrResponse.getText()));
if (response &&
response.status &&
response.status == 'done' &&
@@ -157,16 +157,16 @@ remoting.AppRemoting.prototype.start = function(connector, token) {
// TODO(garykac) Start using remoting.Error.fromHttpStatus once it has
// been updated to properly report 'unknown' errors (rather than
// reporting them as AUTHENTICATION_FAILED).
- if (xhr.status == 0) {
+ if (xhrResponse.status == 0) {
that.handleError(new remoting.Error(
remoting.Error.Tag.NETWORK_FAILURE));
- } else if (xhr.status == 401) {
+ } else if (xhrResponse.status == 401) {
that.handleError(new remoting.Error(
remoting.Error.Tag.AUTHENTICATION_FAILED));
- } else if (xhr.status == 403) {
+ } else if (xhrResponse.status == 403) {
that.handleError(new remoting.Error(
remoting.Error.Tag.APP_NOT_AUTHORIZED));
- } else if (xhr.status == 502 || xhr.status == 503) {
+ } else if (xhrResponse.status == 502 || xhrResponse.status == 503) {
that.handleError(new remoting.Error(
remoting.Error.Tag.SERVICE_UNAVAILABLE));
} else {
@@ -175,12 +175,11 @@ remoting.AppRemoting.prototype.start = function(connector, token) {
}
};
- remoting.xhr.start({
+ new remoting.Xhr({
method: 'POST',
url: that.runApplicationUrl(),
- onDone: parseAppHostResponse,
oauthToken: token
- });
+ }).start().then(parseAppHostResponse);
};
/**
diff --git a/remoting/webapp/app_remoting/js/feedback_consent.js b/remoting/webapp/app_remoting/js/feedback_consent.js
index f792376..1437d05 100644
--- a/remoting/webapp/app_remoting/js/feedback_consent.js
+++ b/remoting/webapp/app_remoting/js/feedback_consent.js
@@ -127,21 +127,19 @@ function onToken(token) {
'/applications/' + remoting.settings.getAppRemotingApplicationId() +
'/hosts/' + hostId +
'/reportIssue';
- /** @param {XMLHttpRequest} xhr */
- var onDone = function(xhr) {
- if (xhr.status >= 200 && xhr.status < 300) {
+ var onDone = function(/** !remoting.Xhr.Response */ response) {
+ if (response.status >= 200 && response.status < 300) {
getUserInfo();
} else {
showError();
}
};
- remoting.xhr.start({
+ new remoting.Xhr({
method: 'POST',
url: uri,
- onDone: onDone,
jsonContent: body,
oauthToken: token
- });
+ }).start().then(onDone);
} else {
getUserInfo();
}
diff --git a/remoting/webapp/crd/js/dns_blackhole_checker.js b/remoting/webapp/crd/js/dns_blackhole_checker.js
index 4be4ab8..95b342c 100644
--- a/remoting/webapp/crd/js/dns_blackhole_checker.js
+++ b/remoting/webapp/crd/js/dns_blackhole_checker.js
@@ -38,7 +38,7 @@ remoting.DnsBlackholeChecker = function(signalStrategy) {
/** @private */
this.blackholeState_ = BlackholeState.PENDING;
- /** @private {?XMLHttpRequest} */
+ /** @private {?remoting.Xhr} */
this.xhr_ = null;
};
@@ -85,11 +85,11 @@ remoting.DnsBlackholeChecker.prototype.connect = function(server,
this.signalStrategy_.connect(server, username, authToken);
- this.xhr_ = remoting.xhr.start({
+ this.xhr_ = new remoting.Xhr({
method: 'GET',
- url: remoting.DnsBlackholeChecker.URL_TO_REQUEST_,
- onDone: this.onHttpRequestDone_.bind(this)
+ url: remoting.DnsBlackholeChecker.URL_TO_REQUEST_
});
+ this.xhr_.start().then(this.onHttpRequestDone_.bind(this));
};
remoting.DnsBlackholeChecker.prototype.getState = function() {
@@ -153,12 +153,12 @@ remoting.DnsBlackholeChecker.prototype.onWrappedSignalStrategyStateChanged_ =
};
/**
- * @param {XMLHttpRequest} xhr
+ * @param {!remoting.Xhr.Response} response
* @private
*/
-remoting.DnsBlackholeChecker.prototype.onHttpRequestDone_ = function(xhr) {
+remoting.DnsBlackholeChecker.prototype.onHttpRequestDone_ = function(response) {
this.xhr_ = null;
- if (xhr.status >= 200 && xhr.status <= 299) {
+ if (response.status >= 200 && response.status <= 299) {
console.log("DNS blackhole check succeeded.");
this.blackholeState_ = BlackholeState.OPEN;
if (this.signalStrategy_.getState() ==
@@ -166,14 +166,15 @@ remoting.DnsBlackholeChecker.prototype.onHttpRequestDone_ = function(xhr) {
this.setState_(remoting.SignalStrategy.State.CONNECTED);
}
} else {
- console.error("DNS blackhole check failed: " + xhr.status + " " +
- xhr.statusText + ". Response URL: " + xhr.responseURL +
- ". Response Text: " + xhr.responseText);
+ console.error("DNS blackhole check failed: " + response.status + " " +
+ response.statusText + ". Response URL: " +
+ response.url + ". Response Text: " +
+ response.getText());
this.blackholeState_ = BlackholeState.BLOCKED;
base.dispose(this.signalStrategy_);
this.setState_(remoting.SignalStrategy.State.FAILED);
}
-}
+};
/**
* @param {remoting.SignalStrategy.State} newState
diff --git a/remoting/webapp/crd/js/dns_blackhole_checker_unittest.js b/remoting/webapp/crd/js/dns_blackhole_checker_unittest.js
index 9b0be49..e451fef 100644
--- a/remoting/webapp/crd/js/dns_blackhole_checker_unittest.js
+++ b/remoting/webapp/crd/js/dns_blackhole_checker_unittest.js
@@ -23,13 +23,15 @@ var checker = null;
/** @type {remoting.MockSignalStrategy} */
var signalStrategy = null;
-var fakeXhrs;
+
+/** @type {sinon.FakeXhr} */
+var fakeXhr = null;
QUnit.module('dns_blackhole_checker', {
beforeEach: function(assert) {
- fakeXhrs = [];
sinon.useFakeXMLHttpRequest().onCreate = function(xhr) {
- fakeXhrs.push(xhr);
+ QUnit.equal(fakeXhr, null, 'exactly one XHR is issued');
+ fakeXhr = xhr;
};
onStateChange = sinon.spy();
@@ -46,9 +48,8 @@ QUnit.module('dns_blackhole_checker', {
sinon.assert.calledWith(signalStrategy.connect, 'server', 'username',
'authToken');
- assert.equal(fakeXhrs.length, 1, 'exactly one XHR is issued');
assert.equal(
- fakeXhrs[0].url, remoting.DnsBlackholeChecker.URL_TO_REQUEST_,
+ fakeXhr.url, remoting.DnsBlackholeChecker.URL_TO_REQUEST_,
'the correct URL is requested');
},
afterEach: function() {
@@ -59,112 +60,136 @@ QUnit.module('dns_blackhole_checker', {
onStateChange = null;
onIncomingStanzaCallback = null;
checker = null;
- },
+ fakeXhr = null;
+ }
});
QUnit.test('success',
function(assert) {
- fakeXhrs[0].respond(200);
- sinon.assert.notCalled(onStateChange);
-
- [
- remoting.SignalStrategy.State.CONNECTING,
- remoting.SignalStrategy.State.HANDSHAKE,
- remoting.SignalStrategy.State.CONNECTED
- ].forEach(function(state) {
+ function checkState(state) {
signalStrategy.setStateForTesting(state);
sinon.assert.calledWith(onStateChange, state);
assert.equal(checker.getState(), state);
+ }
+
+ return base.SpyPromise.run(function() {
+ fakeXhr.respond(200);
+ }).then(function() {
+ sinon.assert.notCalled(onStateChange);
+ checkState(remoting.SignalStrategy.State.CONNECTING);
+ checkState(remoting.SignalStrategy.State.HANDSHAKE);
+ checkState(remoting.SignalStrategy.State.CONNECTED);
});
- }
-);
+ });
QUnit.test('http response after connected',
function(assert) {
- [
- remoting.SignalStrategy.State.CONNECTING,
- remoting.SignalStrategy.State.HANDSHAKE,
- ].forEach(function(state) {
+ function checkState(state) {
signalStrategy.setStateForTesting(state);
sinon.assert.calledWith(onStateChange, state);
assert.equal(checker.getState(), state);
- });
+ }
+
+ checkState(remoting.SignalStrategy.State.CONNECTING);
+ checkState(remoting.SignalStrategy.State.HANDSHAKE);
onStateChange.reset();
// Verify that DnsBlackholeChecker stays in HANDSHAKE state even if the
// signal strategy has connected.
- signalStrategy.setStateForTesting(remoting.SignalStrategy.State.CONNECTED);
- sinon.assert.notCalled(onStateChange);
+ return base.SpyPromise.run(function() {
+ signalStrategy.setStateForTesting(
+ remoting.SignalStrategy.State.CONNECTED);
+ }).then(function() {
+ sinon.assert.notCalled(onStateChange);
assert.equal(checker.getState(), remoting.SignalStrategy.State.HANDSHAKE);
- // Verify that DnsBlackholeChecker goes to CONNECTED state after the
- // the HTTP request has succeeded.
- fakeXhrs[0].respond(200);
- sinon.assert.calledWith(onStateChange,
- remoting.SignalStrategy.State.CONNECTED);
- }
-);
+ // Verify that DnsBlackholeChecker goes to CONNECTED state after the
+ // the HTTP request has succeeded.
+ return base.SpyPromise.run(function() {
+ fakeXhr.respond(200);
+ });
+ }).then(function() {
+ sinon.assert.calledWith(onStateChange,
+ remoting.SignalStrategy.State.CONNECTED);
+ });
+ });
QUnit.test('connect failed',
function(assert) {
- fakeXhrs[0].respond(200);
- sinon.assert.notCalled(onStateChange);
-
- [
- remoting.SignalStrategy.State.CONNECTING,
- remoting.SignalStrategy.State.FAILED
- ].forEach(function(state) {
+ function checkState(state) {
signalStrategy.setStateForTesting(state);
sinon.assert.calledWith(onStateChange, state);
+ };
+
+ return base.SpyPromise.run(function() {
+ fakeXhr.respond(200);
+ }).then(function() {
+ sinon.assert.notCalled(onStateChange);
+ checkState(remoting.SignalStrategy.State.CONNECTING);
+ checkState(remoting.SignalStrategy.State.FAILED);
});
-}
-);
+ });
QUnit.test('blocked',
function(assert) {
- fakeXhrs[0].respond(400);
- sinon.assert.calledWith(onStateChange,
- remoting.SignalStrategy.State.FAILED);
+ function checkState(state) {
assert.equal(checker.getError().getTag(),
remoting.Error.Tag.NOT_AUTHORIZED);
- onStateChange.reset();
-
- [
- remoting.SignalStrategy.State.CONNECTING,
- remoting.SignalStrategy.State.HANDSHAKE,
- remoting.SignalStrategy.State.CONNECTED
- ].forEach(function(state) {
+ onStateChange.reset();
signalStrategy.setStateForTesting(state);
sinon.assert.notCalled(onStateChange);
- assert.equal(checker.getState(), remoting.SignalStrategy.State.FAILED);
+ assert.equal(checker.getState(),
+ checker.getState(),
+ remoting.SignalStrategy.State.FAILED,
+ 'checker state is still FAILED');
+ };
+
+ return base.SpyPromise.run(function() {
+ fakeXhr.respond(400);
+ }).then(function() {
+ sinon.assert.calledWith(
+ onStateChange, remoting.SignalStrategy.State.FAILED);
+ assert.equal(
+ checker.getError().getTag(),
+ remoting.Error.Tag.NOT_AUTHORIZED,
+ 'checker error is NOT_AUTHORIZED');
+ checkState(remoting.SignalStrategy.State.CONNECTING);
+ checkState(remoting.SignalStrategy.State.HANDSHAKE);
+ checkState(remoting.SignalStrategy.State.FAILED);
});
- }
-);
+ });
QUnit.test('blocked after connected',
function(assert) {
- [
- remoting.SignalStrategy.State.CONNECTING,
- remoting.SignalStrategy.State.HANDSHAKE,
- ].forEach(function(state) {
+ function checkState(state) {
signalStrategy.setStateForTesting(state);
sinon.assert.calledWith(onStateChange, state);
assert.equal(checker.getState(), state);
- });
+ };
+
+ checkState(remoting.SignalStrategy.State.CONNECTING);
+ checkState(remoting.SignalStrategy.State.HANDSHAKE);
onStateChange.reset();
- // Verify that DnsBlackholeChecker stays in HANDSHAKE state even if the
- // signal strategy has connected.
- signalStrategy.setStateForTesting(remoting.SignalStrategy.State.CONNECTED);
- sinon.assert.notCalled(onStateChange);
+ // Verify that DnsBlackholeChecker stays in HANDSHAKE state even
+ // if the signal strategy has connected.
+ return base.SpyPromise.run(function() {
+ signalStrategy.setStateForTesting(
+ remoting.SignalStrategy.State.CONNECTED);
+ }).then(function() {
+ sinon.assert.notCalled(onStateChange);
assert.equal(checker.getState(), remoting.SignalStrategy.State.HANDSHAKE);
- // Verify that DnsBlackholeChecker goes to FAILED state after it gets the
- // blocked HTTP response.
- fakeXhrs[0].respond(400);
- sinon.assert.calledWith(onStateChange,
- remoting.SignalStrategy.State.FAILED);
+ // Verify that DnsBlackholeChecker goes to FAILED state after it
+ // gets the blocked HTTP response.
+ return base.SpyPromise.run(function() {
+ fakeXhr.respond(400);
+ });
+ }).then(function() {
+ sinon.assert.calledWith(onStateChange,
+ remoting.SignalStrategy.State.FAILED);
assert.ok(checker.getError().hasTag(remoting.Error.Tag.NOT_AUTHORIZED));
+ });
}
);
diff --git a/remoting/webapp/crd/js/host_controller.js b/remoting/webapp/crd/js/host_controller.js
index 61529f2..162cb2a 100644
--- a/remoting/webapp/crd/js/host_controller.js
+++ b/remoting/webapp/crd/js/host_controller.js
@@ -234,14 +234,14 @@ remoting.HostController.prototype.start = function(hostPin, consent, onDone,
* @param {string} hostName
* @param {string} publicKey
* @param {string} privateKey
- * @param {XMLHttpRequest} xhr
+ * @param {!remoting.Xhr.Response} response
*/
function onRegistered(
- hostName, publicKey, privateKey, xhr) {
- var success = (xhr.status == 200);
+ hostName, publicKey, privateKey, response) {
+ var success = (response.status == 200);
if (success) {
- var result = base.jsonParseSafe(xhr.responseText);
+ var result = base.jsonParseSafe(response.getText());
if ('data' in result && 'authorizationCode' in result['data']) {
that.hostDaemonFacade_.getCredentialsFromAuthCode(
result['data']['authorizationCode'],
@@ -260,8 +260,8 @@ remoting.HostController.prototype.start = function(hostPin, consent, onDone,
});
}
} else {
- console.log('Failed to register the host. Status: ' + xhr.status +
- ' response: ' + xhr.responseText);
+ console.log('Failed to register the host. Status: ' + response.status +
+ ' response: ' + response.getText());
onError(new remoting.Error(remoting.Error.Tag.REGISTRATION_FAILED));
}
}
@@ -281,16 +281,15 @@ remoting.HostController.prototype.start = function(hostPin, consent, onDone,
publicKey: publicKey
} };
- remoting.xhr.start({
+ new remoting.Xhr({
method: 'POST',
url: remoting.settings.DIRECTORY_API_BASE_URL + '/@me/hosts',
urlParams: {
hostClientId: hostClientId
},
- onDone: onRegistered.bind(null, hostName, publicKey, privateKey),
jsonContent: newHostDetails,
oauthToken: oauthToken
- });
+ }).start().then(onRegistered.bind(null, hostName, publicKey, privateKey));
}
/**
diff --git a/remoting/webapp/crd/js/host_list_api_impl.js b/remoting/webapp/crd/js/host_list_api_impl.js
index a4dbe08..52f9377 100644
--- a/remoting/webapp/crd/js/host_list_api_impl.js
+++ b/remoting/webapp/crd/js/host_list_api_impl.js
@@ -28,17 +28,16 @@ remoting.HostListApiImpl = function() {
* @param {function(!remoting.Error):void} onError
*/
remoting.HostListApiImpl.prototype.get = function(onDone, onError) {
- /** @type {function(XMLHttpRequest):void} */
+ /** @type {function(!remoting.Xhr.Response):void} */
var parseHostListResponse =
this.parseHostListResponse_.bind(this, onDone, onError);
/** @param {string} token */
var onToken = function(token) {
- remoting.xhr.start({
+ new remoting.Xhr({
method: 'GET',
url: remoting.settings.DIRECTORY_API_BASE_URL + '/@me/hosts',
- onDone: parseHostListResponse,
oauthToken: token
- });
+ }).start().then(parseHostListResponse);
};
remoting.identity.getToken().then(onToken, remoting.Error.handler(onError));
};
@@ -63,13 +62,12 @@ remoting.HostListApiImpl.prototype.put =
'publicKey': hostPublicKey
}
};
- remoting.xhr.start({
+ new remoting.Xhr({
method: 'PUT',
url: remoting.settings.DIRECTORY_API_BASE_URL + '/@me/hosts/' + hostId,
- onDone: remoting.xhr.defaultResponse(onDone, onError),
jsonContent: newHostDetails,
oauthToken: token
- });
+ }).start().then(remoting.Xhr.defaultResponse(onDone, onError));
};
remoting.identity.getToken().then(onToken, remoting.Error.handler(onError));
};
@@ -84,13 +82,12 @@ remoting.HostListApiImpl.prototype.put =
remoting.HostListApiImpl.prototype.remove = function(hostId, onDone, onError) {
/** @param {string} token */
var onToken = function(token) {
- remoting.xhr.start({
+ new remoting.Xhr({
method: 'DELETE',
url: remoting.settings.DIRECTORY_API_BASE_URL + '/@me/hosts/' + hostId,
- onDone: remoting.xhr.defaultResponse(onDone, onError,
- [remoting.Error.Tag.NOT_FOUND]),
oauthToken: token
- });
+ }).start().then(remoting.Xhr.defaultResponse(
+ onDone, onError, [remoting.Error.Tag.NOT_FOUND]));
};
remoting.identity.getToken().then(onToken, remoting.Error.handler(onError));
};
@@ -102,19 +99,19 @@ remoting.HostListApiImpl.prototype.remove = function(hostId, onDone, onError) {
*
* @param {function(Array<remoting.Host>):void} onDone
* @param {function(!remoting.Error):void} onError
- * @param {XMLHttpRequest} xhr
+ * @param {!remoting.Xhr.Response} response
* @private
*/
remoting.HostListApiImpl.prototype.parseHostListResponse_ =
- function(onDone, onError, xhr) {
- if (xhr.status == 200) {
- var response = /** @type {{data: {items: Array}}} */
- (base.jsonParseSafe(xhr.responseText));
- if (!response || !response.data) {
+ function(onDone, onError, response) {
+ if (response.status == 200) {
+ var obj = /** @type {{data: {items: Array}}} */
+ (base.jsonParseSafe(response.getText()));
+ if (!obj || !obj.data) {
console.error('Invalid "hosts" response from server.');
onError(remoting.Error.unexpected());
} else {
- var items = response.data.items || [];
+ var items = obj.data.items || [];
var hosts = items.map(
function(/** Object */ item) {
var host = new remoting.Host();
@@ -134,7 +131,7 @@ remoting.HostListApiImpl.prototype.parseHostListResponse_ =
onDone(hosts);
}
} else {
- onError(remoting.Error.fromHttpStatus(xhr.status));
+ onError(remoting.Error.fromHttpStatus(response.status));
}
};
@@ -142,4 +139,3 @@ remoting.HostListApiImpl.prototype.parseHostListResponse_ =
remoting.hostListApi = new remoting.HostListApiImpl();
})();
-
diff --git a/remoting/webapp/crd/js/it2me_connect_flow.js b/remoting/webapp/crd/js/it2me_connect_flow.js
index 20f42c2..df85817 100644
--- a/remoting/webapp/crd/js/it2me_connect_flow.js
+++ b/remoting/webapp/crd/js/it2me_connect_flow.js
@@ -54,8 +54,8 @@ remoting.It2MeConnectFlow.prototype.connect_ = function(accessCode) {
return remoting.identity.getToken();
}).then(function(/** string */ token) {
return that.getHostInfo_(token);
- }).then(function(/** XMLHttpRequest */ xhr) {
- return that.onHostInfo_(xhr);
+ }).then(function(/** !remoting.Xhr.Response */ response) {
+ return that.onHostInfo_(response);
}).then(function(/** remoting.Host */ host) {
that.sessionConnector_.connect(
remoting.DesktopConnectedView.Mode.IT2ME,
@@ -88,33 +88,31 @@ remoting.It2MeConnectFlow.prototype.verifyAccessCode_ = function(accessCode) {
* Continues an IT2Me connection once an access token has been obtained.
*
* @param {string} token An OAuth2 access token.
- * @return {Promise<XMLHttpRequest>}
+ * @return {Promise<!remoting.Xhr.Response>}
* @private
*/
remoting.It2MeConnectFlow.prototype.getHostInfo_ = function(token) {
var that = this;
- return new Promise(function(resolve) {
- remoting.xhr.start({
- method: 'GET',
- url: remoting.settings.DIRECTORY_API_BASE_URL + '/support-hosts/' +
- encodeURIComponent(that.hostId_),
- onDone: resolve,
- oauthToken: token
- });
- });
+ return new remoting.Xhr({
+ method: 'GET',
+ url: remoting.settings.DIRECTORY_API_BASE_URL + '/support-hosts/' +
+ encodeURIComponent(that.hostId_),
+ oauthToken: token
+ }).start();
};
/**
* Continues an IT2Me connection once the host JID has been looked up.
*
- * @param {XMLHttpRequest} xhr The server response to the support-hosts query.
+ * @param {!remoting.Xhr.Response} xhrResponse The server response to the
+ * support-hosts query.
* @return {!Promise<!remoting.Host>} Rejects on error.
* @private
*/
-remoting.It2MeConnectFlow.prototype.onHostInfo_ = function(xhr) {
- if (xhr.status == 200) {
+remoting.It2MeConnectFlow.prototype.onHostInfo_ = function(xhrResponse) {
+ if (xhrResponse.status == 200) {
var response = /** @type {{data: {jabberId: string, publicKey: string}}} */
- (base.jsonParseSafe(xhr.responseText));
+ (base.jsonParseSafe(xhrResponse.getText()));
if (response && response.data &&
response.data.jabberId && response.data.publicKey) {
var host = new remoting.Host();
@@ -128,11 +126,12 @@ remoting.It2MeConnectFlow.prototype.onHostInfo_ = function(xhr) {
return Promise.reject(remoting.Error.unexpected());
}
} else {
- return Promise.reject(translateSupportHostsError(xhr.status));
+ return Promise.reject(translateSupportHostsError(xhrResponse.status));
}
};
/**
+ * TODO(jrw): Replace with remoting.Error.fromHttpStatus.
* @param {number} error An HTTP error code returned by the support-hosts
* endpoint.
* @return {remoting.Error} The equivalent remoting.Error code.
diff --git a/remoting/webapp/crd/js/oauth2.js b/remoting/webapp/crd/js/oauth2.js
index d91da5d..37570b8 100644
--- a/remoting/webapp/crd/js/oauth2.js
+++ b/remoting/webapp/crd/js/oauth2.js
@@ -250,7 +250,7 @@ remoting.OAuth2.prototype.onTokens_ =
remoting.OAuth2.prototype.getAuthorizationCode = function(onDone) {
var xsrf_token = base.generateXsrfToken();
var GET_CODE_URL = this.getOAuth2AuthEndpoint_() + '?' +
- remoting.xhr.urlencodeParamHash({
+ remoting.Xhr.urlencodeParamHash({
'client_id': this.getClientId_(),
'redirect_uri': this.getRedirectUri_(),
'scope': this.SCOPE_,
diff --git a/remoting/webapp/crd/js/oauth2_api_impl.js b/remoting/webapp/crd/js/oauth2_api_impl.js
index f8ed194..de5d9df 100644
--- a/remoting/webapp/crd/js/oauth2_api_impl.js
+++ b/remoting/webapp/crd/js/oauth2_api_impl.js
@@ -75,37 +75,36 @@ remoting.OAuth2ApiImpl.prototype.interpretXhrStatus_ =
*/
remoting.OAuth2ApiImpl.prototype.refreshAccessToken = function(
onDone, onError, clientId, clientSecret, refreshToken) {
- /** @param {XMLHttpRequest} xhr */
- var onResponse = function(xhr) {
- if (xhr.status == 200) {
+ /** @param {!remoting.Xhr.Response} response */
+ var onResponse = function(response) {
+ if (response.status == 200) {
try {
// Don't use base.jsonParseSafe here unless you also include base.js,
// otherwise this won't work from the OAuth trampoline.
// TODO(jamiewalch): Fix this once we're no longer using the trampoline.
- var tokens = JSON.parse(xhr.responseText);
+ var tokens = JSON.parse(response.getText());
onDone(tokens['access_token'], tokens['expires_in']);
} catch (/** @type {Error} */ err) {
console.error('Invalid "token" response from server:', err);
onError(remoting.Error.unexpected());
}
} else {
- console.error('Failed to refresh token. Status: ' + xhr.status +
- ' response: ' + xhr.responseText);
- onError(fromHttpStatus(xhr.status));
+ console.error('Failed to refresh token. Status: ' + response.status +
+ ' response: ' + response.getText());
+ onError(remoting.Error.fromHttpStatus(response.status));
}
};
- remoting.xhr.start({
+ new remoting.Xhr({
method: 'POST',
url: this.getOAuth2TokenEndpoint_(),
- onDone: onResponse,
formContent: {
'client_id': clientId,
'client_secret': clientSecret,
'refresh_token': refreshToken,
'grant_type': 'refresh_token'
}
- });
+ }).start().then(onResponse);
};
/**
@@ -124,14 +123,14 @@ remoting.OAuth2ApiImpl.prototype.refreshAccessToken = function(
*/
remoting.OAuth2ApiImpl.prototype.exchangeCodeForTokens = function(
onDone, onError, clientId, clientSecret, code, redirectUri) {
- /** @param {XMLHttpRequest} xhr */
- var onResponse = function(xhr) {
- if (xhr.status == 200) {
+ /** @param {!remoting.Xhr.Response} response */
+ var onResponse = function(response) {
+ if (response.status == 200) {
try {
// Don't use base.jsonParseSafe here unless you also include base.js,
// otherwise this won't work from the OAuth trampoline.
// TODO(jamiewalch): Fix this once we're no longer using the trampoline.
- var tokens = JSON.parse(xhr.responseText);
+ var tokens = JSON.parse(response.getText());
onDone(tokens['refresh_token'],
tokens['access_token'], tokens['expires_in']);
} catch (/** @type {Error} */ err) {
@@ -139,16 +138,15 @@ remoting.OAuth2ApiImpl.prototype.exchangeCodeForTokens = function(
onError(remoting.Error.unexpected());
}
} else {
- console.error('Failed to exchange code for token. Status: ' + xhr.status +
- ' response: ' + xhr.responseText);
- onError(fromHttpStatus(xhr.status));
+ console.error('Failed to exchange code for token. Status: ' +
+ response.status + ' response: ' + response.getText());
+ onError(remoting.Error.fromHttpStatus(response.status));
}
};
- remoting.xhr.start({
+ new remoting.Xhr({
method: 'POST',
url: this.getOAuth2TokenEndpoint_(),
- onDone: onResponse,
formContent: {
'client_id': clientId,
'client_secret': clientSecret,
@@ -156,7 +154,7 @@ remoting.OAuth2ApiImpl.prototype.exchangeCodeForTokens = function(
'code': code,
'grant_type': 'authorization_code'
}
- });
+ }).start().then(onResponse);
};
/**
@@ -170,28 +168,27 @@ remoting.OAuth2ApiImpl.prototype.exchangeCodeForTokens = function(
* @return {void} Nothing.
*/
remoting.OAuth2ApiImpl.prototype.getEmail = function(onDone, onError, token) {
- /** @param {XMLHttpRequest} xhr */
- var onResponse = function(xhr) {
- if (xhr.status == 200) {
+ /** @param {!remoting.Xhr.Response} response */
+ var onResponse = function(response) {
+ if (response.status == 200) {
try {
- var result = JSON.parse(xhr.responseText);
+ var result = JSON.parse(response.getText());
onDone(result['email']);
} catch (/** @type {Error} */ err) {
console.error('Invalid "userinfo" response from server:', err);
onError(remoting.Error.unexpected());
}
} else {
- console.error('Failed to get email. Status: ' + xhr.status +
- ' response: ' + xhr.responseText);
- onError(fromHttpStatus(xhr.status));
+ console.error('Failed to get email. Status: ' + response.status +
+ ' response: ' + response.getText());
+ onError(remoting.Error.fromHttpStatus(response.status));
}
};
- remoting.xhr.start({
+ new remoting.Xhr({
method: 'GET',
url: this.getOAuth2ApiUserInfoEndpoint_(),
- onDone: onResponse,
oauthToken: token
- });
+ }).start().then(onResponse);
};
/**
@@ -206,28 +203,27 @@ remoting.OAuth2ApiImpl.prototype.getEmail = function(onDone, onError, token) {
*/
remoting.OAuth2ApiImpl.prototype.getUserInfo =
function(onDone, onError, token) {
- /** @param {XMLHttpRequest} xhr */
- var onResponse = function(xhr) {
- if (xhr.status == 200) {
+ /** @param {!remoting.Xhr.Response} response */
+ var onResponse = function(response) {
+ if (response.status == 200) {
try {
- var result = JSON.parse(xhr.responseText);
+ var result = JSON.parse(response.getText());
onDone(result['email'], result['name']);
} catch (/** @type {Error} */ err) {
console.error('Invalid "userinfo" response from server:', err);
onError(remoting.Error.unexpected());
}
} else {
- console.error('Failed to get user info. Status: ' + xhr.status +
- ' response: ' + xhr.responseText);
- onError(fromHttpStatus(xhr.status));
+ console.error('Failed to get user info. Status: ' + response.status +
+ ' response: ' + response.getText());
+ onError(remoting.Error.fromHttpStatus(response.status));
}
};
- remoting.xhr.start({
+ new remoting.Xhr({
method: 'GET',
url: this.getOAuth2ApiUserInfoEndpoint_(),
- onDone: onResponse,
oauthToken: token
- });
+ }).start().then(onResponse);
};
/** @returns {!remoting.Error} */
diff --git a/remoting/webapp/crd/js/session_connector_impl.js b/remoting/webapp/crd/js/session_connector_impl.js
index 427d6a3..0d9ea5b 100644
--- a/remoting/webapp/crd/js/session_connector_impl.js
+++ b/remoting/webapp/crd/js/session_connector_impl.js
@@ -75,6 +75,9 @@ remoting.SessionConnectorImpl = function(clientContainer, onConnected, onError,
remoting.SessionConnectorImpl.prototype.resetConnection_ = function() {
this.closeSession();
+ // It's OK to initialize these member variables here because the
+ // constructor calls this method.
+
/** @private {remoting.Host} */
this.host_ = null;
@@ -87,8 +90,6 @@ remoting.SessionConnectorImpl.prototype.resetConnection_ = function() {
/** @private {remoting.ClientSession} */
this.clientSession_ = null;
- /** @private {XMLHttpRequest} */
- this.pendingXhr_ = null;
/** @private {remoting.CredentialsProvider} */
this.credentialsProvider_ = null;
diff --git a/remoting/webapp/crd/js/third_party_token_fetcher.js b/remoting/webapp/crd/js/third_party_token_fetcher.js
index b51e1b1..66189e9 100644
--- a/remoting/webapp/crd/js/third_party_token_fetcher.js
+++ b/remoting/webapp/crd/js/third_party_token_fetcher.js
@@ -132,7 +132,7 @@ remoting.ThirdPartyTokenFetcher.prototype.parseRedirectUrl_ =
* @private
*/
remoting.ThirdPartyTokenFetcher.prototype.getFullTokenUrl_ = function() {
- return this.tokenUrl_ + '?' + remoting.xhr.urlencodeParamHash({
+ return this.tokenUrl_ + '?' + remoting.Xhr.urlencodeParamHash({
'redirect_uri': this.redirectUri_,
'scope': this.tokenScope_,
'client_id': this.hostPublicKey_,
diff --git a/remoting/webapp/crd/js/xhr.js b/remoting/webapp/crd/js/xhr.js
index 0529838..cdefb0aa 100644
--- a/remoting/webapp/crd/js/xhr.js
+++ b/remoting/webapp/crd/js/xhr.js
@@ -4,7 +4,7 @@
/**
* @fileoverview
- * Simple utilities for making XHRs more pleasant.
+ * Utility class for making XHRs more pleasant.
*/
'use strict';
@@ -12,26 +12,83 @@
/** @suppress {duplicate} */
var remoting = remoting || {};
-/** Namespace for XHR functions */
-/** @type {Object} */
-remoting.xhr = remoting.xhr || {};
-
/**
- * Takes an associative array of parameters and urlencodes it.
- *
- * @param {Object<string,string>} paramHash The parameter key/value pairs.
- * @return {string} URLEncoded version of paramHash.
+ * @constructor
+ * @param {remoting.Xhr.Params} params
*/
-remoting.xhr.urlencodeParamHash = function(paramHash) {
- var paramArray = [];
- for (var key in paramHash) {
- paramArray.push(encodeURIComponent(key) +
- '=' + encodeURIComponent(paramHash[key]));
+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;
+
+ // Apply URL parameters.
+ var url = params.url;
+ var parameterString = '';
+ if (typeof(params.urlParams) === 'string') {
+ parameterString = params.urlParams;
+ } else if (typeof(params.urlParams) === 'object') {
+ parameterString = remoting.Xhr.urlencodeParamHash(
+ remoting.Xhr.removeNullFields_(params.urlParams));
}
- if (paramArray.length > 0) {
- return paramArray.join('&');
+ if (parameterString) {
+ base.debug.assert(url.indexOf('?') == -1);
+ url += '?' + parameterString;
}
- return '';
+
+ // 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);
+
+ // Convert the content fields to a single text content variable.
+ /** @private {?string} */
+ this.content_ = null;
+ if (params.textContent !== undefined) {
+ this.content_ = params.textContent;
+ } else if (params.formContent !== undefined) {
+ if (!('Content-type' in headers)) {
+ headers['Content-type'] = '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.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.nativeXhr_.open(params.method, url, true);
+ for (var key in headers) {
+ this.nativeXhr_.setRequestHeader(key, headers[key]);
+ }
+
+ /** @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.
};
/**
@@ -41,8 +98,6 @@ remoting.xhr.urlencodeParamHash = function(paramHash) {
*
* url: The URL to request.
*
- * onDone: Function to call when the XHR finishes.
-
* urlParams: (optional) Parameters to be appended to the URL.
* Null-valued parameters are omitted.
*
@@ -64,20 +119,118 @@ remoting.xhr.urlencodeParamHash = function(paramHash) {
* oauthToken: (optional) An OAuth2 token used to construct an
* Authentication header.
*
+ * responseType: (optional) Request a response of a specific
+ * type. Default: TEXT.
+ *
* @typedef {{
* method: string,
* url:string,
- * onDone:(function(XMLHttpRequest):void),
* urlParams:(string|Object<string,?string>|undefined),
* textContent:(string|undefined),
* formContent:(Object|undefined),
* jsonContent:(*|undefined),
* headers:(Object<string,?string>|undefined),
* withCredentials:(boolean|undefined),
- * oauthToken:(string|undefined)
+ * oauthToken:(string|undefined),
+ * responseType:(remoting.Xhr.ResponseType|undefined)
* }}
*/
-remoting.XhrParams;
+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.
+ *
+ * NOTE: Calling this method more than once will return the same
+ * promise and not start a new request, despite what the name
+ * suggests.
+ *
+ * @return {!Promise<!remoting.Xhr.Response>}
+ */
+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();
+ }
+ return this.deferred_.promise();
+};
+
+/**
+ * @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_));
+ }
+};
+
+/**
+ * The response-related parts of an XMLHttpRequest. Note that this
+ * class is not just a facade for XMLHttpRequest; it saves the value
+ * of the |responseText| field becuase once onReadyStateChange_
+ * (above) returns, the value of |responseText| is reset to the empty
+ * string! This is a documented anti-feature of the XMLHttpRequest
+ * API.
+ *
+ * @constructor
+ * @param {!XMLHttpRequest} xhr
+ * @param {remoting.Xhr.ResponseType} type
+ */
+remoting.Xhr.Response = function(xhr, type) {
+ /** @private @const */
+ this.type_ = type;
+
+ /**
+ * The HTTP status code.
+ * @const {number}
+ */
+ this.status = xhr.status;
+
+ /**
+ * The HTTP status description.
+ * @const {string}
+ */
+ this.statusText = xhr.statusText;
+
+ /**
+ * The response URL, if any.
+ * @const {?string}
+ */
+ this.url = xhr.responseURL;
+
+ /** @private {string} */
+ this.text_ = xhr.responseText || '';
+};
+
+/**
+ * @return {string} The text content of the response.
+ */
+remoting.Xhr.Response.prototype.getText = function() {
+ return this.text_;
+};
+
+/**
+ * @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_);
+};
/**
* Returns a copy of the input object with all null or undefined
@@ -87,7 +240,7 @@ remoting.XhrParams;
* @return {!Object<string,string>}
* @private
*/
-remoting.xhr.removeNullFields_ = function(input) {
+remoting.Xhr.removeNullFields_ = function(input) {
/** @type {!Object<string,string>} */
var result = {};
if (input) {
@@ -102,112 +255,38 @@ remoting.xhr.removeNullFields_ = function(input) {
};
/**
- * Executes an arbitrary HTTP method asynchronously.
+ * Takes an associative array of parameters and urlencodes it.
*
- * @param {remoting.XhrParams} params
- * @return {XMLHttpRequest} The XMLHttpRequest object.
+ * @param {Object<string,string>} paramHash The parameter key/value pairs.
+ * @return {string} URLEncoded version of paramHash.
*/
-remoting.xhr.start = function(params) {
- // Extract fields that can be used more or less as-is.
- var method = params.method;
- var url = params.url;
- var onDone = params.onDone;
- var headers = remoting.xhr.removeNullFields_(params.headers);
- var withCredentials = params.withCredentials || false;
-
- // Apply URL parameters.
- var parameterString = '';
- if (typeof(params.urlParams) === 'string') {
- parameterString = params.urlParams;
- } else if (typeof(params.urlParams) === 'object') {
- parameterString = remoting.xhr.urlencodeParamHash(
- 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');
- }
-
- // Convert the content fields to a single text content variable.
- /** @type {?string} */
- var content = null;
- if (params.textContent !== undefined) {
- content = params.textContent;
- } else if (params.formContent !== undefined) {
- if (!('Content-type' in headers)) {
- headers['Content-type'] = 'application/x-www-form-urlencoded';
- }
- content = remoting.xhr.urlencodeParamHash(params.formContent);
- } else if (params.jsonContent !== undefined) {
- if (!('Content-type' in headers)) {
- headers['Content-type'] = 'application/json; charset=UTF-8';
- }
- content = JSON.stringify(params.jsonContent);
- }
-
- // Apply the oauthToken field.
- if (params.oauthToken !== undefined) {
- base.debug.assert(!('Authorization' in headers));
- headers['Authorization'] = 'Bearer ' + params.oauthToken;
+remoting.Xhr.urlencodeParamHash = function(paramHash) {
+ var paramArray = [];
+ for (var key in paramHash) {
+ paramArray.push(encodeURIComponent(key) +
+ '=' + encodeURIComponent(paramHash[key]));
}
-
- return remoting.xhr.startInternal_(
- method, url, onDone, content, headers, withCredentials);
-};
-
-/**
- * Executes an arbitrary HTTP method asynchronously.
- *
- * @param {string} method
- * @param {string} url
- * @param {function(XMLHttpRequest):void} onDone
- * @param {?string} content
- * @param {!Object<string,string>} headers
- * @param {boolean} withCredentials
- * @return {XMLHttpRequest} The XMLHttpRequest object.
- * @private
- */
-remoting.xhr.startInternal_ = function(
- method, url, onDone, content, headers, withCredentials) {
- /** @type {XMLHttpRequest} */
- var xhr = new XMLHttpRequest();
- xhr.onreadystatechange = function() {
- if (xhr.readyState != 4) {
- return;
- }
- onDone(xhr);
- };
-
- xhr.open(method, url, true);
- for (var key in headers) {
- xhr.setRequestHeader(key, headers[key]);
+ if (paramArray.length > 0) {
+ return paramArray.join('&');
}
- xhr.withCredentials = withCredentials;
- xhr.send(content);
- return xhr;
+ 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(XMLHttpRequest):void}
+ * @return {function(!remoting.Xhr.Response):void}
*/
-remoting.xhr.defaultResponse = function(onDone, onError, opt_ignoreErrors) {
- /** @param {XMLHttpRequest} xhr */
- var result = function(xhr) {
- var error =
- remoting.Error.fromHttpStatus(/** @type {number} */ (xhr.status));
+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;
diff --git a/remoting/webapp/crd/js/xhr_unittest.js b/remoting/webapp/crd/js/xhr_unittest.js
index 7102c2e..b02987e 100644
--- a/remoting/webapp/crd/js/xhr_unittest.js
+++ b/remoting/webapp/crd/js/xhr_unittest.js
@@ -4,193 +4,185 @@
/**
* @fileoverview
- * @suppress {checkTypes|checkVars|reportUnknownTypes}
*/
(function() {
'use strict';
-QUnit.module('xhr');
+
+/** @type {sinon.FakeXhr} */
+var fakeXhr;
+
+QUnit.module('xhr', {
+ beforeEach: function() {
+ fakeXhr = null;
+ sinon.useFakeXMLHttpRequest().onCreate =
+ function(/** sinon.FakeXhr */ xhr) {
+ fakeXhr = xhr;
+ };
+ }
+});
QUnit.test('urlencodeParamHash', function(assert) {
assert.equal(
- remoting.xhr.urlencodeParamHash({}),
+ remoting.Xhr.urlencodeParamHash({}),
'');
assert.equal(
- remoting.xhr.urlencodeParamHash({'key': 'value'}),
+ remoting.Xhr.urlencodeParamHash({'key': 'value'}),
'key=value');
assert.equal(
- remoting.xhr.urlencodeParamHash({'key /?=&': 'value /?=&'}),
+ remoting.Xhr.urlencodeParamHash({'key /?=&': 'value /?=&'}),
'key%20%2F%3F%3D%26=value%20%2F%3F%3D%26');
assert.equal(
- remoting.xhr.urlencodeParamHash({'k1': 'v1', 'k2': 'v2'}),
+ remoting.Xhr.urlencodeParamHash({'k1': 'v1', 'k2': 'v2'}),
'k1=v1&k2=v2');
});
QUnit.test('basic GET', function(assert) {
- sinon.useFakeXMLHttpRequest();
- var done = assert.async();
- var request = remoting.xhr.start({
+ var promise = new remoting.Xhr({
method: 'GET',
url: 'http://foo.com',
- onDone: function(xhr) {
- assert.ok(xhr === request);
- assert.equal(xhr.status, 200);
- assert.equal(xhr.responseText, 'body');
- done();
- }
+ responseType: remoting.Xhr.ResponseType.TEXT
+ }).start().then(function(response) {
+ assert.equal(response.status, 200);
+ assert.equal(response.getText(), 'body');
});
- assert.equal(request.method, 'GET');
- assert.equal(request.url, 'http://foo.com');
- assert.equal(request.withCredentials, false);
- assert.equal(request.requestBody, null);
- assert.ok(!('Content-type' in request.requestHeaders));
- request.respond(200, {}, 'body');
+ assert.equal(fakeXhr.method, 'GET');
+ assert.equal(fakeXhr.url, 'http://foo.com');
+ assert.equal(fakeXhr.withCredentials, false);
+ assert.equal(fakeXhr.requestBody, null);
+ assert.ok(!('Content-type' in fakeXhr.requestHeaders));
+ fakeXhr.respond(200, {}, 'body');
+ return promise;
});
QUnit.test('GET with param string', function(assert) {
- var done = assert.async();
- sinon.useFakeXMLHttpRequest();
- var request = remoting.xhr.start({
+ var promise = new remoting.Xhr({
method: 'GET',
url: 'http://foo.com',
- onDone: function(xhr) {
- assert.ok(xhr === request);
- assert.equal(xhr.status, 200);
- assert.equal(xhr.responseText, 'body');
- done();
- },
+ responseType: remoting.Xhr.ResponseType.TEXT,
urlParams: 'the_param_string'
+ }).start().then(function(response) {
+ assert.equal(response.status, 200);
+ assert.equal(response.getText(), 'body');
});
- assert.equal(request.method, 'GET');
- assert.equal(request.url, 'http://foo.com?the_param_string');
- assert.equal(request.withCredentials, false);
- assert.equal(request.requestBody, null);
- assert.ok(!('Content-type' in request.requestHeaders));
- request.respond(200, {}, 'body');
+ 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');
+ return promise;
});
QUnit.test('GET with param object', function(assert) {
- var done = assert.async();
- sinon.useFakeXMLHttpRequest();
- var request = remoting.xhr.start({
+ var promise = new remoting.Xhr({
method: 'GET',
url: 'http://foo.com',
- onDone: function(xhr) {
- assert.ok(xhr === request);
- assert.equal(xhr.status, 200);
- assert.equal(xhr.responseText, 'body');
- done();
- },
+ responseType: remoting.Xhr.ResponseType.TEXT,
urlParams: {'a': 'b', 'c': 'd'}
+ }).start().then(function(response) {
+ assert.equal(response.status, 200);
+ assert.equal(response.getText(), 'body');
});
- assert.equal(request.method, 'GET');
- assert.equal(request.url, 'http://foo.com?a=b&c=d');
- assert.equal(request.withCredentials, false);
- assert.equal(request.requestBody, null);
- assert.ok(!('Content-type' in request.requestHeaders));
- request.respond(200, {}, 'body');
+ 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');
+ return promise;
});
QUnit.test('GET with headers', function(assert) {
- sinon.useFakeXMLHttpRequest();
- var done = assert.async();
- var request = remoting.xhr.start({
+ var promise = new remoting.Xhr({
method: 'GET',
url: 'http://foo.com',
- onDone: function(xhr) {
- assert.ok(xhr === request);
- assert.equal(xhr.status, 200);
- assert.equal(xhr.responseText, 'body');
- done();
- },
+ 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(request.method, 'GET');
- assert.equal(request.url, 'http://foo.com');
- assert.equal(request.withCredentials, false);
- assert.equal(request.requestBody, null);
+ assert.equal(fakeXhr.method, 'GET');
+ assert.equal(fakeXhr.url, 'http://foo.com');
+ assert.equal(fakeXhr.withCredentials, false);
+ assert.equal(fakeXhr.requestBody, null);
assert.equal(
- request.requestHeaders['Header1'],
+ fakeXhr.requestHeaders['Header1'],
'headerValue1');
assert.equal(
- request.requestHeaders['Header2'],
+ fakeXhr.requestHeaders['Header2'],
'headerValue2');
- assert.ok(!('Content-type' in request.requestHeaders));
- request.respond(200, {}, 'body');
+ assert.ok(!('Content-type' in fakeXhr.requestHeaders));
+ fakeXhr.respond(200, {}, 'body');
+ return promise;
});
QUnit.test('GET with credentials', function(assert) {
- sinon.useFakeXMLHttpRequest();
- var done = assert.async();
- var request = remoting.xhr.start({
+ var promise = new remoting.Xhr({
method: 'GET',
url: 'http://foo.com',
- onDone: function(xhr) {
- assert.ok(xhr === request);
- assert.equal(xhr.status, 200);
- assert.equal(xhr.responseText, 'body');
- done();
- },
+ responseType: remoting.Xhr.ResponseType.TEXT,
withCredentials: true
+ }).start().then(function(response) {
+ assert.equal(response.status, 200);
+ assert.equal(response.getText(), 'body');
});
- assert.equal(request.method, 'GET');
- assert.equal(request.url, 'http://foo.com');
- assert.equal(request.withCredentials, true);
- assert.equal(request.requestBody, null);
- assert.ok(!('Content-type' in request.requestHeaders));
- request.respond(200, {}, 'body');
+ assert.equal(fakeXhr.method, 'GET');
+ assert.equal(fakeXhr.url, 'http://foo.com');
+ assert.equal(fakeXhr.withCredentials, true);
+ assert.equal(fakeXhr.requestBody, null);
+ assert.ok(!('Content-type' in fakeXhr.requestHeaders));
+ fakeXhr.respond(200, {}, 'body');
+ return promise;
});
QUnit.test('POST with text content', function(assert) {
- sinon.useFakeXMLHttpRequest();
var done = assert.async();
- var request = remoting.xhr.start({
+
+ var promise = new remoting.Xhr({
method: 'POST',
url: 'http://foo.com',
- onDone: function(xhr) {
- assert.ok(xhr === request);
- assert.equal(xhr.status, 200);
- assert.equal(xhr.responseText, 'body');
- done();
- },
+ responseType: remoting.Xhr.ResponseType.TEXT,
textContent: 'the_content_string'
+ }).start().then(function(response) {
+ assert.equal(response.status, 200);
+ assert.equal(response.getText(), 'body');
+ done();
});
- assert.equal(request.method, 'POST');
- assert.equal(request.url, 'http://foo.com');
- assert.equal(request.withCredentials, false);
- assert.equal(request.requestBody, 'the_content_string');
- assert.ok(!('Content-type' in request.requestHeaders));
- request.respond(200, {}, 'body');
+ assert.equal(fakeXhr.method, 'POST');
+ 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));
+ fakeXhr.respond(200, {}, 'body');
+ return promise;
});
QUnit.test('POST with form content', function(assert) {
- sinon.useFakeXMLHttpRequest();
- var done = assert.async();
- var request = remoting.xhr.start({
+ var promise = new remoting.Xhr({
method: 'POST',
url: 'http://foo.com',
- onDone: function(xhr) {
- assert.ok(xhr === request);
- assert.equal(xhr.status, 200);
- assert.equal(xhr.responseText, 'body');
- done();
- },
+ 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(request.method, 'POST');
- assert.equal(request.url, 'http://foo.com');
- assert.equal(request.withCredentials, false);
- assert.equal(request.requestBody, 'a=b&c=d');
+ assert.equal(fakeXhr.method, 'POST');
+ assert.equal(fakeXhr.url, 'http://foo.com');
+ assert.equal(fakeXhr.withCredentials, false);
+ assert.equal(fakeXhr.requestBody, 'a=b&c=d');
assert.equal(
- request.requestHeaders['Content-type'],
+ fakeXhr.requestHeaders['Content-type'],
'application/x-www-form-urlencoded');
- request.respond(200, {}, 'body');
+ fakeXhr.respond(200, {}, 'body');
+ return promise;
});
QUnit.test('defaultResponse 200', function(assert) {
- sinon.useFakeXMLHttpRequest();
var done = assert.async();
var onDone = function() {
@@ -203,18 +195,17 @@ QUnit.test('defaultResponse 200', function(assert) {
done();
};
- var request = remoting.xhr.start({
+ new remoting.Xhr({
method: 'POST',
- url: 'http://foo.com',
- onDone: remoting.xhr.defaultResponse(onDone, onError)
- });
- request.respond(200);
+ url: 'http://foo.com'
+ }).start().then(remoting.Xhr.defaultResponse(onDone, onError));
+ fakeXhr.respond(200, {}, '');
});
QUnit.test('defaultResponse 404', function(assert) {
- sinon.useFakeXMLHttpRequest();
var done = assert.async();
+
var onDone = function() {
assert.ok(false);
done();
@@ -225,12 +216,11 @@ QUnit.test('defaultResponse 404', function(assert) {
done();
};
- var request = remoting.xhr.start({
+ new remoting.Xhr({
method: 'POST',
- url: 'http://foo.com',
- onDone: remoting.xhr.defaultResponse(onDone, onError)
- });
- request.respond(404);
+ url: 'http://foo.com'
+ }).start().then(remoting.Xhr.defaultResponse(onDone, onError));
+ fakeXhr.respond(404, {}, '');
});
})();
diff --git a/remoting/webapp/js_proto/qunit_proto.js b/remoting/webapp/js_proto/qunit_proto.js
index aed670e..33b30f7 100644
--- a/remoting/webapp/js_proto/qunit_proto.js
+++ b/remoting/webapp/js_proto/qunit_proto.js
@@ -100,4 +100,4 @@ QUnit.module = function(desc, opt_args) {};
* @param {string} desc
* @param {function(QUnit.Assert)} f
*/
-QUnit.test = function(desc, f) {}; \ No newline at end of file
+QUnit.test = function(desc, f) {};
diff --git a/remoting/webapp/js_proto/sinon_proto.js b/remoting/webapp/js_proto/sinon_proto.js
index ff7eb96..9e223dc 100644
--- a/remoting/webapp/js_proto/sinon_proto.js
+++ b/remoting/webapp/js_proto/sinon_proto.js
@@ -126,3 +126,36 @@ sinon.TestStub.prototype.onFirstCall = function() {};
/** @returns {Object} */
sinon.createStubInstance = function (/** * */ constructor) {};
+
+/** @return {sinon.FakeXhr} */
+sinon.useFakeXMLHttpRequest = function() {};
+
+/** @interface */
+sinon.FakeXhr = function() {};
+
+/** @type {string} */
+sinon.FakeXhr.prototype.method;
+
+/** @type {string} */
+sinon.FakeXhr.prototype.url;
+
+/** @type {boolean} */
+sinon.FakeXhr.prototype.withCredentials;
+
+/** @type {?string} */
+sinon.FakeXhr.prototype.requestBody;
+
+/** @type {!Object<string,string>} */
+sinon.FakeXhr.prototype.requestHeaders;
+
+/**
+ * @param {number} status
+ * @param {!Object<string,string>} headers
+ * @param {?string} content
+ */
+sinon.FakeXhr.prototype.respond;
+
+/**
+ * @type {?function(!sinon.FakeXhr)}
+ */
+sinon.FakeXhr.prototype.onCreate; \ No newline at end of file