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.
4
5/**
6 * @fileoverview
7 * Simple utilities for making XHRs more pleasant.
8 */
9
10'use strict';
11
12/** @suppress {duplicate} */
13var remoting = remoting || {};
14
15/** Namespace for XHR functions */
16/** @type {Object} */
17remoting.xhr = remoting.xhr || {};
18
19/**
20 * Takes an associative array of parameters and urlencodes it.
21 *
22 * @param {Object.<string>} paramHash The parameter key/value pairs.
23 * @return {string} URLEncoded version of paramHash.
24 */
25remoting.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 '';
35};
36
37/**
38 * Execute an XHR GET asynchronously.
39 *
40 * @param {string} url The base URL to GET, excluding parameters.
41 * @param {function(XMLHttpRequest):void} onDone The function to call on
42 *     completion.
43 * @param {(string|Object.<string>)=} opt_parameters The request parameters,
44 *     either as an associative array, or a string.  If it is a string, do
45 *     not include the ? and be sure it is correctly URLEncoded.
46 * @param {Object.<string>=} opt_headers Additional headers to include on the
47 *     request.
48 * @param {boolean=} opt_withCredentials Set the withCredentials flags in the
49 *     XHR.
50 * @return {XMLHttpRequest} The request object.
51 */
52remoting.xhr.get = function(url, onDone, opt_parameters, opt_headers,
53                            opt_withCredentials) {
54  return remoting.xhr.doMethod('GET', url, onDone, opt_parameters,
55                               opt_headers, opt_withCredentials);
56};
57
58/**
59 * Execute an XHR POST asynchronously.
60 *
61 * @param {string} url The base URL to POST, excluding parameters.
62 * @param {function(XMLHttpRequest):void} onDone The function to call on
63 *     completion.
64 * @param {(string|Object.<string>)=} opt_parameters The request parameters,
65 *     either as an associative array, or a string.  If it is a string, be
66 *     sure it is correctly URLEncoded.
67 * @param {Object.<string>=} opt_headers Additional headers to include on the
68 *     request.
69 * @param {boolean=} opt_withCredentials Set the withCredentials flags in the
70 *     XHR.
71 * @return {XMLHttpRequest} The request object.
72 */
73remoting.xhr.post = function(url, onDone, opt_parameters, opt_headers,
74                             opt_withCredentials) {
75  return remoting.xhr.doMethod('POST', url, onDone, opt_parameters,
76                               opt_headers, opt_withCredentials);
77};
78
79/**
80 * Execute an XHR DELETE asynchronously.
81 *
82 * @param {string} url The base URL to DELETE, excluding parameters.
83 * @param {function(XMLHttpRequest):void} onDone The function to call on
84 *     completion.
85 * @param {(string|Object.<string>)=} opt_parameters The request parameters,
86 *     either as an associative array, or a string.  If it is a string, be
87 *     sure it is correctly URLEncoded.
88 * @param {Object.<string>=} opt_headers Additional headers to include on the
89 *     request.
90 * @param {boolean=} opt_withCredentials Set the withCredentials flags in the
91 *     XHR.
92 * @return {XMLHttpRequest} The request object.
93 */
94remoting.xhr.remove = function(url, onDone, opt_parameters, opt_headers,
95                             opt_withCredentials) {
96  return remoting.xhr.doMethod('DELETE', url, onDone, opt_parameters,
97                               opt_headers, opt_withCredentials);
98};
99
100/**
101 * Execute an XHR PUT asynchronously.
102 *
103 * @param {string} url The base URL to PUT, excluding parameters.
104 * @param {function(XMLHttpRequest):void} onDone The function to call on
105 *     completion.
106 * @param {(string|Object.<string>)=} opt_parameters The request parameters,
107 *     either as an associative array, or a string.  If it is a string, be
108 *     sure it is correctly URLEncoded.
109 * @param {Object.<string>=} opt_headers Additional headers to include on the
110 *     request.
111 * @param {boolean=} opt_withCredentials Set the withCredentials flags in the
112 *     XHR.
113 * @return {XMLHttpRequest} The request object.
114 */
115remoting.xhr.put = function(url, onDone, opt_parameters, opt_headers,
116                             opt_withCredentials) {
117  return remoting.xhr.doMethod('PUT', url, onDone, opt_parameters,
118                               opt_headers, opt_withCredentials);
119};
120
121/**
122 * Execute an arbitrary HTTP method asynchronously.
123 *
124 * @param {string} methodName The HTTP method name, e.g. "GET", "POST" etc.
125 * @param {string} url The base URL, excluding parameters.
126 * @param {function(XMLHttpRequest):void} onDone The function to call on
127 *     completion.
128 * @param {(string|Object.<string>)=} opt_parameters The request parameters,
129 *     either as an associative array, or a string.  If it is a string, be
130 *     sure it is correctly URLEncoded.
131 * @param {Object.<string>=} opt_headers Additional headers to include on the
132 *     request.
133 * @param {boolean=} opt_withCredentials Set the withCredentials flags in the
134 *     XHR.
135 * @return {XMLHttpRequest} The XMLHttpRequest object.
136 */
137remoting.xhr.doMethod = function(methodName, url, onDone,
138                                 opt_parameters, opt_headers,
139                                 opt_withCredentials) {
140  /** @type {XMLHttpRequest} */
141  var xhr = new XMLHttpRequest();
142  xhr.onreadystatechange = function() {
143    if (xhr.readyState != 4) {
144      return;
145    }
146    onDone(xhr);
147  };
148
149  var parameterString = '';
150  if (typeof(opt_parameters) === 'string') {
151    parameterString = opt_parameters;
152  } else if (typeof(opt_parameters) === 'object') {
153    parameterString = remoting.xhr.urlencodeParamHash(opt_parameters);
154  } else if (opt_parameters === undefined) {
155    // No problem here. Do nothing.
156  } else {
157    throw 'opt_parameters must be string or associated array.';
158  }
159
160  var useBody = (methodName == 'POST') || (methodName == 'PUT');
161
162  if (!useBody && parameterString != '') {
163    url = url + '?' + parameterString;
164  }
165
166  xhr.open(methodName, url, true);
167  if (methodName == 'POST' &&
168      (typeof opt_headers !== 'object' ||
169       typeof opt_headers['Content-type'] !== 'string')) {
170    xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
171  }
172  // Add in request headers.
173  if (typeof(opt_headers) === 'object') {
174    for (var key in opt_headers) {
175      xhr.setRequestHeader(key, opt_headers[key]);
176    }
177  } else if (opt_headers === undefined) {
178    // No problem here. Do nothing.
179  } else {
180    throw 'opt_headers must be associative array.';
181  }
182
183  if (opt_withCredentials) {
184    xhr.withCredentials = true;
185  }
186
187  xhr.send(useBody ? parameterString : null);
188  return xhr;
189};
190