Rewrite AndroidSyncSettings to be significantly simpler.
[chromium-blink-merge.git] / remoting / webapp / crd / js / xhr.js
blob90d8e555b117d3a3b34430546f79be15d0190efe
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.
5 /**
6  * @fileoverview
7  * Simple utilities for making XHRs more pleasant.
8  */
10 'use strict';
12 /** @suppress {duplicate} */
13 var remoting = remoting || {};
15 /** Namespace for XHR functions */
16 /** @type {Object} */
17 remoting.xhr = remoting.xhr || {};
19 /**
20  * Takes an associative array of parameters and urlencodes it.
21  *
22  * @param {Object.<string,string>} paramHash The parameter key/value pairs.
23  * @return {string} URLEncoded version of paramHash.
24  */
25 remoting.xhr.urlencodeParamHash = function(paramHash) {
26   var paramArray = [];
27   for (var key in paramHash) {
28     paramArray.push(encodeURIComponent(key) +
29                      '=' + encodeURIComponent(paramHash[key]));
30   }
31   if (paramArray.length > 0) {
32     return paramArray.join('&');
33   }
34   return '';
37 /**
38  * Parameters for the 'start' function.
39  *
40  * method: The HTTP method to use.
41  *
42  * url: The URL to request.
43  *
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.
48  *
49  * textContent: (optional) Text to be sent as the request body.
50  *
51  * formContent: (optional) Data to be URL-encoded and sent as the
52  *     request body.  Causes Content-type header to be set
53  *     appropriately.
54  *
55  * jsonContent: (optional) Data to be JSON-encoded and sent as the
56  *     request body.  Causes Content-type header to be set
57  *     appropriately.
58  *
59  * headers: (optional) Additional request headers to be sent.
60  *     Null-valued headers are omitted.
61  *
62  * withCredentials: (optional) Value of the XHR's withCredentials field.
63  *
64  * oauthToken: (optional) An OAuth2 token used to construct an
65  *     Authentication header.
66  *
67  * @typedef {{
68  *   method: string,
69  *   url:string,
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)
78  * }}
79  */
80 remoting.XhrParams;
82 /**
83  * Returns a copy of the input object with all null or undefined
84  * fields removed.
85  *
86  * @param {Object<string,?string>|undefined} input
87  * @return {!Object<string,string>}
88  * @private
89  */
90 remoting.xhr.removeNullFields_ = function(input) {
91   /** @type {!Object<string,string>} */
92   var result = {};
93   if (input) {
94     for (var field in input) {
95       var value = input[field];
96       if (value != null) {
97         result[field] = value;
98       }
99     }
100   }
101   return result;
105  * Executes an arbitrary HTTP method asynchronously.
107  * @param {remoting.XhrParams} params
108  * @return {XMLHttpRequest} The XMLHttpRequest object.
109  */
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));
125   }
126   if (parameterString) {
127     base.debug.assert(url.indexOf('?') == -1);
128     url += '?' + parameterString;
129   }
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) {
135     throw new Error(
136         'may only specify one of textContent, formContent, and jsonContent');
137   }
139   // Convert the content fields to a single text content variable.
140   /** @type {?string} */
141   var content = null;
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';
147     }
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';
152     }
153     content = JSON.stringify(params.jsonContent);
154   }
156   // Apply the oauthToken field.
157   if (params.oauthToken !== undefined) {
158     base.debug.assert(!('Authorization' in headers));
159     headers['Authorization'] = 'Bearer ' + params.oauthToken;
160   }
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.
176  * @private
177  */
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) {
184       return;
185     }
186     onDone(xhr);
187   };
189   xhr.open(method, url, true);
190   for (var key in headers) {
191     xhr.setRequestHeader(key, headers[key]);
192   }
193   xhr.withCredentials = withCredentials;
194   xhr.send(content);
195   return xhr;
199  * Generic success/failure response proxy.
201  * @param {function():void} onDone
202  * @param {function(remoting.Error):void} onError
203  * @return {function(XMLHttpRequest):void}
204  */
205 remoting.xhr.defaultResponse = function(onDone, onError) {
206   /** @param {XMLHttpRequest} xhr */
207   var result = function(xhr) {
208     /** @type {remoting.Error} */
209     var error =
210         remoting.Error.fromHttpStatus(/** @type {number} */ (xhr.status));
211     if (error == remoting.Error.NONE) {
212       onDone();
213     } else {
214       onError(error);
215     }
216   };
217   return result;