Refactor to use Ajax helper everywhere + no-cache (#1119)

diff --git a/app/addons/auth/api.js b/app/addons/auth/api.js
index 7536376..a281675 100644
--- a/app/addons/auth/api.js
+++ b/app/addons/auth/api.js
@@ -12,43 +12,12 @@
 
 import app from './../../app';
 import Helpers from "../../helpers";
-import { defaultsDeep } from "lodash";
-
-export const json = (url, opts = {}) => fetch(
-  url,
-  defaultsDeep(
-    {
-      credentials: "include",
-      headers: {
-        accept: "application/json",
-        "Content-Type": "application/json"
-      }
-    },
-    opts
-  )
-).then(resp => resp.json());
-
-export const formEncoded = (url, opts = {}) => fetch(
-  url,
-  defaultsDeep(
-    {
-      credentials: "include",
-      headers: {
-        accept: "application/json",
-        "Content-Type": 'application/x-www-form-urlencoded;charset=UTF-8'
-      }
-    },
-    opts
-  )
-).then(resp => resp.json());
+import {deleteFormEncoded, get, postFormEncoded, put} from '../../core/ajax';
 
 
 export function createAdmin({name, password, node}) {
   const url = Helpers.getServerUrl(`/_node/${node}/_config/admins/${name}`);
-  return json(url, {
-    method: "PUT",
-    body: JSON.stringify(password)
-  });
+  return put(url, password);
 }
 
 let loggedInSessionPromise;
@@ -59,7 +28,7 @@
   }
 
   const url = Helpers.getServerUrl('/_session');
