UNPKG

httpism

Version:

HTTP client with middleware and good defaults

799 lines (667 loc) 18.9 kB
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ var httpism = require('./httpism'); var utils = require('./middlewareUtils'); var qs = require('qs'); function json(request, next) { if (request.body instanceof Object) { request.body = JSON.stringify(request.body); utils.setHeaderTo(request, "content-type", "application/json"); } utils.setHeaderTo(request, "accept", "application/json"); return next().then(function(response) { if (utils.shouldParseAs(response, "json", request)) { response.body = JSON.parse(response.body); } return response; }); } function text(request, next) { if (typeof request.body === 'string') { utils.setHeaderTo(request, "content-type", "text/plain;charset=UTF-8"); } return next(); } function form(request, next) { if (request.options.form && request.body instanceof Object) { setBodyToString(request, qs.stringify(request.body)); utils.setHeaderTo(request, "content-type", "application/x-www-form-urlencoded"); } return next().then(function(response) { if (utils.shouldParseAs(response, "form", request)) { response.body = qs.parse(response.body); } return response; }); } function setBodyToString(r, s) { r.body = s; } function parseHeaders(headers) { var object = {}; var lines = headers.split('\n'); for(var n = 0; n < lines.length; n++) { var line = lines[n]; var match = /^(.*?):(.*)/.exec(line); if (match) { object[match[1].toLowerCase()] = match[2].trim(); } } return object; } function setHeaders(headers, xhr) { var headerNames = Object.keys(headers); for (var n = 0; n < headerNames.length; n++) { var headerName = headerNames[n]; xhr.setRequestHeader(headerName, headers[headerName]); } } function responseUrl(xhr, requestUrl) { var origin = location.origin; var responseUrl = xhr.responseURL; if (responseUrl) { if (responseUrl.substring(0, origin.length) == origin) { return responseUrl.substring(origin.length); } else { return responseUrl; } } else { return requestUrl; } } function send(request) { var xhr = new XMLHttpRequest(); var reject; var promise = new Promise(function (fulfil, _reject) { reject = _reject; xhr.open(request.method, request.url, true); xhr.onload = function (event) { var statusCode = xhr.status; var response = { body: statusCode == 204? undefined: xhr.responseText, headers: parseHeaders(xhr.getAllResponseHeaders()), statusCode: statusCode, url: responseUrl(xhr, request.url), xhr: xhr, statusText: xhr.statusText }; fulfil(response); }; xhr.onerror = function (error) { reject(new Error('failed to connect to ' + request.method + ' ' + request.url)); }; if (!isCrossDomain(request.url) && !request.headers['x-requested-with']) { request.headers['x-requested-with'] = 'XMLHttpRequest' } setHeaders(request.headers, xhr); xhr.withCredentials = !!request.options.withCredentials; xhr.send(request.body); }); function abort() { xhr.abort(); var error = new Error('aborted connection to ' + request.method + ' ' + request.url); error.aborted = true; reject(error); } addAbortToPromise(promise, abort); return promise; } function isCrossDomain(url) { return /^https?:\/\//.test(url); } function addAbortToPromise(promise, abort) { var then = promise.then; promise.then = function () { var p = then.apply(this, arguments); p.abort = abort; addAbortToPromise(p, abort); return p; }; } module.exports = httpism( undefined, {}, [ utils.exception, form, json, text, utils.querystring, send ] ); },{"./httpism":2,"./middlewareUtils":4,"qs":5}],2:[function(require,module,exports){ var merge = require('./merge'); var resolveUrl = require('./resolveUrl'); var utils = require('./middlewareUtils'); function client(url, options, middlewares) { return new Httpism(url, options, middlewares); } function Httpism(url, options, middlewares) { this.url = url; this._options = options; this.middlewares = middlewares; } Httpism.prototype.send = function(method, url, body, _options, api) { var options = merge(_options, this._options) var request = { method: method, url: resolveUrl(this.url, url), headers: options.headers || {}, body: body, options: options }; var self = this; function sendToMiddleware(index) { if (index < self.middlewares.length) { var middleware = self.middlewares[index]; return middleware(request, function () { return sendToMiddleware(index + 1); }, self); } } return sendToMiddleware(0).then(function (response) { return makeResponse(self, response); }, function (e) { if (e.redirectResponse) { return e.redirectResponse; } else { throw e; } }); }; function makeResponse(api, response) { return utils.extend(new Httpism(api.url, api._options, api.middlewares), response); } Httpism.prototype.api = function (url, options, middlewares) { var args = parseClientArguments(url, options, middlewares); return new Httpism( resolveUrl(this.url, args.url), merge(args.options, this._options), args.middlewares ? args.middlewares.concat(this.middlewares) : this.middlewares ); }; function addMethod(method) { Httpism.prototype[method] = function (url, options) { return this.send(method, url, undefined, options, this); }; } function addMethodWithBody(method) { Httpism.prototype[method] = function (url, body, options) { return this.send(method, url, body, options, this); }; } addMethod('get'); addMethod('delete'); addMethod('head'); addMethodWithBody('post'); addMethodWithBody('put'); addMethodWithBody('patch'); addMethodWithBody('options'); function resolveUrl(base, url) { if (base) { return resolveUrl(base, url); } else { return url; } } function parseClientArguments() { var url, options, middlewares; for(var n = 0; n < arguments.length; n++) { var arg = arguments[n]; if (typeof arg === 'string') { url = arg; } else if (typeof arg === 'function') { middlewares = [arg]; } else if (arg instanceof Array) { middlewares = arg; } else if (arg instanceof Object) { options = arg; } } return { url: url, options: options, middlewares: middlewares }; } module.exports = client; },{"./merge":3,"./middlewareUtils":4,"./resolveUrl":6}],3:[function(require,module,exports){ module.exports = function(x, y) { if (x && y) { var r = {}; Object.keys(y).forEach(function (ykey) { r[ykey] = y[ykey]; }); Object.keys(x).forEach(function (xkey) { r[xkey] = x[xkey]; }); return r; } else if (y) { return y; } else { return x; } }; },{}],4:[function(require,module,exports){ var merge = require("./merge"); var qs = require('qs'); module.exports.setHeaderTo = function (request, header, value) { if (!request.headers[header]) { return request.headers[header] = value; } }; var responseBodyTypes = { json: function(response) { return contentTypeIs(response, "application/json"); }, text: function(response) { return contentTypeIsText(response) || contentTypeIs(response, "application/javascript"); }, form: function(response) { return contentTypeIs(response, "application/x-www-form-urlencoded"); }, stream: function(response) { return false; } }; function contentTypeIs(response, expectedContentType) { var re = new RegExp("^\\s*" + expectedContentType + "\\s*($|;)"); return re.test(response.headers["content-type"]); } function contentTypeIsText(response) { return contentTypeIs(response, "text/.*"); } module.exports.shouldParseAs = function(response, type, request, options) { var contentType = options !== undefined && options.hasOwnProperty(options, "contentType") && options.contentType !== undefined ? options.contentType : undefined; if (request.options.responseBody) { return type === request.options.responseBody; } else { var bodyType = responseBodyTypes[type]; if (bodyType) { return bodyType(response); } } }; function extend(object, extension) { var keys = Object.keys(extension); for (var n = 0; n < keys.length; n++) { var key = keys[n]; object[key] = extension[key]; } return object; } exports.extend = extend; exports.exception = function(request, next) { return next().then(function(response) { if (response.statusCode >= 400 && request.options.exceptions !== false) { var msg = request.method.toUpperCase() + " " + request.url + " => " + response.statusCode + " " + response.statusText; var error = extend(new Error(msg), response); throw error; } else { return response; } }); }; exports.querystring = function(request, next) { if (request.options.querystring instanceof Object) { var split = request.url.split("?"); var path = split[0]; var querystring = qs.parse(split[1]); var mergedQueryString = merge(request.options.querystring, querystring); request.url = path + "?" + qs.stringify(mergedQueryString); } return next(); }; },{"./merge":3,"qs":5}],5:[function(require,module,exports){ /** * Object#toString() ref for stringify(). */ var toString = Object.prototype.toString; /** * Object#hasOwnProperty ref */ var hasOwnProperty = Object.prototype.hasOwnProperty; /** * Array#indexOf shim. */ var indexOf = typeof Array.prototype.indexOf === 'function' ? function(arr, el) { return arr.indexOf(el); } : function(arr, el) { for (var i = 0; i < arr.length; i++) { if (arr[i] === el) return i; } return -1; }; /** * Array.isArray shim. */ var isArray = Array.isArray || function(arr) { return toString.call(arr) == '[object Array]'; }; /** * Object.keys shim. */ var objectKeys = Object.keys || function(obj) { var ret = []; for (var key in obj) { if (obj.hasOwnProperty(key)) { ret.push(key); } } return ret; }; /** * Array#forEach shim. */ var forEach = typeof Array.prototype.forEach === 'function' ? function(arr, fn) { return arr.forEach(fn); } : function(arr, fn) { for (var i = 0; i < arr.length; i++) fn(arr[i]); }; /** * Array#reduce shim. */ var reduce = function(arr, fn, initial) { if (typeof arr.reduce === 'function') return arr.reduce(fn, initial); var res = initial; for (var i = 0; i < arr.length; i++) res = fn(res, arr[i]); return res; }; /** * Cache non-integer test regexp. */ var isint = /^[0-9]+$/; function promote(parent, key) { if (parent[key].length == 0) return parent[key] = {} var t = {}; for (var i in parent[key]) { if (hasOwnProperty.call(parent[key], i)) { t[i] = parent[key][i]; } } parent[key] = t; return t; } function parse(parts, parent, key, val) { var part = parts.shift(); // illegal if (Object.getOwnPropertyDescriptor(Object.prototype, key)) return; // end if (!part) { if (isArray(parent[key])) { parent[key].push(val); } else if ('object' == typeof parent[key]) { parent[key] = val; } else if ('undefined' == typeof parent[key]) { parent[key] = val; } else { parent[key] = [parent[key], val]; } // array } else { var obj = parent[key] = parent[key] || []; if (']' == part) { if (isArray(obj)) { if ('' != val) obj.push(val); } else if ('object' == typeof obj) { obj[objectKeys(obj).length] = val; } else { obj = parent[key] = [parent[key], val]; } // prop } else if (~indexOf(part, ']')) { part = part.substr(0, part.length - 1); if (!isint.test(part) && isArray(obj)) obj = promote(parent, key); parse(parts, obj, part, val); // key } else { if (!isint.test(part) && isArray(obj)) obj = promote(parent, key); parse(parts, obj, part, val); } } } /** * Merge parent key/val pair. */ function merge(parent, key, val){ if (~indexOf(key, ']')) { var parts = key.split('[') , len = parts.length , last = len - 1; parse(parts, parent, 'base', val); // optimize } else { if (!isint.test(key) && isArray(parent.base)) { var t = {}; for (var k in parent.base) t[k] = parent.base[k]; parent.base = t; } set(parent.base, key, val); } return parent; } /** * Compact sparse arrays. */ function compact(obj) { if ('object' != typeof obj) return obj; if (isArray(obj)) { var ret = []; for (var i in obj) { if (hasOwnProperty.call(obj, i)) { ret.push(obj[i]); } } return ret; } for (var key in obj) { obj[key] = compact(obj[key]); } return obj; } /** * Parse the given obj. */ function parseObject(obj){ var ret = { base: {} }; forEach(objectKeys(obj), function(name){ merge(ret, name, obj[name]); }); return compact(ret.base); } /** * Parse the given str. */ function parseString(str){ var ret = reduce(String(str).split('&'), function(ret, pair){ var eql = indexOf(pair, '=') , brace = lastBraceInKey(pair) , key = pair.substr(0, brace || eql) , val = pair.substr(brace || eql, pair.length) , val = val.substr(indexOf(val, '=') + 1, val.length); // ?foo if ('' == key) key = pair, val = ''; if ('' == key) return ret; return merge(ret, decode(key), decode(val)); }, { base: {} }).base; return compact(ret); } /** * Parse the given query `str` or `obj`, returning an object. * * @param {String} str | {Object} obj * @return {Object} * @api public */ exports.parse = function(str){ if (null == str || '' == str) return {}; return 'object' == typeof str ? parseObject(str) : parseString(str); }; /** * Turn the given `obj` into a query string * * @param {Object} obj * @return {String} * @api public */ var stringify = exports.stringify = function(obj, prefix) { if (isArray(obj)) { return stringifyArray(obj, prefix); } else if ('[object Object]' == toString.call(obj)) { return stringifyObject(obj, prefix); } else if ('string' == typeof obj) { return stringifyString(obj, prefix); } else { return prefix + '=' + encodeURIComponent(String(obj)); } }; /** * Stringify the given `str`. * * @param {String} str * @param {String} prefix * @return {String} * @api private */ function stringifyString(str, prefix) { if (!prefix) throw new TypeError('stringify expects an object'); return prefix + '=' + encodeURIComponent(str); } /** * Stringify the given `arr`. * * @param {Array} arr * @param {String} prefix * @return {String} * @api private */ function stringifyArray(arr, prefix) { var ret = []; if (!prefix) throw new TypeError('stringify expects an object'); for (var i = 0; i < arr.length; i++) { ret.push(stringify(arr[i], prefix + '[' + i + ']')); } return ret.join('&'); } /** * Stringify the given `obj`. * * @param {Object} obj * @param {String} prefix * @return {String} * @api private */ function stringifyObject(obj, prefix) { var ret = [] , keys = objectKeys(obj) , key; for (var i = 0, len = keys.length; i < len; ++i) { key = keys[i]; if ('' == key) continue; if (null == obj[key]) { ret.push(encodeURIComponent(key) + '='); } else { ret.push(stringify(obj[key], prefix ? prefix + '[' + encodeURIComponent(key) + ']' : encodeURIComponent(key))); } } return ret.join('&'); } /** * Set `obj`'s `key` to `val` respecting * the weird and wonderful syntax of a qs, * where "foo=bar&foo=baz" becomes an array. * * @param {Object} obj * @param {String} key * @param {String} val * @api private */ function set(obj, key, val) { var v = obj[key]; if (Object.getOwnPropertyDescriptor(Object.prototype, key)) return; if (undefined === v) { obj[key] = val; } else if (isArray(v)) { v.push(val); } else { obj[key] = [v, val]; } } /** * Locate last brace in `str` within the key. * * @param {String} str * @return {Number} * @api private */ function lastBraceInKey(str) { var len = str.length , brace , c; for (var i = 0; i < len; ++i) { c = str[i]; if (']' == c) brace = false; if ('[' == c) brace = true; if ('=' == c && !brace) return i; } } /** * Decode `str`. * * @param {String} str * @return {String} * @api private */ function decode(str) { try { return decodeURIComponent(str.replace(/\+/g, ' ')); } catch (err) { return str; } } },{}],6:[function(require,module,exports){ // from https://gist.github.com/Yaffle/1088850 /*jslint regexp: true, white: true, maxerr: 50, indent: 2 */ function parseURI(url) { var m = String(url).replace(/^\s+|\s+$/g, '').match(/^([^:\/?#]+:)?(\/\/(?:[^:@]*(?::[^:@]*)?@)?(([^:\/?#]*)(?::(\d*))?))?([^?#]*)(\?[^#]*)?(#[\s\S]*)?/); // authority = '//' + user + ':' + pass '@' + hostname + ':' port return (m ? { href : m[0] || '', protocol : m[1] || '', authority: m[2] || '', host : m[3] || '', hostname : m[4] || '', port : m[5] || '', pathname : m[6] || '', search : m[7] || '', hash : m[8] || '' } : null); } module.exports = function (base, href) {// RFC 3986 function removeDotSegments(input) { var output = []; input.replace(/^(\.\.?(\/|$))+/, '') .replace(/\/(\.(\/|$))+/g, '/') .replace(/\/\.\.$/, '/../') .replace(/\/?[^\/]*/g, function (p) { if (p === '/..') { output.pop(); } else { output.push(p); } }); return output.join('').replace(/^\//, input.charAt(0) === '/' ? '/' : ''); } href = parseURI(href || ''); base = parseURI(base || ''); return !href || !base ? null : (href.protocol || base.protocol) + (href.protocol || href.authority ? href.authority : base.authority) + removeDotSegments(href.protocol || href.authority || href.pathname.charAt(0) === '/' ? href.pathname : (href.pathname ? ((base.authority && !base.pathname ? '/' : '') + base.pathname.slice(0, base.pathname.lastIndexOf('/') + 1) + href.pathname) : base.pathname)) + (href.protocol || href.authority || href.pathname ? href.search : (href.search || base.search)) + href.hash; }; },{}]},{},[1]);