Chromium Code Reviews| 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 |