-  const promise = json(url).then(resp => {
+  const promise = get(url).then(resp => {
     if (resp.userCtx.name) {
       loggedInSessionPromise = promise;
     }
@@ -71,19 +40,13 @@
 
 export function login(body) {
   const url = Helpers.getServerUrl('/_session');
-  return formEncoded(url, {
-    method: "POST",
-    body: app.utils.queryParams(body)
-  });
+  return postFormEncoded(url, app.utils.queryParams(body));
 }
 
 export function logout() {
   loggedInSessionPromise = null;
   const url = Helpers.getServerUrl('/_session');
-  return formEncoded(url, {
-    method: "DELETE",
-    body: app.utils.queryParams({ username: "_", password: "_" })
-  });
+  return deleteFormEncoded(url, app.utils.queryParams({ username: "_", password: "_" }));
 }
 
 export default {
diff --git a/app/addons/cors/api.js b/app/addons/cors/api.js
index 82dda38..3d04c60 100644
--- a/app/addons/cors/api.js
+++ b/app/addons/cors/api.js
@@ -10,51 +10,33 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import 'whatwg-fetch';
+import {get, put} from '../../core/ajax';
 
 export const fetchCORSConfig = (baseURL) => {
   const configURL = baseURL + '/cors';
-  return fetch(configURL, {
-    headers: {
-      'Accept': 'application/json',
-      'Content-Type': 'application/json'
-    },
-    credentials: 'include',
-    method: 'GET'
-  })
-    .then((res) => res.json())
-    .then((json) => {
-      if (json.error) {
-        throw new Error(json.reason);
-      }
+  return get(configURL).then((json) => {
+    if (json.error) {
+      throw new Error(json.reason);
+    }
 
-      const origins = !json.origins ? [] : json.origins.split(',');
-      return {
-        origins: origins,
-        methods: json.methods,
-        credentials: json.credentials,
-        headers: json.headers
-      };
-    });
+    const origins = !json.origins ? [] : json.origins.split(',');
+    return {
+      origins: origins,
+      methods: json.methods,
+      credentials: json.credentials,
+      headers: json.headers
+    };
+  });
 };
 
 export const fetchHttpdConfig = (baseURL) => {
   const configURL = baseURL + '/httpd';
-  return fetch(configURL, {
-    headers: {
-      'Accept': 'application/json',
-      'Content-Type': 'application/json'
-    },
-    credentials: 'include',
-    method: 'GET'
-  })
-    .then((res) => res.json())
-    .then((json) => {
-      if (json.error) {
-        throw new Error(json.reason);
-      }
-      return json;
-    });
+  return get(configURL).then((json) => {
+    if (json.error) {
+      throw new Error(json.reason);
+    }
+    return json;
+  });
 };
 
 export const updateEnableCorsToHttpd = (baseURL, node, enableCors) => {
@@ -62,22 +44,12 @@
     throw new Error('node not set');
   }
   const configURL = baseURL + '/httpd/enable_cors';
-  return fetch(configURL, {
-    headers: {
-      'Accept': 'application/json',
-      'Content-Type': 'application/json'
-    },
-    credentials: 'include',
-    method: 'PUT',
-    body: JSON.stringify(enableCors.toString())
-  })
-    .then((res) => res.json())
-    .then((json) => {
-      if (json.error) {
-        throw new Error(json.reason);
-      }
-      return json;
-    });
+  return put(configURL, enableCors.toString())    .then((json) => {
+    if (json.error) {
+      throw new Error(json.reason);
+    }
+    return json;
+  });
 };
 
 export const updateCorsOrigins = (baseURL, node, origins) => {
@@ -101,20 +73,10 @@
     throw new Error('node not set');
   }
   const configURL = baseURL + '/cors/' + propName;
-  return fetch(configURL, {
-    headers: {
-      'Accept': 'application/json',
-      'Content-Type': 'application/json'
-    },
-    credentials: 'include',
-    method: 'PUT',
-    body: JSON.stringify(propValue)
-  })
-    .then((res) => res.json())
-    .then((json) => {
-      if (json.error) {
-        throw new Error(json.reason);
-      }
-      return json;
-    });
+  return put(configURL, propValue).then((json) => {
+    if (json.error) {
+      throw new Error(json.reason);
+    }
+    return json;
+  });
 };
diff --git a/app/addons/documents/index-results/api.js b/app/addons/documents/index-results/api.js
index 5f35a48..9f836fe 100644
--- a/app/addons/documents/index-results/api.js
+++ b/app/addons/documents/index-results/api.js
@@ -11,7 +11,7 @@
 // the License.
 
 import '@webcomponents/url';
-import 'whatwg-fetch';
+import {get, post} from '../../../core/ajax';
 import app from '../../../app';
 import Constants from '../constants';
 import FauxtonAPI from '../../../core/api';
@@ -21,21 +21,15 @@
   Object.assign(params, {reduce: undefined, group: undefined, group_level: undefined});
   const query = app.utils.queryString(params);
   const url = `${fetchUrl}${fetchUrl.includes('?') ? '&' : '?'}${query}`;
-  return fetch(url, {
-    credentials: 'include',
-    headers: {
-      'Accept': 'application/json; charset=utf-8'
+  return get(url).then(json => {
+    if (json.error) {
+      throw new Error('(' + json.error + ') ' + json.reason);
     }
-  }).then(res => res.json())
-    .then(json => {
-      if (json.error) {
-        throw new Error('(' + json.error + ') ' + json.reason);
-      }
-      return {
-        docs: json.rows,
-        docType: Constants.INDEX_RESULTS_DOC_TYPE.VIEW
-      };
-    });
+    return {
+      docs: json.rows,
+      docType: Constants.INDEX_RESULTS_DOC_TYPE.VIEW
+    };
+  });
 };
 
 export const queryMapReduceView = (fetchUrl, params) => {
@@ -51,48 +45,23 @@
   }
   const query = app.utils.queryString(params);
   const url = `${fetchUrl}${fetchUrl.includes('?') ? '&' : '?'}${query}`;
-  return fetch(url, {
-    credentials: 'include',
-    headers: {
-      'Accept': 'application/json; charset=utf-8'
+  return get(url).then(json => {
+    if (json.error) {
+      throw new Error('(' + json.error + ') ' + json.reason);
     }
-  })
-    .then(res => res.json())
-    .then(json => {
-      if (json.error) {
-        throw new Error('(' + json.error + ') ' + json.reason);
-      }
-      return {
-        docs: json.rows,
-        docType: Constants.INDEX_RESULTS_DOC_TYPE.VIEW
-      };
-    });
+    return {
+      docs: json.rows,
+      docType: Constants.INDEX_RESULTS_DOC_TYPE.VIEW
+    };
+  });
 };
 
 export const postToBulkDocs = (databaseName, payload) => {
   const url = FauxtonAPI.urls('bulk_docs', 'server', databaseName);
-  return fetch(url, {
-    method: 'POST',
-    credentials: 'include',
-    body: JSON.stringify(payload),
-    headers: {
-      'Accept': 'application/json; charset=utf-8',
-      'Content-Type': 'application/json'
-    }
-  })
-    .then(res => res.json());
+  return post(url, payload);
 };
 
 export const postToIndexBulkDelete = (databaseName, payload) => {
   const url = FauxtonAPI.urls('mango', 'index-server-bulk-delete', encodeURIComponent(databaseName));
-  return fetch(url, {
-    method: 'POST',
-    credentials: 'include',
-    body: JSON.stringify(payload),
-    headers: {
-      'Accept': 'application/json; charset=utf-8',
-      'Content-Type': 'application/json'
-    }
-  })
-    .then(res => res.json());
+  return post(url, payload);
 };
diff --git a/app/addons/documents/mango/mango.api.js b/app/addons/documents/mango/mango.api.js
index 1d0b452..0671172 100644
--- a/app/addons/documents/mango/mango.api.js
+++ b/app/addons/documents/mango/mango.api.js
@@ -10,52 +10,32 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import 'whatwg-fetch';
 import app from "../../../app";
+import {post, get} from '../../../core/ajax';
 import FauxtonAPI from "../../../core/api";
 import Constants from '../constants';
 
 export const fetchQueryExplain = (databaseName, queryCode) => {
   const url = FauxtonAPI.urls('mango', 'explain-server', encodeURIComponent(databaseName));
 
-  return fetch(url, {
-    headers: {
-      'Accept': 'application/json',
-      'Content-Type': 'application/json; charset=utf-8'
-    },
-    credentials: 'include',
-    method: 'POST',
-    body: queryCode
-  })
-    .then((res) => res.json())
-    .then((json) => {
-      if (json.error) {
-        throw new Error('(' + json.error + ') ' + json.reason);
-      }
-      return json;
-    });
+  return post(url, queryCode, {rawBody: true}).then((json) => {
+    if (json.error) {
+      throw new Error('(' + json.error + ') ' + json.reason);
+    }
+    return json;
+  });
 };
 
 export const createIndex = (databaseName, indexCode) => {
   const url = FauxtonAPI.urls('mango', 'index-server',
     app.utils.safeURLName(databaseName));
 
-  return fetch(url, {
-    headers: {
-      'Accept': 'application/json',
-      'Content-Type': 'application/json; charset=utf-8'
-    },
-    credentials: 'include',
-    method: 'POST',
-    body: indexCode
-  })
-    .then((res) => res.json())
-    .then((json) => {
-      if (json.error) {
-        throw new Error('(' + json.error + ') ' + json.reason);
-      }
-      return json;
-    });
+  return post(url, indexCode, {rawBody: true}).then((json) => {
+    if (json.error) {
+      throw new Error('(' + json.error + ') ' + json.reason);
+    }
+    return json;
+  });
 };
 
 export const fetchIndexes = (databaseName, params) => {
@@ -63,24 +43,15 @@
   let url = FauxtonAPI.urls('mango', 'index-server', app.utils.safeURLName(databaseName));
   url = `${url}${url.includes('?') ? '&' : '?'}${query}`;
 
-  return fetch(url, {
-    headers: {
-      'Accept': 'application/json',
-      'Content-Type': 'application/json; charset=utf-8'
-    },
-    credentials: 'include',
-    method: 'GET'
-  })
-    .then((res) => res.json())
-    .then((json) => {
-      if (json.error) {
-        throw new Error('(' + json.error + ') ' + json.reason);
-      }
-      return {
-        docs: json.indexes,
-        docType: Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX
-      };
-    });
+  return get(url).then((json) => {
+    if (json.error) {
+      throw new Error('(' + json.error + ') ' + json.reason);
+    }
+    return {
+      docs: json.indexes,
+      docType: Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX
+    };
+  });
 };
 
 // assume all databases being accessed are on the same
@@ -128,15 +99,7 @@
   const url = FauxtonAPI.urls('mango', 'query-server', encodeURIComponent(databaseName));
   const modifiedQuery = mergeFetchParams(queryCode, fetchParams);
 
-  return fetch(url, {
-    headers: {
-      'Accept': 'application/json',
-      'Content-Type': 'application/json; charset=utf-8'
-    },
-    credentials: 'include',
-    method: 'POST',
-    body: JSON.stringify(modifiedQuery)
-  });
+  return post(url, modifiedQuery, {raw: true});
 };
 
 export const mangoQueryDocs = (databaseName, queryCode, fetchParams) => {
diff --git a/app/core/ajax.js b/app/core/ajax.js
index 7db397c..07fd094 100644
--- a/app/core/ajax.js
+++ b/app/core/ajax.js
@@ -1,6 +1,6 @@
 import 'whatwg-fetch';
-import { defaultsDeep } from "lodash";
-import { Subject } from 'rxjs/Subject';
+import {defaultsDeep} from "lodash";
+import {Subject} from 'rxjs/Subject';
 import 'rxjs/add/operator/filter';
 
 /* Add a multicast observer so that all fetch requests can be observed
@@ -39,8 +39,10 @@
         credentials: "include",
         headers: {
           accept: "application/json",
-          "Content-Type": "application/json"
-        }
+          "Content-Type": "application/json",
+          "Pragma":"no-cache" //Disables cache for IE11
+        },
+        cache: "no-cache"
       }
     )
   ).then(resp => {
@@ -80,7 +82,10 @@
  */
 export const post = (url, body, opts = {}) => {
   if (body) {
-    opts.body = JSON.stringify(body);
+    if (opts.rawBody)
+      opts.body = body;
+    else
+      opts.body = JSON.stringify(body);
   }
   return json(url, "POST", opts);
 };
@@ -91,12 +96,45 @@
  * @param {string}   url  Url of request
  * @param {object} [body] Body of request
  * @param {object} [opts={}] Opts to add to request
+ * Passing in `rawBody: true` in here will not stringify the body.
  *
  * @return {Promise} A promise with the request's response
  */
 export const put = (url, body, opts = {}) => {
   if (body) {
-    opts.body = JSON.stringify(body);
+    if (opts.rawBody)
+      opts.body = body;
+    else
+      opts.body = JSON.stringify(body);
   }
   return json(url, "PUT", opts);
 };
+
+export const formEncoded = (url, method, opts = {}) => {
+  return json(url, method, defaultsDeep(
+    {},
+    opts,
+    {
+      headers: {
+        "Content-Type": 'application/x-www-form-urlencoded;charset=UTF-8'
+      }
+    }));
+};
+
+export const postFormEncoded = (url, body, opts = {}) => {
+  if (body)
+    opts.body = body;
+  return formEncoded(url, "POST", opts);
+};
+
+export const putFormEncoded = (url, body, opts = {}) => {
+  if (body)
+    opts.body = body;
+  return formEncoded(url, "PUT", opts);
+};
+
+export const deleteFormEncoded = (url, body, opts = {}) => {
+  if (body)
+    opts.body = body;
+  return formEncoded(url, "DELETE", opts);
+};