OLD | NEW |
---|---|
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * @fileoverview | 6 * @fileoverview |
7 * Simple utilities for making XHRs more pleasant. | 7 * Simple utilities for making XHRs more pleasant. |
8 */ | 8 */ |
9 | 9 |
10 'use strict'; | 10 'use strict'; |
11 | 11 |
12 /** @suppress {duplicate} */ | 12 /** @suppress {duplicate} */ |
13 var remoting = remoting || {}; | 13 var remoting = remoting || {}; |
14 | 14 |
15 /** Namespace for XHR functions */ | 15 /** Namespace for XHR functions */ |
16 /** @type {Object} */ | 16 /** @type {Object} */ |
17 remoting.xhr = remoting.xhr || {}; | 17 remoting.xhr = remoting.xhr || {}; |
18 | 18 |
19 /** | 19 /** |
20 * Takes an associative array of parameters and urlencodes it. | 20 * Takes an associative array of parameters and urlencodes it. |
21 * | 21 * |
22 * @param {Object.<string>} paramHash The parameter key/value pairs. | 22 * @param {Object.<string,string>} paramHash The parameter key/value pairs. |
23 * @return {string} URLEncoded version of paramHash. | 23 * @return {string} URLEncoded version of paramHash. |
24 */ | 24 */ |
25 remoting.xhr.urlencodeParamHash = function(paramHash) { | 25 remoting.xhr.urlencodeParamHash = function(paramHash) { |
26 var paramArray = []; | 26 var paramArray = []; |
27 for (var key in paramHash) { | 27 for (var key in paramHash) { |
28 paramArray.push(encodeURIComponent(key) + | 28 paramArray.push(encodeURIComponent(key) + |
29 '=' + encodeURIComponent(paramHash[key])); | 29 '=' + encodeURIComponent(paramHash[key])); |
30 } | 30 } |
31 if (paramArray.length > 0) { | 31 if (paramArray.length > 0) { |
32 paramArray.sort(); // To simplify testing. | 32 paramArray.sort(); // To simplify testing. |
33 return paramArray.join('&'); | 33 return paramArray.join('&'); |
34 } | 34 } |
35 return ''; | 35 return ''; |
36 }; | 36 }; |
37 | 37 |
38 /** | 38 /** |
39 * Execute an XHR GET asynchronously. | 39 * Parameters for the 'start' function. |
Jamie
2015/02/21 01:35:48
I'm not a big fan of mixing optional and mandatory
John Williams
2015/02/23 23:00:55
I prefer to keep it the way it is, because when th
| |
40 * | 40 * |
41 * @param {string} url The base URL to GET, excluding parameters. | 41 * method: The HTTP method to use. |
42 * @param {function(XMLHttpRequest):void} onDone The function to call on | 42 * |
43 * completion. | 43 * url: The URL to request. |
44 * @param {(string|Object.<string>)=} opt_parameters The request parameters, | 44 * |
45 * either as an associative array, or a string. If it is a string, do | 45 * onDone: Function to call when the XHR finishes. |
46 * not include the ? and be sure it is correctly URLEncoded. | 46 |
47 * @param {Object.<string>=} opt_headers Additional headers to include on the | 47 * urlParams: (optional) Parameters to be appended to the URL. |
48 * request. | 48 * Null-valued parameters are omitted. |
49 * @param {boolean=} opt_withCredentials Set the withCredentials flags in the | 49 * |
50 * XHR. | 50 * textContent: (optional) Text to be sent as the request body. |
51 * @return {XMLHttpRequest} The request object. | 51 * |
52 * fromContent: (optional) Data to be URL-encoded and sent as the | |
Jamie
2015/02/21 01:35:48
s/from/form/
John Williams
2015/02/23 23:00:55
Done.
| |
53 * request body. Causes Content-type header to be set | |
54 * appropriately. | |
55 * | |
56 * jsonContent: (optional) Data to be JSON-encoded and sent as the | |
57 * request body. Causes Content-type header to be set | |
58 * appropriately. | |
59 * | |
60 * headers: (optional) Additional request headers to be sent. | |
61 * Null-valued headers are omitted. | |
62 * | |
63 * withCredentials: (optional) Value of the XHR's withCredentials field. | |
64 * | |
65 * oauthToken: (optional) An OAuth2 token used to construct an | |
66 * Authentication header. | |
67 * | |
68 * @typedef {{ | |
69 * method: string, | |
70 * url:string, | |
71 * onDone:(function(XMLHttpRequest):void), | |
72 * urlParams:(string|Object<string,?string>|undefined), | |
73 * textContent:(string|undefined), | |
74 * formContent:(Object|undefined), | |
75 * jsonContent:(*|undefined), | |
76 * headers:(Object<string,?string>|undefined), | |
77 * withCredentials:(boolean|undefined), | |
78 * oauthToken:(string|undefined) | |
79 * }} | |
52 */ | 80 */ |
53 remoting.xhr.get = function(url, onDone, opt_parameters, opt_headers, | 81 remoting.XhrParams; |
54 opt_withCredentials) { | 82 |
55 return remoting.xhr.doMethod('GET', url, onDone, opt_parameters, | 83 /** |
56 opt_headers, opt_withCredentials); | 84 * Returns a copy of the input object with all null or undefined |
85 * fields removed. | |
86 * | |
87 * @param {Object<string,?string>|undefined} input | |
88 * @return {!Object<string,string>} | |
89 * @private | |
90 */ | |
91 remoting.xhr.removeNullFields_ = function(input) { | |
92 /** @type {!Object<string,string>} */ | |
93 var result = {}; | |
94 if (input) { | |
95 for (var field in input) { | |
96 var value = input[field]; | |
97 if (value != null) { | |
98 result[field] = value; | |
99 } | |
100 } | |
101 } | |
102 return result; | |
57 }; | 103 }; |
58 | 104 |
59 /** | 105 /** |
60 * Execute an XHR POST asynchronously. | 106 * Executes an arbitrary HTTP method asynchronously. |
61 * | 107 * |
62 * @param {string} url The base URL to POST, excluding parameters. | 108 * @param {remoting.XhrParams} params |
63 * @param {function(XMLHttpRequest):void} onDone The function to call on | 109 * @return {XMLHttpRequest} The XMLHttpRequest object. |
64 * completion. | |
65 * @param {(string|Object.<string>)=} opt_parameters The request parameters, | |
66 * either as an associative array, or a string. If it is a string, be | |
67 * sure it is correctly URLEncoded. | |
68 * @param {Object.<string>=} opt_headers Additional headers to include on the | |
69 * request. | |
70 * @param {boolean=} opt_withCredentials Set the withCredentials flags in the | |
71 * XHR. | |
72 * @return {XMLHttpRequest} The request object. | |
73 */ | 110 */ |
74 remoting.xhr.post = function(url, onDone, opt_parameters, opt_headers, | 111 remoting.xhr.start = function(params) { |
75 opt_withCredentials) { | 112 // Extract fields that can be used more or less as-is. |
76 return remoting.xhr.doMethod('POST', url, onDone, opt_parameters, | 113 var method = params.method; |
77 opt_headers, opt_withCredentials); | 114 var url = params.url; |
115 var onDone = params.onDone; | |
116 var headers = remoting.xhr.removeNullFields_(params.headers); | |
117 var withCredentials = params.withCredentials || false; | |
118 | |
119 // Apply URL parameters. | |
120 var parameterString = ''; | |
121 if (typeof(params.urlParams) === 'string') { | |
122 parameterString = params.urlParams; | |
123 } else if (typeof(params.urlParams) === 'object') { | |
124 parameterString = remoting.xhr.urlencodeParamHash( | |
125 remoting.xhr.removeNullFields_(params.urlParams)); | |
126 } | |
127 if (parameterString) { | |
128 base.debug.assert(url.indexOf('?') == -1); | |
129 url += '?' + parameterString; | |
130 } | |
131 | |
132 // Check that the content spec is consistent. | |
133 if ((0 + | |
Jamie
2015/02/21 01:35:48
An explicit cast using Number would be more readab
John Williams
2015/02/23 23:00:55
Changed to an explicit cast. I don't want to use s
| |
134 (params.textContent !== undefined) + | |
135 (params.formContent !== undefined) + | |
136 (params.jsonContent !== undefined)) > 1) { | |
137 throw Error( | |
Jamie
2015/02/21 01:35:48
s/Error/new Error/ for consistency with (most of)
John Williams
2015/02/23 23:00:55
Done.
| |
138 'may only specify one of textContent, formContent, and jsonContent'); | |
139 } | |
140 | |
141 // Convert the content fields to a single text content variable. | |
142 /** @type {?string} */ | |
143 var content = null; | |
144 if (params.textContent !== undefined) { | |
145 content = params.textContent; | |
146 } else if (params.formContent !== undefined) { | |
147 if (!('Content-type' in headers)) { | |
148 headers['Content-type'] = 'application/x-www-form-urlencoded'; | |
149 } | |
150 content = remoting.xhr.urlencodeParamHash(params.formContent); | |
151 } else if (params.jsonContent !== undefined) { | |
152 if (!('Content-type' in headers)) { | |
153 headers['Content-type'] = 'application/json; charset=UTF-8'; | |
154 } | |
155 content = JSON.stringify(params.jsonContent); | |
156 } | |
157 | |
158 // Apply the oauthToken field. | |
159 if (params.oauthToken !== undefined) { | |
160 base.debug.assert(!('Authorization' in headers)); | |
161 headers['Authorization'] = 'Bearer ' + params.oauthToken; | |
162 } | |
163 | |
164 return remoting.xhr.startInternal_( | |
165 method, url, onDone, content, headers, withCredentials); | |
78 }; | 166 }; |
79 | 167 |
80 /** | 168 /** |
81 * Execute an XHR DELETE asynchronously. | 169 * Executes an arbitrary HTTP method asynchronously. |
82 * | 170 * |
83 * @param {string} url The base URL to DELETE, excluding parameters. | 171 * @param {string} method |
84 * @param {function(XMLHttpRequest):void} onDone The function to call on | 172 * @param {string} url |
85 * completion. | 173 * @param {function(XMLHttpRequest):void} onDone |
86 * @param {(string|Object.<string>)=} opt_parameters The request parameters, | 174 * @param {?string} content |
87 * either as an associative array, or a string. If it is a string, be | 175 * @param {!Object<string,string>} headers |
88 * sure it is correctly URLEncoded. | 176 * @param {boolean} withCredentials |
89 * @param {Object.<string>=} opt_headers Additional headers to include on the | 177 * @return {XMLHttpRequest} The XMLHttpRequest object. |
90 * request. | 178 * @private |
91 * @param {boolean=} opt_withCredentials Set the withCredentials flags in the | |
92 * XHR. | |
93 * @return {XMLHttpRequest} The request object. | |
94 */ | 179 */ |
95 remoting.xhr.remove = function(url, onDone, opt_parameters, opt_headers, | 180 remoting.xhr.startInternal_ = function( |
96 opt_withCredentials) { | 181 method, url, onDone, content, headers, withCredentials) { |
97 return remoting.xhr.doMethod('DELETE', url, onDone, opt_parameters, | |
98 opt_headers, opt_withCredentials); | |
99 }; | |
100 | |
101 /** | |
102 * Execute an XHR PUT asynchronously. | |
103 * | |
104 * @param {string} url The base URL to PUT, excluding parameters. | |
105 * @param {function(XMLHttpRequest):void} onDone The function to call on | |
106 * completion. | |
107 * @param {(string|Object.<string>)=} opt_parameters The request parameters, | |
108 * either as an associative array, or a string. If it is a string, be | |
109 * sure it is correctly URLEncoded. | |
110 * @param {Object.<string>=} opt_headers Additional headers to include on the | |
111 * request. | |
112 * @param {boolean=} opt_withCredentials Set the withCredentials flags in the | |
113 * XHR. | |
114 * @return {XMLHttpRequest} The request object. | |
115 */ | |
116 remoting.xhr.put = function(url, onDone, opt_parameters, opt_headers, | |
117 opt_withCredentials) { | |
118 return remoting.xhr.doMethod('PUT', url, onDone, opt_parameters, | |
119 opt_headers, opt_withCredentials); | |
120 }; | |
121 | |
122 /** | |
123 * Execute an arbitrary HTTP method asynchronously. | |
124 * | |
125 * @param {string} methodName The HTTP method name, e.g. "GET", "POST" etc. | |
126 * @param {string} url The base URL, excluding parameters. | |
127 * @param {function(XMLHttpRequest):void} onDone The function to call on | |
128 * completion. | |
129 * @param {(string|Object.<string>)=} opt_parameters The request parameters, | |
130 * either as an associative array, or a string. If it is a string, be | |
131 * sure it is correctly URLEncoded. | |
132 * @param {Object.<string>=} opt_headers Additional headers to include on the | |
133 * request. | |
134 * @param {boolean=} opt_withCredentials Set the withCredentials flags in the | |
135 * XHR. | |
136 * @return {XMLHttpRequest} The XMLHttpRequest object. | |
137 */ | |
138 remoting.xhr.doMethod = function(methodName, url, onDone, | |
139 opt_parameters, opt_headers, | |
140 opt_withCredentials) { | |
141 /** @type {XMLHttpRequest} */ | 182 /** @type {XMLHttpRequest} */ |
142 var xhr = new XMLHttpRequest(); | 183 var xhr = new XMLHttpRequest(); |
143 xhr.onreadystatechange = function() { | 184 xhr.onreadystatechange = function() { |
144 if (xhr.readyState != 4) { | 185 if (xhr.readyState != 4) { |
145 return; | 186 return; |
146 } | 187 } |
147 onDone(xhr); | 188 onDone(xhr); |
148 }; | 189 }; |
149 | 190 |
150 var parameterString = ''; | 191 xhr.open(method, url, true); |
151 if (typeof(opt_parameters) === 'string') { | 192 for (var key in headers) { |
152 parameterString = opt_parameters; | 193 xhr.setRequestHeader(key, headers[key]); |
153 } else if (typeof(opt_parameters) === 'object') { | |
154 parameterString = remoting.xhr.urlencodeParamHash(opt_parameters); | |
155 } else if (opt_parameters === undefined) { | |
156 // No problem here. Do nothing. | |
157 } else { | |
158 throw 'opt_parameters must be string or associated array.'; | |
159 } | 194 } |
160 | 195 xhr.withCredentials = withCredentials; |
161 var useBody = (methodName == 'POST') || (methodName == 'PUT'); | 196 xhr.send(content); |
162 | |
163 if (!useBody && parameterString != '') { | |
164 url = url + '?' + parameterString; | |
165 } | |
166 | |
167 xhr.open(methodName, url, true); | |
168 if (methodName == 'POST' && | |
169 (typeof opt_headers !== 'object' || | |
170 typeof opt_headers['Content-type'] !== 'string')) { | |
171 xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); | |
172 } | |
173 // Add in request headers. | |
174 if (typeof(opt_headers) === 'object') { | |
175 for (var key in opt_headers) { | |
176 xhr.setRequestHeader(key, opt_headers[key]); | |
177 } | |
178 } else if (opt_headers === undefined) { | |
179 // No problem here. Do nothing. | |
180 } else { | |
181 throw 'opt_headers must be associative array.'; | |
182 } | |
183 | |
184 if (opt_withCredentials) { | |
185 xhr.withCredentials = true; | |
186 } | |
187 | |
188 xhr.send(useBody ? parameterString : null); | |
189 return xhr; | 197 return xhr; |
190 }; | 198 }; |
191 | 199 |
192 /** | 200 /** |
193 * Generic success/failure response proxy. | 201 * Generic success/failure response proxy. |
194 * | 202 * |
195 * @param {function():void} onDone | 203 * @param {function():void} onDone |
196 * @param {function(remoting.Error):void} onError | 204 * @param {function(remoting.Error):void} onError |
197 * @return {function(XMLHttpRequest):void} | 205 * @return {function(XMLHttpRequest):void} |
198 */ | 206 */ |
199 remoting.xhr.defaultResponse = function(onDone, onError) { | 207 remoting.xhr.defaultResponse = function(onDone, onError) { |
200 /** @param {XMLHttpRequest} xhr */ | 208 /** @param {XMLHttpRequest} xhr */ |
201 var result = function(xhr) { | 209 var result = function(xhr) { |
202 /** @type {remoting.Error} */ | 210 /** @type {remoting.Error} */ |
203 var error = | 211 var error = |
204 remoting.Error.fromHttpStatus(/** @type {number} */ (xhr.status)); | 212 remoting.Error.fromHttpStatus(/** @type {number} */ (xhr.status)); |
205 if (error == remoting.Error.NONE) { | 213 if (error == remoting.Error.NONE) { |
206 onDone(); | 214 onDone(); |
207 } else { | 215 } else { |
208 onError(error); | 216 onError(error); |
209 } | 217 } |
210 }; | 218 }; |
211 return result; | 219 return result; |
212 }; | 220 }; |
OLD | NEW |