1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
7 * Simple utilities for making XHRs more pleasant.
12 /** @suppress {duplicate} */
13 var remoting = remoting || {};
15 /** Namespace for XHR functions */
17 remoting.xhr = remoting.xhr || {};
20 * Takes an associative array of parameters and urlencodes it.
22 * @param {Object.<string,string>} paramHash The parameter key/value pairs.
23 * @return {string} URLEncoded version of paramHash.
25 remoting.xhr.urlencodeParamHash = function(paramHash) {
27 for (var key in paramHash) {
28 paramArray.push(encodeURIComponent(key) +
29 '=' + encodeURIComponent(paramHash[key]));
31 if (paramArray.length > 0) {
32 return paramArray.join('&');
38 * Parameters for the 'start' function.
40 * method: The HTTP method to use.
42 * url: The URL to request.
44 * onDone: Function to call when the XHR finishes.
46 * urlParams: (optional) Parameters to be appended to the URL.
47 * Null-valued parameters are omitted.
49 * textContent: (optional) Text to be sent as the request body.
51 * formContent: (optional) Data to be URL-encoded and sent as the
52 * request body. Causes Content-type header to be set
55 * jsonContent: (optional) Data to be JSON-encoded and sent as the
56 * request body. Causes Content-type header to be set
59 * headers: (optional) Additional request headers to be sent.
60 * Null-valued headers are omitted.
62 * withCredentials: (optional) Value of the XHR's withCredentials field.
64 * oauthToken: (optional) An OAuth2 token used to construct an
65 * Authentication header.
70 * onDone:(function(XMLHttpRequest):void),
71 * urlParams:(string|Object<string,?string>|undefined),
72 * textContent:(string|undefined),
73 * formContent:(Object|undefined),
74 * jsonContent:(*|undefined),
75 * headers:(Object<string,?string>|undefined),
76 * withCredentials:(boolean|undefined),
77 * oauthToken:(string|undefined)
83 * Returns a copy of the input object with all null or undefined
86 * @param {Object<string,?string>|undefined} input
87 * @return {!Object<string,string>}
90 remoting.xhr.removeNullFields_ = function(input) {
91 /** @type {!Object<string,string>} */
94 for (var field in input) {
95 var value = input[field];
97 result[field] = value;
105 * Executes an arbitrary HTTP method asynchronously.
107 * @param {remoting.XhrParams} params
108 * @return {XMLHttpRequest} The XMLHttpRequest object.
110 remoting.xhr.start = function(params) {
111 // Extract fields that can be used more or less as-is.
112 var method = params.method;
113 var url = params.url;
114 var onDone = params.onDone;
115 var headers = remoting.xhr.removeNullFields_(params.headers);
116 var withCredentials = params.withCredentials || false;
118 // Apply URL parameters.
119 var parameterString = '';
120 if (typeof(params.urlParams) === 'string') {
121 parameterString = params.urlParams;
122 } else if (typeof(params.urlParams) === 'object') {
123 parameterString = remoting.xhr.urlencodeParamHash(
124 remoting.xhr.removeNullFields_(params.urlParams));
126 if (parameterString) {
127 base.debug.assert(url.indexOf('?') == -1);
128 url += '?' + parameterString;
131 // Check that the content spec is consistent.
132 if ((Number(params.textContent !== undefined) +
133 Number(params.formContent !== undefined) +
134 Number(params.jsonContent !== undefined)) > 1) {
136 'may only specify one of textContent, formContent, and jsonContent');
139 // Convert the content fields to a single text content variable.
140 /** @type {?string} */
142 if (params.textContent !== undefined) {
143 content = params.textContent;
144 } else if (params.formContent !== undefined) {
145 if (!('Content-type' in headers)) {
146 headers['Content-type'] = 'application/x-www-form-urlencoded';
148 content = remoting.xhr.urlencodeParamHash(params.formContent);
149 } else if (params.jsonContent !== undefined) {
150 if (!('Content-type' in headers)) {
151 headers['Content-type'] = 'application/json; charset=UTF-8';
153 content = JSON.stringify(params.jsonContent);
156 // Apply the oauthToken field.
157 if (params.oauthToken !== undefined) {
158 base.debug.assert(!('Authorization' in headers));
159 headers['Authorization'] = 'Bearer ' + params.oauthToken;
162 return remoting.xhr.startInternal_(
163 method, url, onDone, content, headers, withCredentials);
167 * Executes an arbitrary HTTP method asynchronously.
169 * @param {string} method
170 * @param {string} url
171 * @param {function(XMLHttpRequest):void} onDone
172 * @param {?string} content
173 * @param {!Object<string,string>} headers
174 * @param {boolean} withCredentials
175 * @return {XMLHttpRequest} The XMLHttpRequest object.
178 remoting.xhr.startInternal_ = function(
179 method, url, onDone, content, headers, withCredentials) {
180 /** @type {XMLHttpRequest} */
181 var xhr = new XMLHttpRequest();
182 xhr.onreadystatechange = function() {
183 if (xhr.readyState != 4) {
189 xhr.open(method, url, true);
190 for (var key in headers) {
191 xhr.setRequestHeader(key, headers[key]);
193 xhr.withCredentials = withCredentials;
199 * Generic success/failure response proxy.
201 * @param {function():void} onDone
202 * @param {function(remoting.Error):void} onError
203 * @return {function(XMLHttpRequest):void}
205 remoting.xhr.defaultResponse = function(onDone, onError) {
206 /** @param {XMLHttpRequest} xhr */
207 var result = function(xhr) {
208 /** @type {remoting.Error} */
210 remoting.Error.fromHttpStatus(/** @type {number} */ (xhr.status));
211 if (error == remoting.Error.NONE) {