http-ng
Version:
a standalone angular.js $http service
1,235 lines (1,217 loc) • 47.1 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.http = global.http || {})));
}(this, (function (exports) { 'use strict';
var toString = Object.prototype.toString;
var getPrototypeOf = Object.getPrototypeOf;
var hasOwnProperty = Object.prototype.hasOwnProperty;
function lowercase(string) { return isString(string) ? string.toLowerCase() : string; }
function uppercase(string) { return isString(string) ? string.toUpperCase() : string; }
/**
* Determines if a reference is undefined.
*
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is undefined.
*/
function isUndefined(value) { return typeof value === 'undefined'; }
/**
* Determines if a reference is defined.
*
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is defined.
*/
function isDefined(value) { return typeof value !== 'undefined'; }
/**
* Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
* considered to be objects. Note that JavaScript arrays are objects.
*
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is an `Object` but not `null`.
*/
function isObject(value) {
// http://jsperf.com/isobject4
return value !== null && typeof value === 'object';
}
/**
* Determine if a value is an object with a null prototype
*
* @returns {boolean} True if `value` is an `Object` with a null prototype
*/
function isBlankObject(value) {
return value !== null && typeof value === 'object' && !getPrototypeOf(value);
}
/**
* Determines if a reference is a `String`.
*
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a `String`.
*/
function isString(value) { return typeof value === 'string'; }
/**
* Determines if a reference is a `Number`.
*
* This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`.
*
* If you wish to exclude these then you can use the native
* [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite)
* method.
*
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a `Number`.
*/
function isNumber(value) { return typeof value === 'number'; }
/**
* Determines if a value is a date.
*
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a `Date`.
*/
function isDate(value) {
return toString.call(value) === '[object Date]';
}
var isArray = Array.isArray;
/**
* Determines if a reference is a `Function`.
*
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a `Function`.
*/
function isFunction(value) { return typeof value === 'function'; }
/**
* Determines if a value is a regular expression object.
*
* @private
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a `RegExp`.
*/
function isRegExp(value) {
return toString.call(value) === '[object RegExp]';
}
/**
* Checks if `obj` is a window object.
*
* @private
* @param {*} obj Object to check
* @returns {boolean} True if `obj` is a window obj.
*/
function isWindow(obj) {
return obj && obj.window === obj;
}
function isFile(obj) {
return toString.call(obj) === '[object File]';
}
function isFormData(obj) {
return toString.call(obj) === '[object FormData]';
}
function isBlob(obj) {
return toString.call(obj) === '[object Blob]';
}
function isPromiseLike(obj) {
return obj && isFunction(obj.then);
}
function trim(value) {
return isString(value) ? value.trim() : value;
}
/**
* Determines if a reference is a DOM element (or wrapped jQuery element).
*
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
*/
function isElement(node) {
return !!(node &&
(node.nodeName // We are a direct element.
|| (node.prop && node.attr && node.find))); // We have an on and find method part of jQuery API.
}
/**
* @private
* @param {*} obj
* @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments,
* String ...)
*/
function isArrayLike(obj) {
// `null`, `undefined` and `window` are not array-like
if (obj == null || isWindow(obj))
return false;
// arrays, strings and jQuery/jqLite objects are array like
// * jqLite is either the jQuery or jqLite constructor function
// * we have to check the existence of jqLite first as this method is called
// via the forEach method when constructing the jqLite object in the first place
if (isArray(obj) || isString(obj))
return true;
// Support: iOS 8.2 (not reproducible in simulator)
// "length" in obj used to prevent JIT error (gh-11508)
var length = 'length' in Object(obj) && obj.length;
// NodeList objects (with `item` method) and
// other objects with suitable length characteristics are array-like
return isNumber(length) &&
(length >= 0 && ((length - 1) in obj || obj instanceof Array) || typeof obj.item === 'function');
}
/**
* Invokes the `iterator` function once for each item in `obj` collection, which can be either an
* object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value`
* is the value of an object property or an array element, `key` is the object property key or
* array element index and obj is the `obj` itself. Specifying a `context` for the function is optional.
*
* It is worth noting that `.forEach` does not iterate over inherited properties because it filters
* using the `hasOwnProperty` method.
*
* Unlike ES262's
* [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18),
* providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just
* return the value provided.
*
```js
var values = {name: 'misko', gender: 'male'};
var log = [];
angular.forEach(values, function(value, key) {
this.push(key + ': ' + value);
}, log);
expect(log).toEqual(['name: misko', 'gender: male']);
```
*
* @param {Object|Array} obj Object to iterate over.
* @param {Function} iterator Iterator function.
* @param {Object=} context Object to become context (`this`) for the iterator function.
* @returns {Object|Array} Reference to `obj`.
*/
function forEach(obj, iterator, context) {
var key, length;
if (obj) {
if (isFunction(obj)) {
for (key in obj) {
if (key !== 'prototype' && key !== 'length' && key !== 'name' && obj.hasOwnProperty(key)) {
iterator.call(context, obj[key], key, obj);
}
}
}
else if (isArray(obj) || isArrayLike(obj)) {
var isPrimitive = typeof obj !== 'object';
for (key = 0, length = obj.length; key < length; key++) {
if (isPrimitive || key in obj) {
iterator.call(context, obj[key], key, obj);
}
}
}
else if (obj.forEach && obj.forEach !== forEach) {
obj.forEach(iterator, context, obj);
}
else if (isBlankObject(obj)) {
// createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
for (key in obj) {
iterator.call(context, obj[key], key, obj);
}
}
else if (typeof obj.hasOwnProperty === 'function') {
// Slow path for objects inheriting Object.prototype, hasOwnProperty check needed
for (key in obj) {
if (obj.hasOwnProperty(key)) {
iterator.call(context, obj[key], key, obj);
}
}
}
else {
// Slow path for objects which do not have a method `hasOwnProperty`
for (key in obj) {
if (hasOwnProperty.call(obj, key)) {
iterator.call(context, obj[key], key, obj);
}
}
}
}
return obj;
}
function forEachSorted(obj, iterator, context) {
var keys = Object.keys(obj).sort();
for (var i = 0; i < keys.length; i++) {
iterator.call(context, obj[keys[i]], keys[i]);
}
return keys;
}
var JSON_START = /^\[|^\{(?!\{)/;
var JSON_ENDS = {
'[': /]$/,
'{': /}$/
};
function isJsonLike(str) {
var jsonStart = str.match(JSON_START);
return jsonStart && JSON_ENDS[jsonStart[0]].test(str);
}
/**
* Deserializes a JSON string.
*
* @param {string} json JSON string to deserialize.
* @returns {Object|Array|string|number} Deserialized JSON string.
*/
function fromJson(json) {
return isString(json)
? JSON.parse(json)
: json;
}
/**
* Creates a new object without a prototype. This object is useful for lookup without having to
* guard against prototypically inherited properties via hasOwnProperty.
*
* Related micro-benchmarks:
* - http://jsperf.com/object-create2
* - http://jsperf.com/proto-map-lookup/2
* - http://jsperf.com/for-in-vs-object-keys2
*
* @returns {Object}
*/
function createMap() {
return Object.create(null);
}
/**
* Creates a shallow copy of an object, an array or a primitive.
*
* Assumes that there are no proto properties for objects.
*/
function shallowCopy(src, dst) {
if (isArray(src)) {
dst = dst || [];
for (var i = 0, ii = src.length; i < ii; i++) {
dst[i] = src[i];
}
}
else if (isObject(src)) {
dst = dst || {};
for (var key in src) {
dst[key] = src[key];
}
}
return dst || src;
}
function baseExtend(dst, objs, deep) {
for (var i = 0, ii = objs.length; i < ii; ++i) {
var obj = objs[i];
if (!isObject(obj) && !isFunction(obj))
continue;
var keys = Object.keys(obj);
for (var j = 0, jj = keys.length; j < jj; j++) {
var key = keys[j];
var src = obj[key];
if (deep && isObject(src)) {
if (isDate(src)) {
dst[key] = new Date(src.valueOf());
}
else if (isRegExp(src)) {
dst[key] = new RegExp(src);
}
else if (src.nodeName) {
dst[key] = src.cloneNode(true);
}
else if (isElement(src)) {
dst[key] = src.clone();
}
else {
if (!isObject(dst[key]))
dst[key] = isArray(src) ? [] : {};
baseExtend(dst[key], [src], true);
}
}
else {
dst[key] = src;
}
}
}
return dst;
}
/**
* Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
* by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
*
* **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use
* {@link angular.merge} for this.
*
* @param {Object} dst Destination object.
* @param {...Object} src Source object(s).
* @returns {Object} Reference to `dst`.
*/
function extend(dst) {
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
return baseExtend(dst, args, false);
}
// NOTE: The usage of window and document instead of $window and $document here is
// deliberate. This service depends on the specific behavior of anchor nodes created by the
// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and
// cause us to break tests. In addition, when the browser resolves a URL for XHR, it
// doesn't know about mocked locations and resolves URLs to the real document - which is
// exactly the behavior needed here. There is little value is mocking these out for this
// service.
var urlParsingNode = window.document.createElement('a');
var originUrl = urlResolve(window.location.href);
/**
* documentMode is an IE-only property
* http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
*/
var msie = window.document.documentMode;
/**
*
* Implementation Notes for non-IE browsers
* ----------------------------------------
* Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM,
* results both in the normalizing and parsing of the URL. Normalizing means that a relative
* URL will be resolved into an absolute URL in the context of the application document.
* Parsing means that the anchor node's host, hostname, protocol, port, pathname and related
* properties are all populated to reflect the normalized URL. This approach has wide
* compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See
* http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
*
* Implementation Notes for IE
* ---------------------------
* IE <= 10 normalizes the URL when assigned to the anchor node similar to the other
* browsers. However, the parsed components will not be set if the URL assigned did not specify
* them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We
* work around that by performing the parsing in a 2nd step by taking a previously normalized
* URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the
* properties such as protocol, hostname, port, etc.
*
* References:
* http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement
* http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
* http://url.spec.whatwg.org/#urlutils
* https://github.com/angular/angular.js/pull/2902
* http://james.padolsey.com/javascript/parsing-urls-with-the-dom/
*
* @kind function
* @param {string} url The URL to be parsed.
* @description Normalizes and parses a URL.
* @returns {object} Returns the normalized URL as a dictionary.
*
* | member name | Description |
* |---------------|----------------|
* | href | A normalized version of the provided URL if it was not an absolute URL |
* | protocol | The protocol including the trailing colon |
* | host | The host and port (if the port is non-default) of the normalizedUrl |
* | search | The search params, minus the question mark |
* | hash | The hash string, minus the hash symbol
* | hostname | The hostname
* | port | The port, without ":"
* | pathname | The pathname, beginning with "/"
*
*/
function urlResolve(url) {
var href = url;
if (msie) {
// Normalize before parse. Refer Implementation Notes on why this is
// done in two steps on IE.
urlParsingNode.setAttribute('href', href);
href = urlParsingNode.href;
}
urlParsingNode.setAttribute('href', href);
// urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils
return {
href: urlParsingNode.href,
protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',
host: urlParsingNode.host,
search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '',
hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
hostname: urlParsingNode.hostname,
port: urlParsingNode.port,
pathname: (urlParsingNode.pathname.charAt(0) === '/')
? urlParsingNode.pathname
: '/' + urlParsingNode.pathname
};
}
/**
* Parse a request URL and determine whether this is a same-origin request as the application document.
*
* @param {string|object} requestUrl The url of the request as a string that will be resolved
* or a parsed URL object.
* @returns {boolean} Whether the request is for the same origin as the application document.
*/
function urlIsSameOrigin(requestUrl) {
return urlsAreSameOrigin(requestUrl, originUrl);
}
/**
* Determines if two URLs share the same origin.
*
* @param {string|object} url1 First URL to compare as a string or a normalized URL in the form of
* a dictionary object returned by `urlResolve()`.
* @param {string|object} url2 Second URL to compare as a string or a normalized URL in the form of
* a dictionary object returned by `urlResolve()`.
* @return {boolean} True if both URLs have the same origin, and false otherwise.
*/
function urlsAreSameOrigin(url1, url2) {
url1 = (isString(url1)) ? urlResolve(url1) : url1;
url2 = (isString(url2)) ? urlResolve(url2) : url2;
return (url1.protocol === url2.protocol &&
url1.host === url2.host);
}
/**
* This method is intended for encoding *key* or *value* parts of query component. We need a custom
* method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
* encoded per http://tools.ietf.org/html/rfc3986:
* query = *( pchar / "/" / "?" )
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* pct-encoded = "%" HEXDIG HEXDIG
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
* / "*" / "+" / "," / ";" / "="
*/
function encodeUriQuery(val, pctEncodeSpaces) {
return encodeURIComponent(val).
replace(/%40/gi, '@').
replace(/%3A/gi, ':').
replace(/%24/g, '$').
replace(/%2C/gi, ',').
replace(/%3B/gi, ';').
replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
}
function buildUrl(url, serializedParams) {
if (serializedParams.length > 0) {
url += ((url.indexOf('?') === -1) ? '?' : '&') + serializedParams;
}
return url;
}
function sanitizeJsonpCallbackParam(url, key) {
if (/[&?][^=]+=JSON_CALLBACK/.test(url)) {
// Throw if the url already contains a reference to JSON_CALLBACK
throw new Error('badjsonp: Illegal use of JSON_CALLBACK in url, ' + url);
}
var callbackParamRegex = new RegExp('[&?]' + key + '=');
if (callbackParamRegex.test(url)) {
// Throw if the callback param was already provided
throw new Error('badjsonp: Illegal use of callback param, "' + key + '", in url, "' + url + '"');
}
// Add in the JSON_CALLBACK callback param value
url += ((url.indexOf('?') === -1) ? '?' : '&') + key + '=JSON_CALLBACK';
return url;
}
/**
* Parse headers into key value object
*
* @param {string} headers Raw headers as a string
* @returns {Object} Parsed headers as key value object
*/
function parseHeaders(headers) {
var parsed = createMap(), i;
function fillInParsed(key, val) {
if (key) {
parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
}
}
if (isString(headers)) {
forEach(headers.split('\n'), function (line) {
i = line.indexOf(':');
fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1)));
});
}
else if (isObject(headers)) {
forEach(headers, function (headerVal, headerKey) {
fillInParsed(lowercase(headerKey), trim(headerVal));
});
}
return parsed;
}
/**
* Returns a function that provides access to parsed headers.
*
* Headers are lazy parsed when first requested.
* @see parseHeaders
*
* @param {(string|Object)} headers Headers to provide access to.
* @returns {function(string=)} Returns a getter function which if called with:
*
* - if called with an argument returns a single header value or null
* - if called with no arguments returns an object containing all headers.
*/
function headersGetter(headers) {
var headersObj;
return function (name) {
if (!headersObj) {
headersObj = parseHeaders(headers);
}
if (name) {
var value = headersObj[lowercase(name)];
if (value === undefined) {
value = null;
}
return value;
}
return headersObj;
};
}
function isSuccess(status) {
return 200 <= status && status < 300;
}
function httpParamSerializer(params) {
if (!params)
return '';
var parts = [];
forEachSorted(params, function (value, key) {
if (value === null || isUndefined(value))
return;
if (isArray(value)) {
forEach(value, function (v) {
parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v)));
});
}
else {
parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
}
});
return parts.join('&');
}
function httpParamSerializerJQLike(params) {
if (!params)
return '';
var parts = [];
serialize(params, '', true);
return parts.join('&');
function serialize(toSerialize, prefix, topLevel) {
if (toSerialize === null || isUndefined(toSerialize))
return;
if (isArray(toSerialize)) {
forEach(toSerialize, function (value, index) {
serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']');
});
}
else if (isObject(toSerialize) && !isDate(toSerialize)) {
forEachSorted(toSerialize, function (value, key) {
serialize(value, prefix +
(topLevel ? '' : '[') +
key +
(topLevel ? '' : ']'));
});
}
else {
parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize)));
}
}
}
function serializeValue(v) {
if (isObject(v)) {
return isDate(v) ? v.toISOString() : JSON.stringify(v);
}
return v;
}
function Defer() {
var promise, resolve, reject;
promise = new Promise(function (_resolve, _reject) {
resolve = _resolve;
reject = _reject;
});
return {
promise: promise,
resolve: resolve,
reject: reject
};
}
var lastCookies = {};
var lastCookieString = '';
function safeGetCookie(rawDocument) {
try {
return rawDocument.cookie || '';
}
catch (e) {
return '';
}
}
function safeDecodeURIComponent(str) {
try {
return decodeURIComponent(str);
}
catch (e) {
return str;
}
}
function cookieReader() {
var cookieArray, cookie, i, index, name;
var currentCookieString = safeGetCookie(document);
if (currentCookieString !== lastCookieString) {
lastCookieString = currentCookieString;
cookieArray = lastCookieString.split('; ');
lastCookies = {};
for (i = 0; i < cookieArray.length; i++) {
cookie = cookieArray[i];
index = cookie.indexOf('=');
if (index > 0) {
name = safeDecodeURIComponent(cookie.substring(0, index));
// the first value that is seen for a cookie is the most
// specific one. values for the same cookie name that
// follow are for less specific paths.
if (isUndefined(lastCookies[name])) {
lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
}
}
}
}
return lastCookies;
}
function xhrFactory() {
return new window.XMLHttpRequest();
}
var callbacks = {
$$counter: 0
};
window.$_$callbacks = callbacks;
var callbackMap = {};
function createCallback(callbackId) {
var callback = function (data) {
callback.data = data;
callback.called = true;
};
callback.id = callbackId;
return callback;
}
var jsonpCallbacks = {
/**
* @name $jsonpCallbacks#createCallback
* @param {string} url the url of the JSONP request
* @returns {string} the callback path to send to the server as part of the JSONP request
* @description
* {@link $httpBackend} calls this method to create a callback and get hold of the path to the callback
* to pass to the server, which will be used to call the callback with its payload in the JSONP response.
*/
createCallback: function (url) {
var callbackId = '_' + (callbacks.$$counter++).toString(36);
var callbackPath = '$_$callbacks.' + callbackId;
var callback = createCallback(callbackId);
callbackMap[callbackPath] = callbacks[callbackId] = callback;
return callbackPath;
},
/**
* @name $jsonpCallbacks#wasCalled
* @param {string} callbackPath the path to the callback that was sent in the JSONP request
* @returns {boolean} whether the callback has been called, as a result of the JSONP response
* @description
* {@link $httpBackend} calls this method to find out whether the JSONP response actually called the
* callback that was passed in the request.
*/
wasCalled: function (callbackPath) {
return callbackMap[callbackPath].called;
},
/**
* @name $jsonpCallbacks#getResponse
* @param {string} callbackPath the path to the callback that was sent in the JSONP request
* @returns {*} the data received from the response via the registered callback
* @description
* {@link $httpBackend} calls this method to get hold of the data that was provided to the callback
* in the JSONP response.
*/
getResponse: function (callbackPath) {
return callbackMap[callbackPath].data;
},
/**
* @name $jsonpCallbacks#removeCallback
* @param {string} callbackPath the path to the callback that was sent in the JSONP request
* @description
* {@link $httpBackend} calls this method to remove the callback after the JSONP request has
* completed or timed-out.
*/
removeCallback: function (callbackPath) {
var callback = callbackMap[callbackPath];
delete callbacks[callback.id];
delete callbackMap[callbackPath];
}
};
function httpBackEnd(method, url, post, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers) {
url = url || window.location.href.replace(/%27/g, '\'');
// if is jsonp, use script to perform request
if (lowercase(method) === 'jsonp') {
var callbackPath = jsonpCallbacks.createCallback(url);
var jsonpDone = jsonpReq(url, callbackPath, function (status, text) {
// jsonpReq only ever sets status to 200 (OK), 404 (ERROR) or -1 (WAITING)
var response = (status === 200) && jsonpCallbacks.getResponse(callbackPath);
completeRequest(callback, status, response, '', text);
jsonpCallbacks.removeCallback(callbackPath);
});
}
else {
var xhr = xhrFactory();
xhr.open(method, url, true);
forEach(headers, function (value, key) {
if (isDefined(value)) {
xhr.setRequestHeader(key, value);
}
});
xhr.onload = function requestLoaded() {
var statusText = xhr.statusText || '';
// responseText is the old-school way of retrieving response (supported by IE9)
// response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
var response = ('response' in xhr) ? xhr.response : xhr.responseText;
// normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
var status = xhr.status === 1223 ? 204 : xhr.status;
// fix status code when it is 0 (0 status is undocumented).
// Occurs when accessing file resources or on Android 4.1 stock browser
// while retrieving files from application cache.
if (status === 0) {
status = response ? 200 : urlResolve(url).protocol === 'file' ? 404 : 0;
}
completeRequest(callback, status, response, xhr.getAllResponseHeaders(), statusText);
};
var requestError = function () {
// The response is always empty
// See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
completeRequest(callback, -1, null, null, '');
};
xhr.onerror = requestError;
xhr.onabort = requestError;
xhr.ontimeout = requestError;
forEach(eventHandlers, function (value, key) {
xhr.addEventListener(key, value);
});
forEach(uploadEventHandlers, function (value, key) {
xhr.upload.addEventListener(key, value);
});
if (withCredentials) {
xhr.withCredentials = true;
}
if (responseType) {
try {
xhr.responseType = responseType;
}
catch (e) {
// WebKit added support for the json responseType value on 09/03/2013
// https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are
// known to throw when setting the value "json" as the response type. Other older
// browsers implementing the responseType
//
// The json response type can be ignored if not supported, because JSON payloads are
// parsed on the client-side regardless.
if (responseType !== 'json') {
throw e;
}
}
}
xhr.send(isUndefined(post) ? null : post);
}
if (timeout > 0) {
var timeoutId = setTimeout(timeoutRequest, timeout);
}
else if (isPromiseLike(timeout)) {
timeout.then(timeoutRequest);
}
function timeoutRequest() {
if (jsonpDone) {
jsonpDone();
}
if (xhr) {
xhr.abort();
}
}
function completeRequest(callback, status, response, headersString, statusText) {
// cancel timeout and subsequent timeout promise resolution
if (isDefined(timeoutId)) {
clearTimeout(timeoutId);
}
jsonpDone = xhr = null;
callback(status, response, headersString, statusText);
}
}
/**
* @return {Function} callback, invoke callback to cancel script load and error event listeners
*/
function jsonpReq(url, callbackPath, done) {
url = url.replace('JSON_CALLBACK', callbackPath);
// we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.:
// - fetches local scripts via XHR and evals them
// - adds and immediately removes script elements from the document
var script = document.createElement('script'), callback = null;
script.type = 'text/javascript';
script.src = url;
script.async = true;
callback = function (event) {
script.removeEventListener('load', callback);
script.removeEventListener('error', callback);
document.body.removeChild(script);
script = null;
var status = -1;
var text = 'unknown';
if (event) {
if (event.type === 'load' && !jsonpCallbacks.wasCalled(callbackPath)) {
event = { type: 'error' };
}
text = event.type;
status = event.type === 'error' ? 404 : 200;
}
if (done) {
done(status, text);
}
};
script.addEventListener('load', callback);
script.addEventListener('error', callback);
document.body.appendChild(script);
return callback;
}
var APPLICATION_JSON = 'application/json';
var CONTENT_TYPE_APPLICATION_JSON = { 'Content-Type': APPLICATION_JSON + ';charset=utf-8' };
var JSON_PROTECTION_PREFIX = /^\)]\}',?\n/;
/**
* Chain all given functions
*
* This function is used for both request and response transforming
*
* @param {*} data Data to transform.
* @param {function(string=)} headers HTTP headers getter fn.
* @param {number} status HTTP status code of the response.
* @param {(Function|Array.<Function>)} fns Function or an array of functions.
* @returns {*} Transformed data.
*/
function transformData(data, headers, status, fns) {
if (isFunction(fns)) {
return fns(data, headers, status);
}
forEach(fns, function (fn) {
data = fn(data, headers, status);
});
return data;
}
/**
* Object containing default values for all {@link ng.$http $http} requests.
*
* - **`defaults.cache`** - {boolean|Object} - A boolean value or object created with
* {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of HTTP responses
* by default. See {@link $http#caching $http Caching} for more information.
*
* - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token.
* Defaults value is `'XSRF-TOKEN'`.
*
* - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the
* XSRF token. Defaults value is `'X-XSRF-TOKEN'`.
*
* - **`defaults.headers`** - {Object} - Default headers for all $http requests.
* Refer to {@link ng.$http#setting-http-headers $http} for documentation on
* setting default headers.
* - **`defaults.headers.common`**
* - **`defaults.headers.post`**
* - **`defaults.headers.put`**
* - **`defaults.headers.patch`**
*
*
* - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function
* used to the prepare string representation of request parameters (specified as an object).
* If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}.
* Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}.
*
* - **`defaults.jsonpCallbackParam`** - `{string} - the name of the query parameter that passes the name of the
* callback in a JSONP request. The value of this parameter will be replaced with the expression generated by the
* {@link $jsonpCallbacks} service. Defaults to `'callback'`.
*
**/
var defaults = {
// transform incoming response data
transformResponse: [function defaultHttpResponseTransform(data, headers) {
if (isString(data)) {
// Strip json vulnerability protection prefix and trim whitespace
var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim();
if (tempData) {
var contentType = headers('Content-Type');
if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) {
data = fromJson(tempData);
}
}
}
return data;
}],
// transform outgoing request data
transformRequest: [function defaultHttpRequestTransform(d) {
return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? JSON.stringify(d) : d;
}],
// default headers
headers: {
common: {
'Accept': 'application/json, text/plain, */*'
},
post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON)
},
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
paramSerializer: httpParamSerializer,
jsonpCallbackParam: 'callback',
withCredentials: false,
cache: false
};
/**
* Array containing service factories for all synchronous or asynchronous {@link ng.$http $http}
* pre-processing of request or postprocessing of responses.
*
* These service factories are ordered by request, i.e. they are applied in the same order as the
* array, on request, but reverse order, on response.
*
* {@link ng.$http#interceptors Interceptors detailed info}
**/
var interceptors = [];
var $http = $http$1;
function $http$1(requestConfig) {
if (!isObject(requestConfig)) {
throw new Error('badreq: Http request configuration must be an object. Received: ' + JSON.stringify(requestConfig));
}
if (!isString(requestConfig.url)) {
throw new Error('badreq: Http request configuration url must be a string. Received: ' + requestConfig.url);
}
/**
* Interceptors stored in reverse order. Inner interceptors before outer interceptors.
* The reversal is needed so that we can build up the interception chain around the
* server request.
*/
var reversedInterceptors = [];
/**
* [1, 2, 3] => [3, 2, 1]
*/
forEach(interceptors, function (interceptor) {
if (isObject(interceptor)) {
reversedInterceptors.unshift(interceptor);
}
});
var config = extend({
method: 'get',
transformRequest: defaults.transformRequest,
transformResponse: defaults.transformResponse,
paramSerializer: defaults.paramSerializer,
jsonpCallbackParam: defaults.jsonpCallbackParam
}, requestConfig);
config.headers = mergeHeaders(requestConfig);
config.method = uppercase(config.method);
config.paramSerializer = isFunction(config.paramSerializer) ?
config.paramSerializer : defaults.paramSerializer;
var requestInterceptors = [];
var responseInterceptors = [];
var promise = Promise.resolve(config);
// apply interceptors
/**
* interceptors [1, 2, 3] reversed to
* reversedInterceptors [3, 2, 1] lead to
* request: [1, 2, 3]
* response: [3, 2, 1]
* Inner interceptors before outer interceptors.
* aka. internal interceptors before user defined interceptors
*/
forEach(reversedInterceptors, function (interceptor) {
if (interceptor.request || interceptor.requestError) {
requestInterceptors.unshift(interceptor.request, interceptor.requestError);
}
if (interceptor.response || interceptor.responseError) {
responseInterceptors.push(interceptor.response, interceptor.responseError);
}
});
promise = chainInterceptors(promise, requestInterceptors);
promise = promise.then(serverRequest);
promise = chainInterceptors(promise, responseInterceptors);
return promise;
function chainInterceptors(promise, interceptors) {
for (var i = 0, ii = interceptors.length; i < ii;) {
var thenFn = interceptors[i++];
var rejectFn = interceptors[i++];
promise = promise.then(thenFn, rejectFn);
}
interceptors.length = 0;
return promise;
}
function executeHeaderFns(headers, config) {
var headerContent, processedHeaders = {};
forEach(headers, function (headerFn, header) {
if (isFunction(headerFn)) {
headerContent = headerFn(config);
if (headerContent != null) {
processedHeaders[header] = headerContent;
}
}
else {
processedHeaders[header] = headerFn;
}
});
return processedHeaders;
}
function mergeHeaders(config) {
var defHeaders = defaults.headers, reqHeaders = extend({}, config.headers), defHeaderName, lowercaseDefHeaderName, reqHeaderName;
defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]);
// using for-in instead of forEach to avoid unnecessary iteration after header has been found
defaultHeadersIteration: for (defHeaderName in defHeaders) {
lowercaseDefHeaderName = lowercase(defHeaderName);
for (reqHeaderName in reqHeaders) {
if (lowercase(reqHeaderName) === lowercaseDefHeaderName) {
continue defaultHeadersIteration;
}
}
reqHeaders[defHeaderName] = defHeaders[defHeaderName];
}
// execute if header value is a function for merged headers
return executeHeaderFns(reqHeaders, shallowCopy(config));
}
function serverRequest(config) {
var headers = config.headers;
var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest);
// strip content-type if data is undefined
if (isUndefined(reqData)) {
forEach(headers, function (value, header) {
if (lowercase(header) === 'content-type') {
delete headers[header];
}
});
}
if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
config.withCredentials = defaults.withCredentials;
}
// send request
return sendReq(config, reqData).then(transformResponse, transformResponse);
}
function transformResponse(response) {
// make a copy since the response must be cacheable
var resp = extend({}, response);
resp.data = transformData(response.data, response.headers, response.status, config.transformResponse);
return (isSuccess(response.status))
? resp
: Promise.reject(resp);
}
}
/**
* Array of config objects for currently pending requests.
* This is primarily meant to be used for debugging purposes
*/
$http$1.pendingRequests = [];
/**
* Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
* default headers, withCredentials as well as request and response transformations.
*
* See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
*/
$http$1.defaults = defaults;
/**
* Array containing service factories for all synchronous or asynchronous
* pre-processing of request or postprocessing of responses.
*
* These service factories are ordered by request, i.e. they are applied in the same order as the
* array, on request, but reverse order, on response.
**/
$http$1.interceptors = interceptors;
// add get, delete, head, jsonp method alias
(function createShortMethods() {
var names = [];
for (var _i = 0; _i < arguments.length; _i++) {
names[_i] = arguments[_i];
}
forEach(names, function (name) {
$http$1[name] = function (url, config) {
return $http$1(extend({}, config || {}, {
method: name == 'del' ? 'delete' : name,
url: url
}));
};
});
})('get', 'delete', 'del', 'head', 'jsonp');
// add post, put, patch method alias
(function createShortMethodsWithData() {
var names = [];
for (var _i = 0; _i < arguments.length; _i++) {
names[_i] = arguments[_i];
}
forEach(names, function (name) {
$http$1[name] = function (url, data, config) {
return $http$1(extend({}, config || {}, {
method: name,
url: url,
data: data
}));
};
});
})('post', 'put', 'patch');
/**
* Makes the request.
*/
function sendReq(config, reqData) {
var deferred = Defer(), promise = deferred.promise, cache, cachedResp, reqHeaders = config.headers, isJsonp = lowercase(config.method) === 'jsonp', url = config.url;
url = buildUrl(url, config.paramSerializer(config.params));
if (isJsonp) {
// Check the url and add the JSONP callback placeholder
url = sanitizeJsonpCallbackParam(url, config.jsonpCallbackParam);
}
$http$1.pendingRequests.push(config);
promise.then(removePendingReq, removePendingReq);
if ((config.cache || defaults.cache) && config.cache !== false &&
(config.method === 'GET' || config.method === 'JSONP')) {
cache = isObject(config.cache) ? config.cache
: isObject(defaults.cache) ? defaults.cache
: null;
}
if (cache) {
cachedResp = cache.get(url);
if (isDefined(cachedResp)) {
if (isPromiseLike(cachedResp)) {
// cached request has already been sent, but there is no response yet
cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult);
}
else {
// serving from cache
if (isArray(cachedResp)) {
resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]);
}
else {
resolvePromise(cachedResp, 200, {}, 'OK');
}
}
}
else {
// put the promise for the non-transformed response into cache as a placeholder
cache.put(url, promise);
}
}
// if we won't have the response in cache, set the xsrf headers and
// send the request to the backend
if (isUndefined(cachedResp)) {
var xsrfValue = urlIsSameOrigin(config.url)
? cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName]
: undefined;
if (xsrfValue) {
reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
}
httpBackEnd(config.method, url, reqData, done, reqHeaders, config.timeout, config.withCredentials, config.responseType, config.eventHandlers, config.uploadEventHandlers);
}
return promise;
/**
* Callback registered to $httpBackend():
* - caches the response if desired
* - resolves the raw $http promise
* - calls $apply
*/
function done(status, response, headersString, statusText) {
if (cache) {
if (isSuccess(status)) {
cache.put(url, [status, response, parseHeaders(headersString), statusText]);
}
else {
// remove promise from the cache
cache.remove(url);
}
}
resolvePromise(response, status, headersString, statusText);
}
/**
* Resolves the raw $http promise.
*/
function resolvePromise(response, status, headers, statusText) {
//status: HTTP response status code, 0, -1 (aborted by timeout / promise)
status = status >= -1 ? status : 0;
(isSuccess(status) ? deferred.resolve : deferred.reject)({
data: response,
status: status,
headers: headersGetter(headers),
config: config,
statusText: statusText
});
}
function resolvePromiseWithResult(result) {
resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText);
}
function removePendingReq() {
var idx = $http$1.pendingRequests.indexOf(config);
if (idx !== -1)
$http$1.pendingRequests.splice(idx, 1);
}
}
exports.defaults = defaults;
exports.interceptors = interceptors;
exports['default'] = $http;
exports.httpParamSerializer = httpParamSerializer;
exports.httpParamSerializerJQLike = httpParamSerializerJQLike;
Object.defineProperty(exports, '__esModule', { value: true });
})));