Comparing version 10.0.0 to 11.0.0-0
@@ -1,13 +0,16 @@ | ||
import { Request } from './request'; | ||
import { Response } from './response'; | ||
export declare type Types = 'text' | 'document' | 'blob' | 'arraybuffer' | 'json'; | ||
export interface Options { | ||
type?: Types; | ||
withCredentials?: boolean; | ||
overrideMimeType?: string; | ||
import { Request, CreateHeaders } from 'servie'; | ||
import { CreateBody } from 'servie/dist/body/browser'; | ||
export * from './transports/xhr'; | ||
/** | ||
* Universal request options. | ||
*/ | ||
export interface RequestOptions { | ||
method?: string; | ||
headers?: CreateHeaders; | ||
trailer?: CreateHeaders | Promise<CreateHeaders>; | ||
body?: CreateBody; | ||
} | ||
export declare function createTransport(options: Options): { | ||
use: ((request: Request, next: () => Promise<Response>) => Promise<Response>)[]; | ||
abort: (request: Request) => void; | ||
open(request: Request): Promise<Response>; | ||
}; | ||
/** | ||
* Simple universal request creator. | ||
*/ | ||
export declare function request(url: string, options?: RequestOptions): Request; |
"use strict"; | ||
function __export(m) { | ||
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; | ||
} | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var response_1 = require("./response"); | ||
var index_1 = require("./plugins/index"); | ||
function createTransport(options) { | ||
return { | ||
use: use, | ||
abort: abort, | ||
open: function (request) { | ||
return handle(request, options); | ||
} | ||
}; | ||
const servie_1 = require("servie"); | ||
const browser_1 = require("servie/dist/body/browser"); | ||
// Use `xhr` transport in browsers. | ||
__export(require("./transports/xhr")); | ||
/** | ||
* Simple universal request creator. | ||
*/ | ||
function request(url, options = {}) { | ||
const { method } = options; | ||
const headers = servie_1.createHeaders(options.headers); | ||
const body = browser_1.createBody(options.body); | ||
const trailer = Promise.resolve(options.trailer).then(servie_1.createHeaders); | ||
return new servie_1.Request({ url, method, headers, body, trailer }); | ||
} | ||
exports.createTransport = createTransport; | ||
var use = [index_1.stringify(), index_1.headers()]; | ||
function handle(request, options) { | ||
return new Promise(function (resolve, reject) { | ||
var type = options.type || 'text'; | ||
var url = request.url, method = request.method; | ||
if (window.location.protocol === 'https:' && /^http\:/.test(url)) { | ||
return reject(request.error("The request to \"" + url + "\" was blocked", 'EBLOCKED')); | ||
} | ||
var xhr = request._raw = new XMLHttpRequest(); | ||
function done() { | ||
return new Promise(function (resolve) { | ||
return resolve(new response_1.Response({ | ||
status: xhr.status === 1223 ? 204 : xhr.status, | ||
statusText: xhr.statusText, | ||
rawHeaders: parseToRawHeaders(xhr.getAllResponseHeaders()), | ||
body: type === 'text' ? xhr.responseText : xhr.response, | ||
url: xhr.responseURL | ||
})); | ||
}); | ||
} | ||
xhr.onload = function () { return resolve(done()); }; | ||
xhr.onabort = function () { return resolve(done()); }; | ||
xhr.onerror = function () { | ||
return reject(request.error("Unable to connect to \"" + request.url + "\"", 'EUNAVAILABLE')); | ||
}; | ||
xhr.onprogress = function (e) { | ||
if (e.lengthComputable) { | ||
request.downloadLength = e.total; | ||
} | ||
request._setDownloadedBytes(e.loaded); | ||
}; | ||
xhr.upload.onloadend = function () { return request.downloaded = 1; }; | ||
if (method === 'GET' || method === 'HEAD' || !xhr.upload) { | ||
request.uploadLength = 0; | ||
request._setUploadedBytes(0, 1); | ||
} | ||
else { | ||
xhr.upload.onprogress = function (e) { | ||
if (e.lengthComputable) { | ||
request.uploadLength = e.total; | ||
} | ||
request._setUploadedBytes(e.loaded); | ||
}; | ||
xhr.upload.onloadend = function () { return request.uploaded = 1; }; | ||
} | ||
try { | ||
xhr.open(method, url); | ||
} | ||
catch (e) { | ||
return reject(request.error("Refused to connect to \"" + url + "\"", 'ECSP', e)); | ||
} | ||
if (options.withCredentials) { | ||
xhr.withCredentials = true; | ||
} | ||
if (options.overrideMimeType) { | ||
xhr.overrideMimeType(options.overrideMimeType); | ||
} | ||
if (type !== 'text') { | ||
try { | ||
xhr.responseType = type; | ||
} | ||
finally { | ||
if (xhr.responseType !== type) { | ||
return reject(request.error("Unsupported type: " + type, 'ETYPE')); | ||
} | ||
} | ||
} | ||
for (var i = 0; i < request.rawHeaders.length; i += 2) { | ||
xhr.setRequestHeader(request.rawHeaders[i], request.rawHeaders[i + 1]); | ||
} | ||
xhr.send(request.body); | ||
}); | ||
} | ||
function abort(request) { | ||
request._raw.abort(); | ||
} | ||
function parseToRawHeaders(headers) { | ||
var rawHeaders = []; | ||
var lines = headers.split(/\r?\n/); | ||
for (var _i = 0, lines_1 = lines; _i < lines_1.length; _i++) { | ||
var line = lines_1[_i]; | ||
if (line) { | ||
var indexOf = line.indexOf(':'); | ||
var name_1 = line.substr(0, indexOf).trim(); | ||
var value = line.substr(indexOf + 1).trim(); | ||
rawHeaders.push(name_1, value); | ||
} | ||
} | ||
return rawHeaders; | ||
} | ||
exports.request = request; | ||
//# sourceMappingURL=browser.js.map |
@@ -1,20 +0,17 @@ | ||
import FormData = require('form-data'); | ||
import { Request, RequestOptions, DefaultsOptions } from './request'; | ||
import * as plugins from './plugins/index'; | ||
import form from './form'; | ||
import jar from './jar'; | ||
import PopsicleError from './error'; | ||
import { createTransport } from './index'; | ||
export declare function defaults(defaultsOptions: DefaultsOptions): (options: string | RequestOptions) => Request; | ||
export declare const request: (options: string | RequestOptions) => Request; | ||
export declare const get: (options: string | RequestOptions) => Request; | ||
export declare const post: (options: string | RequestOptions) => Request; | ||
export declare const put: (options: string | RequestOptions) => Request; | ||
export declare const patch: (options: string | RequestOptions) => Request; | ||
export declare const del: (options: string | RequestOptions) => Request; | ||
export declare const head: (options: string | RequestOptions) => Request; | ||
export { PopsicleError, FormData, plugins, form, jar, createTransport }; | ||
export * from './base'; | ||
export * from './request'; | ||
export * from './response'; | ||
export default request; | ||
import { Request, Response } from 'servie'; | ||
import { Middleware } from 'throwback'; | ||
/** | ||
* Default header handling. | ||
*/ | ||
export declare function normalizeRequest<T extends Request, U extends Response>(): Middleware<T, U>; | ||
/** | ||
* Redirect middleware configuration. | ||
*/ | ||
export interface FollowRedirectsOptions { | ||
maxRedirects?: number; | ||
confirmRedirect?: (request: Request, response: Response) => boolean; | ||
} | ||
/** | ||
* Middleware function for following HTTP redirects. | ||
*/ | ||
export declare function followRedirects<T extends Request, U extends Response>(options?: FollowRedirectsOptions): Middleware<T, U>; |
"use strict"; | ||
function __export(m) { | ||
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; | ||
} | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var FormData = require("form-data"); | ||
exports.FormData = FormData; | ||
var request_1 = require("./request"); | ||
var plugins = require("./plugins/index"); | ||
exports.plugins = plugins; | ||
var form_1 = require("./form"); | ||
exports.form = form_1.default; | ||
var jar_1 = require("./jar"); | ||
exports.jar = jar_1.default; | ||
var error_1 = require("./error"); | ||
exports.PopsicleError = error_1.default; | ||
var index_1 = require("./index"); | ||
exports.createTransport = index_1.createTransport; | ||
function defaults(defaultsOptions) { | ||
var transport = index_1.createTransport({ type: 'text' }); | ||
var defaults = Object.assign({}, { transport: transport }, defaultsOptions); | ||
return function popsicle(options) { | ||
var opts = Object.assign({}, defaults, typeof options === 'string' ? { url: options } : options); | ||
if (typeof opts.url !== 'string') { | ||
throw new TypeError('The URL must be a string'); | ||
const url_1 = require("url"); | ||
const servie_1 = require("servie"); | ||
const error_1 = require("./error"); | ||
const universal_1 = require("servie/dist/body/universal"); | ||
/** | ||
* Redirection types to handle. | ||
*/ | ||
var REDIRECT_TYPE; | ||
(function (REDIRECT_TYPE) { | ||
REDIRECT_TYPE[REDIRECT_TYPE["FOLLOW_WITH_GET"] = 0] = "FOLLOW_WITH_GET"; | ||
REDIRECT_TYPE[REDIRECT_TYPE["FOLLOW_WITH_CONFIRMATION"] = 1] = "FOLLOW_WITH_CONFIRMATION"; | ||
})(REDIRECT_TYPE || (REDIRECT_TYPE = {})); | ||
/** | ||
* Possible redirection status codes. | ||
*/ | ||
const REDIRECT_STATUS = { | ||
'301': REDIRECT_TYPE.FOLLOW_WITH_GET, | ||
'302': REDIRECT_TYPE.FOLLOW_WITH_GET, | ||
'303': REDIRECT_TYPE.FOLLOW_WITH_GET, | ||
'307': REDIRECT_TYPE.FOLLOW_WITH_CONFIRMATION, | ||
'308': REDIRECT_TYPE.FOLLOW_WITH_CONFIRMATION | ||
}; | ||
/** | ||
* Default header handling. | ||
*/ | ||
function normalizeRequest() { | ||
return function (req, next) { | ||
// Block requests when already aborted. | ||
if (req.aborted) | ||
return Promise.reject(new error_1.PopsicleError('Request aborted', 'EABORT', req)); | ||
// If we have no accept header set already, default to accepting | ||
// everything. This is needed because otherwise Firefox defaults to | ||
// an accept header of `html/xml`. | ||
if (!req.headers.get('Accept')) { | ||
req.headers.set('Accept', '*/*'); | ||
} | ||
return new request_1.Request(opts); | ||
// Remove headers that should never be set by the user. | ||
req.headers.delete('Host'); | ||
return next(req); | ||
}; | ||
} | ||
exports.defaults = defaults; | ||
exports.request = defaults({}); | ||
exports.get = defaults({ method: 'get' }); | ||
exports.post = defaults({ method: 'post' }); | ||
exports.put = defaults({ method: 'put' }); | ||
exports.patch = defaults({ method: 'patch' }); | ||
exports.del = defaults({ method: 'delete' }); | ||
exports.head = defaults({ method: 'head' }); | ||
__export(require("./base")); | ||
__export(require("./request")); | ||
__export(require("./response")); | ||
exports.default = exports.request; | ||
exports.normalizeRequest = normalizeRequest; | ||
/** | ||
* Middleware function for following HTTP redirects. | ||
*/ | ||
function followRedirects(options = {}) { | ||
return function (initialRequest, next) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let req = initialRequest.clone(); | ||
let redirectCount = 0; | ||
const maxRedirects = typeof options.maxRedirects === 'number' ? options.maxRedirects : 5; | ||
const confirmRedirect = options.confirmRedirect || (() => false); | ||
while (redirectCount++ < maxRedirects) { | ||
const res = yield next(req); | ||
const redirect = REDIRECT_STATUS[res.statusCode]; | ||
// Handle HTTP redirects. | ||
if (redirect !== undefined && res.headers.has('Location')) { | ||
const newUrl = url_1.resolve(req.url, res.headers.get('Location')); // tslint:disable-line | ||
// Ignore the result of the response on redirect. | ||
req.abort(); | ||
req.events.emit('redirect', newUrl); | ||
if (redirect === REDIRECT_TYPE.FOLLOW_WITH_GET) { | ||
req = initialRequest.clone(); | ||
req.headers.set('Content-Length', '0'); | ||
req.url = newUrl; | ||
req.method = req.method.toUpperCase() === 'HEAD' ? 'HEAD' : 'GET'; | ||
req.body = universal_1.createBody(undefined); | ||
req.trailer = Promise.resolve(servie_1.createHeaders()); | ||
continue; | ||
} | ||
if (redirect === REDIRECT_TYPE.FOLLOW_WITH_CONFIRMATION) { | ||
const method = req.method.toUpperCase(); | ||
// Following HTTP spec by automatically redirecting with GET/HEAD. | ||
if (method === 'GET' || method === 'HEAD') { | ||
req = initialRequest.clone(); | ||
req.url = newUrl; | ||
continue; | ||
} | ||
// Allow the user to confirm redirect according to HTTP spec. | ||
if (confirmRedirect(req, res)) { | ||
req = initialRequest.clone(); | ||
req.url = newUrl; | ||
continue; | ||
} | ||
return res; | ||
} | ||
} | ||
return res; | ||
} | ||
throw new error_1.PopsicleError(`Maximum redirects exceeded: ${maxRedirects}`, 'EMAXREDIRECTS', req); | ||
}); | ||
}; | ||
} | ||
exports.followRedirects = followRedirects; | ||
//# sourceMappingURL=common.js.map |
@@ -1,8 +0,7 @@ | ||
import makeErrorCause = require('make-error-cause'); | ||
import { Request } from './request'; | ||
export default class PopsicleError extends makeErrorCause.BaseError { | ||
import { BaseError } from 'make-error-cause'; | ||
import { Request } from 'servie'; | ||
export declare class PopsicleError extends BaseError { | ||
code: string; | ||
popsicle: Request; | ||
name: string; | ||
constructor(message: string, code: string, original: Error, popsicle: Request); | ||
request: Request; | ||
constructor(message: string, code: string, request: Request, cause?: Error); | ||
} |
"use strict"; | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; | ||
return function (d, b) { | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var makeErrorCause = require("make-error-cause"); | ||
var PopsicleError = (function (_super) { | ||
__extends(PopsicleError, _super); | ||
function PopsicleError(message, code, original, popsicle) { | ||
var _this = _super.call(this, message, original) || this; | ||
_this.name = 'PopsicleError'; | ||
_this.code = code; | ||
_this.popsicle = popsicle; | ||
return _this; | ||
const make_error_cause_1 = require("make-error-cause"); | ||
class PopsicleError extends make_error_cause_1.BaseError { | ||
constructor(message, code, request, cause) { | ||
super(message, cause); | ||
this.code = code; | ||
this.request = request; | ||
} | ||
return PopsicleError; | ||
}(makeErrorCause.BaseError)); | ||
exports.default = PopsicleError; | ||
} | ||
exports.PopsicleError = PopsicleError; | ||
//# sourceMappingURL=error.js.map |
{ | ||
"name": "popsicle", | ||
"version": "10.0.0", | ||
"description": "Simple HTTP requests for node and the browser", | ||
"main": "dist/common.js", | ||
"types": "dist/common.d.ts", | ||
"version": "11.0.0-0", | ||
"description": "Advanced HTTP requests in node.js and browsers, using Servie", | ||
"main": "dist/universal.js", | ||
"types": "dist/universal.d.ts", | ||
"files": [ | ||
"dist/", | ||
"LICENSE", | ||
"logo.svg" | ||
], | ||
"browser": { | ||
"buffer": false, | ||
"form-data": "./dist/browser/form-data.js", | ||
"tough-cookie": "./dist/browser/tough-cookie.js", | ||
"./dist/index.js": "./dist/browser.js", | ||
"./dist/plugins/index.js": "./dist/plugins/browser.js", | ||
"./dist/plugins/is-host/index.js": "./dist/plugins/is-host/browser.js" | ||
"./dist/node.js": "./dist/browser.js" | ||
}, | ||
"scripts": { | ||
"lint": "tslint \"src/**/*.ts\" --project tsconfig.json --type-check", | ||
"check-size": "browserify . -s popsicle --external bluebird > popsicle.js && du -h popsicle.js", | ||
"build": "rm -rf dist/ && tsc && npm run check-size", | ||
"test-spec": "npm run test-server-open && HTTPS_PORT=7358 PORT=7357 node dist/test/index.js; EXIT=$?; npm run test-server-close; exit $EXIT", | ||
"test-cov": "HTTPS_PORT=7358 PORT=7357 istanbul cover --print none dist/test/index.js | tap-spec", | ||
"test-browser": "HTTPS_PORT=7358 PORT=7357 browserify -d -t envify dist/test/index.js | tape-run --render tap-spec", | ||
"test-server-open": "PORT=7357 node scripts/server.js & echo $! > server.pid; HTTPS_PORT=7358 node scripts/https-server.js & echo $! > https-server.pid", | ||
"test-server-close": "if [ -f server.pid ]; then kill -9 $(cat server.pid); rm server.pid; fi; if [ -f https-server.pid ]; then kill -9 $(cat https-server.pid); rm https-server.pid; fi", | ||
"test": "npm run lint && npm run build && npm run test-server-open && npm run test-cov && npm run test-browser; EXIT=$?; npm run test-server-close; exit $EXIT", | ||
"lint": "tslint \"src/**/*.ts\" --project tsconfig.json", | ||
"bundle": "browserify -g [ envify --NODE_ENV production ] -s popsicle -o popsicle.js .", | ||
"bundle:size": "du -h popsicle.js", | ||
"build": "rimraf dist/ && tsc && npm run bundle && npm run bundle:size", | ||
"specs": "HTTPS_PORT=7358 PORT=7357 jest --coverage", | ||
"server:open": "PORT=7357 node scripts/http-server.js & echo $! > server.pid; HTTPS_PORT=7358 node scripts/https-server.js & echo $! > https-server.pid", | ||
"server:close": "if [ -f server.pid ]; then kill -9 $(cat server.pid); rm server.pid; fi; if [ -f https-server.pid ]; then kill -9 $(cat https-server.pid); rm https-server.pid; fi", | ||
"test": "npm run lint && npm run build && npm run server:open && npm run specs; EXIT=$?; npm run server:close; exit $EXIT", | ||
"prepublish": "npm run build" | ||
@@ -34,12 +27,12 @@ }, | ||
"type": "git", | ||
"url": "git://github.com/blakeembrey/popsicle.git" | ||
"url": "git://github.com/serviejs/popsicle.git" | ||
}, | ||
"keywords": [ | ||
"request", | ||
"ajax", | ||
"http", | ||
"middleware", | ||
"node", | ||
"ajax", | ||
"browser", | ||
"promise", | ||
"agent" | ||
"promise" | ||
], | ||
@@ -53,32 +46,46 @@ "author": { | ||
"bugs": { | ||
"url": "https://github.com/blakeembrey/popsicle/issues" | ||
"url": "https://github.com/serviejs/popsicle/issues" | ||
}, | ||
"homepage": "https://github.com/blakeembrey/popsicle", | ||
"homepage": "https://github.com/serviejs/popsicle", | ||
"jest": { | ||
"roots": [ | ||
"<rootDir>/src/" | ||
], | ||
"transform": { | ||
"\\.tsx?$": "ts-jest" | ||
}, | ||
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(tsx?|jsx?)$", | ||
"moduleFileExtensions": [ | ||
"ts", | ||
"tsx", | ||
"js", | ||
"jsx", | ||
"json", | ||
"node" | ||
] | ||
}, | ||
"devDependencies": { | ||
"@types/blue-tape": "^0.1.30", | ||
"@types/jest": "^22.2.3", | ||
"@types/node": "^8.0.0", | ||
"blue-tape": "^1.0.0", | ||
"bluebird": "^3.0.5", | ||
"body-parser": "^1.9.2", | ||
"body-parser": "^1.18.3", | ||
"browserify": "^14.3.0", | ||
"envify": "^4.0.0", | ||
"express": "^4.10.2", | ||
"istanbul": "^0.4.0", | ||
"envify": "^4.1.0", | ||
"express": "^4.16.3", | ||
"jest": "^22.4.4", | ||
"methods": "^1.1.2", | ||
"tap-spec": "^4.1.1", | ||
"tape-run": "3.0.0", | ||
"tslint": "^5.2.0", | ||
"rimraf": "^2.6.2", | ||
"ts-jest": "^22.4.6", | ||
"tslint": "^5.10.0", | ||
"tslint-config-standard": "^7.0.0", | ||
"typescript": "^2.4.1" | ||
"typescript": "^2.8.3" | ||
}, | ||
"dependencies": { | ||
"@types/concat-stream": "^1.6.0", | ||
"@types/form-data": "0.0.33", | ||
"@types/methods": "^1.1.0", | ||
"@types/tough-cookie": "^2.3.0", | ||
"concat-stream": "^1.4.7", | ||
"form-data": "^2.0.0", | ||
"make-error-cause": "^1.2.1", | ||
"@types/pump": "^1.0.1", | ||
"@types/tough-cookie": "^2.3.2", | ||
"make-error-cause": "^2.1.0", | ||
"pump": "^3.0.0", | ||
"servie": "^3.2.2", | ||
"throwback": "^3.0.0", | ||
"tough-cookie": "^2.0.0" | ||
} | ||
} |
418
README.md
@@ -1,20 +0,10 @@ | ||
# ![Popsicle](https://cdn.rawgit.com/blakeembrey/popsicle/master/logo.svg) | ||
# ![Popsicle](https://cdn.rawgit.com/serviejs/popsicle/master/logo.svg) | ||
[![NPM version](https://img.shields.io/npm/v/popsicle.svg?style=flat)](https://npmjs.org/package/popsicle) | ||
[![NPM downloads](https://img.shields.io/npm/dm/popsicle.svg?style=flat)](https://npmjs.org/package/popsicle) | ||
[![Build status](https://img.shields.io/travis/blakeembrey/popsicle.svg?style=flat)](https://travis-ci.org/blakeembrey/popsicle) | ||
[![Test coverage](https://img.shields.io/coveralls/blakeembrey/popsicle.svg?style=flat)](https://coveralls.io/r/blakeembrey/popsicle?branch=master) | ||
[![Greenkeeper badge](https://badges.greenkeeper.io/blakeembrey/popsicle.svg)](https://greenkeeper.io/) | ||
[![Build status](https://img.shields.io/travis/serviejs/popsicle.svg?style=flat)](https://travis-ci.org/serviejs/popsicle) | ||
[![Test coverage](https://img.shields.io/coveralls/serviejs/popsicle.svg?style=flat)](https://coveralls.io/r/serviejs/popsicle?branch=master) | ||
> **Popsicle** is the easiest way to make HTTP requests - a consistent, intuitive and tiny API that works on node and the browser. 9.37 kB in browsers, after minification and gzipping, including dependencies (with `url` being the bulk of it). | ||
> Advanced HTTP requests in node.js and browsers, using [Servie](https://github.com/serviejs/servie). | ||
```js | ||
popsicle.get('/users.json') | ||
.then(function (res) { | ||
console.log(res.status) //=> 200 | ||
console.log(res.body) //=> { ... } | ||
console.log(res.headers) //=> { ... } | ||
}) | ||
``` | ||
## Installation | ||
@@ -29,345 +19,59 @@ | ||
```js | ||
const popsicle = require('popsicle') | ||
import { transport, request } from 'popsicle' | ||
popsicle.request({ | ||
method: 'POST', | ||
url: 'http://example.com/api/users', | ||
body: { | ||
username: 'blakeembrey', | ||
password: 'hunter2' | ||
}, | ||
headers: { | ||
'Content-Type': 'application/x-www-form-urlencoded' | ||
} | ||
}) | ||
.use(popsicle.plugins.parse('json')) | ||
.then(function (res) { | ||
console.log(res.status) // => 200 | ||
console.log(res.body) //=> { ... } | ||
console.log(res.get('Content-Type')) //=> 'application/json' | ||
}) | ||
const req = request('http://example.com') // Creates a `Request` instance. | ||
const res = await transport()(req) // Transports `Request` and returns `Response` instance. | ||
``` | ||
**Popsicle** is a promise-based HTTP request library designed for extensibility. Here's the functions you can import from `popsicle`: | ||
Thin wrapper to transport [Servie](https://github.com/serviejs/servie) HTTP request interfaces. | ||
* **request(options)** The default request handler (also `get`) | ||
* **defaults(options)** Create a new Popsicle instance with `defaults` | ||
* **form(obj?)** Cross-platform form data object | ||
* **plugins** Exposes the default plugins (Object) | ||
* **jar(store?)** Create a cookie jar instance for Node.js | ||
* **transport** Default transportation layer (Object) | ||
* **Request(options)** Constructor for the `Request` class | ||
* **Response(options)** Constructor for the `Response` class | ||
**P.S.** The default export from `popsicle` is `universal.js`. In TypeScript, this can cause trouble with types. Use specific imports such as `popsicle/dist/{browser,node,universal}` instead, depending on preference. | ||
### Request Options | ||
### Node.js Normalization | ||
* **url** _(string)_ The resource location. | ||
* **method** _(string)_ The HTTP request method (default: `"GET"`). | ||
* **headers** _(object)_ A map of header key to value (default: `{}`). | ||
* **query** _(object | string)_ A map or string to be appended to the URL as the query string. | ||
* **body** _(any)_ An object, string, form data, stream (node) or other to pass with the request. | ||
* **timeout** _(number)_ The number of milliseconds to wait before aborting the request (default: `Infinity`) | ||
* **use** _(array)_ An array of plugins to override the defaults (default: `[stringify(), headers()]`) | ||
* **transport** _(object)_ Set the transport layer (default: `createTransport({ type: 'text' })`) | ||
Normalizes some behavior that happens automatically in browsers (each normalization can be disabled). | ||
### Built-in Plugins | ||
* Default `User-Agent` insertion | ||
* Default unzip behaviour of `gzip` and `deflate` encoding | ||
* Follows HTTP redirects | ||
#### `stringify` (default) | ||
Automatically serialize the request body into a string (E.g. JSON, URL-encoded or multipart). | ||
#### `headers` (default) | ||
Sets up default headers for environments. For example, `Content-Length`, `User-Agent`, `Accept`, etc. | ||
#### `parse` | ||
Automatically parses allowed response type(s). | ||
* **json** Parse response as JSON | ||
* **urlencoded** Parse response as URL-encoded | ||
```js | ||
popsicle.get('/users') | ||
.use(popsicle.plugins.parse(['json', 'urlencoded'])) | ||
.then(() => ...) | ||
``` | ||
### Built-in Transports | ||
Popsicle provides two transports, one for node (using `{http,https}.request`) and one for browsers (using `XMLHttpRequest`). These transports have a number of "types" built-in for handling the response body. | ||
#### HTTP (node.js) | ||
* **text** Handle response as a string (default) | ||
* **document** `responseType === 'document'` (browsers) | ||
* **blob** `responseType === 'blob'` (browsers) | ||
* **arraybuffer** `responseType === 'arraybuffer'` (browsers) | ||
* **buffer** Handle response as a buffer (node.js) | ||
* **array** Handle response as an array of integers (node.js) | ||
* **uint8array** Handle the response as a `Uint8Array` (node.js) | ||
* **stream** Respond with the response body stream (node.js) | ||
* **unzip: boolean** Automatically unzip response bodies (default: `true`) | ||
* **follow: boolean** Automatically follow HTTP redirects (default: `true`) | ||
* **jar: CookieJar** An instance of a cookie jar (`jar()` from `node.js` import) | ||
* **maxRedirects: number** Override the number of redirects allowed (default: `5`) | ||
* **confirmRedirect: Function** Validate `307` and `308` redirects (default: `() => false`) | ||
* **rejectUnauthorized: boolean** Reject invalid SSL certificates (default: `true`) | ||
* **agent: http.Agent** Custom `http.request` agent | ||
* **ca: string | Buffer** A string, `Buffer` or array of strings or `Buffers` of trusted certificates in PEM format | ||
* **key: string | Buffer** Private key to use for SSL | ||
* **cert: string | Buffer** Public x509 certificate to use | ||
* **secureProtocol: string** Optional SSL method to use | ||
**Node transport options** | ||
#### XHR (browsers) | ||
* **type** Handle the response (default: `text`) | ||
* **unzip** Automatically unzip response bodies (default: `true`) | ||
* **jar** An instance of a cookie jar (`popsicle.jar()`) (default: `null`) | ||
* **agent** Custom HTTP pooling agent | ||
* **maxRedirects** Override the number of redirects allowed (default: `5`) | ||
* **maxBufferSize** The maximum size of the buffered response body (default: `2000000`) | ||
* **rejectUnauthorized** Reject invalid SSL certificates (default: `true`) | ||
* **confirmRedirect** Confirm redirects on `307` and `308` status codes (default: `() => false`) | ||
* **ca** A string, `Buffer` or array of strings or `Buffers` of trusted certificates in PEM format | ||
* **key** Private key to use for SSL (default: `null`) | ||
* **cert** Public x509 certificate to use (default: `null`) | ||
* **type: XMLHttpRequestResponseType** Handle the XHR response (default: `text`) | ||
* **withCredentials: boolean** Send cookies with CORS requests (default: `false`) | ||
* **overrideMimeType: string** Override the XHR response MIME type | ||
**Browser transport options** | ||
* **type** Handle the XHR response (default: `text`) | ||
* **withCredentials** Send cookies with CORS requests (default: `false`) | ||
* **overrideMimeType** Override the XHR response MIME type | ||
### Short-hand Methods | ||
Common methods have a short hand exported (created using `defaults({ method })`). | ||
```js | ||
popsicle.get('http://example.com/api/users') | ||
popsicle.post('http://example.com/api/users') | ||
popsicle.put('http://example.com/api/users') | ||
popsicle.patch('http://example.com/api/users') | ||
popsicle.del('http://example.com/api/users') | ||
``` | ||
### Default Instances | ||
Create a new Popsicle instance with defaults pre-populated. Handy for a common cookie jar or transport to be used. | ||
```js | ||
const cookiePopsicle = popsicle.defaults({ | ||
transport: popsicle.createTransport({ | ||
jar: popsicle.jar() | ||
}) | ||
}) | ||
``` | ||
### Automatically Stringify Request Body | ||
Popsicle will automatically serialize the request body using the `stringify` plugin. If an object is supplied, it will automatically be stringified as JSON unless the `Content-Type` was set otherwise. If the `Content-Type` is `application/json`, `multipart/form-data` or `application/x-www-form-urlencoded`, it will be automatically serialized accordingly. | ||
```js | ||
popsicle.get({ | ||
url: 'http://example.com/api/users', | ||
body: { | ||
username: 'blakeembrey' | ||
}, | ||
headers: { | ||
'Content-Type': 'application/x-www-form-urlencoded' | ||
} | ||
}) | ||
``` | ||
### Multipart Request Bodies | ||
You can manually create a `FormData` instance by calling `popsicle.form`. When you pass a form data instance as the body, it'll automatically set the correct `Content-Type` - complete with boundary. | ||
```js | ||
const form = popsicle.form({ | ||
username: 'blakeembrey', | ||
profileImage: fs.createReadStream('image.png') | ||
}) | ||
form.append('x', 'y') | ||
popsicle.post({ | ||
url: '/users', | ||
body: form | ||
}) | ||
``` | ||
### Cookie Jar (Node only) | ||
You can create a reusable cookie jar instance for requests by calling `popsicle.jar`. | ||
```js | ||
const jar = popsicle.jar() | ||
popsicle.request({ | ||
method: 'post', | ||
url: '/users', | ||
transport: popsicle.createTransport({ | ||
jar: jar | ||
}) | ||
}) | ||
``` | ||
### Request Class | ||
Calling any of the request functions will return an instance of `Request`. | ||
* **method** _(string)_ The request method. | ||
* **timeout** _(number)_ Configured request timeout. | ||
* **body** _(any)_ The request payload. | ||
* **transport** _(object)_ The transportation layer. | ||
* **events** _(object)_ A map of configured event listeners. | ||
* **middleware** _(array)_ The list of configured middleware. | ||
* **opened** _(boolean)_ A flag indicating the transport was started. | ||
* **aborted** _(boolean)_ A flag indicating the request was aborted before finishing. | ||
* **uploaded** _(number)_ The percentage of upload complete (between 0 and 1). | ||
* **downloaded** _(number)_ The percentage of download complete (between 0 and 1). | ||
* **uploadedBytes** (number)_ Number of bytes uploaded. | ||
* **downloadedBytes** _(number)_ Number of bytes downloaded. | ||
* **uploadLength** _(number)_ Known size of total upload bytes. | ||
* **downloadLength** _(number)_ Known size of total download bytes. | ||
* **error(message, code, original?)** Create a `PopsicleError` instance. | ||
* **then(onFulfilled?, onRejected?)** Promise interface. | ||
* **catch(onRejected)** Promise interface. | ||
* **exec(cb)** Callback interface. | ||
* **toOptions()** Return a new object representing the request options. | ||
* **toJSON()** A JSON friendly representation of the request. | ||
* **clone()** Return a new instance of `Request` from `toOptions()`. | ||
* **use(middleware)** Append middleware to the current request. | ||
* **on(event, fn)** Attach an event listener. | ||
* **off(event, fn)** Detach an event listener. | ||
* **once(event, fn)** Attach an event listener that automatically detaches after the first execution. | ||
* **emit(event, ...args)** Emit an event (mostly internal use). | ||
* **abort()** Abort the current request by emitting the `abort` event. | ||
**P.S.** When cloning a request instance, the current middleware and events are copied. This allows event tricks like `abort()` to also abort cloned request instances (e.g. in the case where working with request retries, and aborting should still work on re-attempts). | ||
#### Promises | ||
Promises are the most expressive interface. Just chain using `Request#then` or `Request#catch` and continue. | ||
```js | ||
popsicle.get('/users') | ||
.then(function (res) { | ||
// Success! | ||
}) | ||
.catch(function (err) { | ||
// Something broke. | ||
}) | ||
``` | ||
If you live on the edge, try with generators ([co](https://www.npmjs.com/package/co)) or ES7 `async`/`await`. | ||
```js | ||
co(function * () { | ||
const users = yield popsicle.get('/users') | ||
}) | ||
async function () { | ||
const users = await popsicle.get('/users') | ||
} | ||
``` | ||
#### Callbacks | ||
For tooling that expects node-style callbacks, you can use `Request#exec`. This accepts a single function to call when the response is complete. | ||
```js | ||
popsicle.get('/users') | ||
.exec(function (err, res) { | ||
if (err) { | ||
// Something broke. | ||
} | ||
// Success! | ||
}) | ||
``` | ||
### Aborting | ||
All requests can be aborted before or during execution by calling `Request#abort`. | ||
```js | ||
const request = popsicle.get('http://example.com') | ||
setTimeout(function () { | ||
request.abort() | ||
}, 100) | ||
request.catch(function (err) { | ||
console.log(err) //=> { message: 'Request aborted', code: 'EABORTED' } | ||
}) | ||
``` | ||
#### Progress | ||
The request object can be used to check progress at any time. | ||
* **request.uploadedBytes** Current upload size in bytes | ||
* **request.uploadLength** Total upload size in bytes | ||
* **request.uploaded** Total uploaded as a percentage | ||
* **request.downloadedBytes** Current download size in bytes | ||
* **request.downloadLength** Total download size in bytes | ||
* **request.downloaded** Total downloaded as a percentage | ||
* **request.completed** Total uploaded and downloaded as a percentage | ||
All percentage properties (`request.uploaded`, `request.downloaded`, `request.completed`) are a number between `0` and `1`. Aborting the request will emit a progress event, if the request had started. | ||
```js | ||
const request = popsicle.get('http://example.com') | ||
request.uploaded //=> 0 | ||
request.downloaded //=> 0 | ||
request.on('progress', function () { | ||
console.log(request) //=> { uploaded: 1, downloaded: 0, completed: 0.5, aborted: false } | ||
}) | ||
request.then(function (response) { | ||
console.log(request.downloaded) //=> 1 | ||
}) | ||
``` | ||
#### Errors | ||
All response methods can return an error. Errors have a `popsicle` property set to the request object and a `code` string. The built-in codes are documented below, but custom errors can be created using `request.error(message, code, cause)`. | ||
Transports can return an error. Errors have a `request` property set to the request object and a `code` string. The built-in codes are documented below: | ||
* **EABORT** Request has been aborted by user | ||
* **EUNAVAILABLE** Unable to connect to the remote URL | ||
* **EINVALID** Request URL is invalid | ||
* **ETIMEOUT** Request has exceeded the allowed timeout | ||
* **ESTRINGIFY** Request body threw an error during stringification plugin | ||
* **EPARSE** Response body threw an error during parse | ||
* **EMAXREDIRECTS** Maximum number of redirects exceeded (Node only) | ||
* **EBODY** Unable to handle request body (Node only) | ||
* **EBLOCKED** The request was blocked (HTTPS -> HTTP) (Browsers only) | ||
* **ECSP** Request violates the documents Content Security Policy (Browsers only) | ||
* **ETYPE** Invalid transport type | ||
* **EINVALID** Request URL is invalid (browsers) | ||
* **EMAXREDIRECTS** Maximum number of redirects exceeded (node.js) | ||
* **EBLOCKED** The request was blocked (HTTPS -> HTTP) (browsers) | ||
* **ECSP** Request violates the documents Content Security Policy (browsers) | ||
* **ETYPE** Invalid transport type (browsers) | ||
### Response Class | ||
Every response will give a `Response` instance on success. T | ||
* **status** The HTTP response status code | ||
* **body** The response body from the transport layer (usually text or a stream) | ||
* **headers** An object of lower-cased keys to header values | ||
* **url** The final response URL (after redirects) | ||
* **statusType()** Return an integer with the HTTP status type (E.g. `200 -> 2`) | ||
* **get(key)** Retrieve a HTTP header using a case-insensitive key | ||
* **name(key)** Retrieve the original HTTP header name using a case-insensitive key | ||
* **type()** Return the response type (E.g. `application/json`) | ||
### Plugins | ||
Plugins can be set as an array with the initial options (which overrides default plugins), or they can be used via `Request#use`. | ||
_Coming back soon._ | ||
#### External Plugins | ||
### Helpful Utilities | ||
* [Server](https://github.com/blakeembrey/popsicle-server) - Automatically mount a server on an available for the request (helpful for testing a la `supertest`) | ||
* [Status](https://github.com/blakeembrey/popsicle-status) - Reject responses on HTTP failure status codes | ||
* [No Cache](https://github.com/blakeembrey/popsicle-no-cache) - Prevent caching of HTTP requests in browsers | ||
* [Basic Auth](https://github.com/blakeembrey/popsicle-basic-auth) - Add a basic authentication header to each request | ||
* [Prefix](https://github.com/blakeembrey/popsicle-prefix) - Prefix all HTTP requests | ||
* [Resolve](https://github.com/blakeembrey/popsicle-resolve) - Resolve all HTTP requests against a base URL | ||
* [Limit](https://github.com/blakeembrey/popsicle-limit) - Transparently handle API rate limits by grouping requests | ||
* [Group](https://github.com/blakeembrey/popsicle-group) - Group requests and perform operations on them all at once | ||
* [Proxy Agent](https://github.com/blakeembrey/popsicle-proxy-agent) - Enable HTTP(s) proxying under node (with environment variable support) | ||
* [Retry](https://github.com/blakeembrey/popsicle-retry) - Retry a HTTP request on network error or server error | ||
* [Rewrite](https://github.com/lbovet/popsicle-rewrite) - Rewrite request URLs dynamically | ||
* [Cache](https://github.com/blakeembrey/popsicle-cache) - Enable caching for HTTP requests into a store (E.g. filesystem) | ||
#### Helpful Utilities | ||
* [`throat`](https://github.com/ForbesLindesay/throat) - Throttle promise-based functions with concurrency support | ||
@@ -377,48 +81,22 @@ * [`is-browser`](https://github.com/ForbesLindesay/is-browser) - Check if your in a browser environment (E.g. Browserify, Webpack) | ||
#### Creating Plugins | ||
### Creating Plugins | ||
Plugins must be a function that accept config and return a middleware function. For example, here's a basic URL prefix plugin. | ||
See [Throwback](https://github.com/serviejs/throwback#usage) for more information: | ||
```js | ||
function prefix (url) { | ||
return function (self, next) { | ||
self.url = url + self.url | ||
return next() | ||
} | ||
} | ||
popsicle.request('/user') | ||
.use(prefix('http://example.com')) | ||
.then(function (response) { | ||
console.log(response.url) //=> "http://example.com/user" | ||
}) | ||
```ts | ||
type Plugin = (req: Request, next: (req?: Request) => Promise<Response>) => Promise<Response> | ||
``` | ||
Middleware functions accept two arguments - the current request and a function to proceed to the next middleware function (a la Koa `2.x`). | ||
**P.S.** The middleware array is exposed on `request.middleware`, which allows you to clone requests and tweak middleware - for example, using `request.middleware.slice(request.middleware.indexOf(currentFn))`. This is useful, as the pre and post steps of previous middleware attach before `currentFn` is executed. | ||
### Transportation Layers | ||
Creating a custom transportation layer is just a matter creating an object with `open`, `abort` and `use` options set. The open method should set any request information required between called as `request._raw`. Abort must abort the current request instance, while `open` must **always** resolve to a promise. You can set `use` to an empty array if no plugins should be used by default. However, it's recommended you keep `use` set to the defaults, or as close as possible using your transport layer. | ||
See [Servie](https://github.com/serviejs/servie#implementers) for more information: | ||
## JavaScript | ||
```ts | ||
type Transport = (req: Request) => Promise<Response> | ||
``` | ||
This module is designed for ES5 environments, but requires two ES2015 polyfills to work: | ||
1. `Promise` - `popsicle` is designed heavily with promises in mind | ||
2. `Object.assign` - used internally for object cloning | ||
## TypeScript | ||
This project is written using [TypeScript](https://github.com/Microsoft/TypeScript) and publishes the typings to NPM alongside the package. | ||
This project is written using [TypeScript](https://github.com/Microsoft/TypeScript) and publishes the types to NPM alongside the package. | ||
## Development | ||
Install dependencies and run the test runners (node and Electron using Tape). | ||
``` | ||
npm install && npm test | ||
``` | ||
## Related Projects | ||
@@ -428,3 +106,3 @@ | ||
* [Fetch](https://github.com/github/fetch) - Browser polyfill for promise-based HTTP requests | ||
* [Axios](https://github.com/mzabriskie/axios) - HTTP request API based on Angular's $http service | ||
* [Axios](https://github.com/mzabriskie/axios) - HTTP request API based on Angular's `$http` service | ||
@@ -431,0 +109,0 @@ ## License |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
7
13
3
106849
31
903
1
109
3
+ Added@types/pump@^1.0.1
+ Addedpump@^3.0.0
+ Addedservie@^3.2.2
+ Addedthrowback@^3.0.0
+ Added@types/pump@1.1.3(transitive)
+ Addedbyte-length@1.0.2(transitive)
+ Addedend-of-stream@1.4.4(transitive)
+ Addedmake-error-cause@2.3.0(transitive)
+ Addedonce@1.4.0(transitive)
+ Addedpump@3.0.2(transitive)
+ Addedservie@3.2.4(transitive)
+ Addedthrowback@3.0.0(transitive)
+ Addedwrappy@1.0.2(transitive)
- Removed@types/concat-stream@^1.6.0
- Removed@types/form-data@0.0.33
- Removed@types/methods@^1.1.0
- Removedconcat-stream@^1.4.7
- Removedform-data@^2.0.0
- Removed@types/concat-stream@1.6.1(transitive)
- Removed@types/form-data@0.0.33(transitive)
- Removed@types/methods@1.1.4(transitive)
- Removedasynckit@0.4.0(transitive)
- Removedbuffer-from@1.1.2(transitive)
- Removedcombined-stream@1.0.8(transitive)
- Removedconcat-stream@1.6.2(transitive)
- Removedcore-util-is@1.0.3(transitive)
- Removeddelayed-stream@1.0.0(transitive)
- Removedform-data@2.5.2(transitive)
- Removedinherits@2.0.4(transitive)
- Removedisarray@1.0.0(transitive)
- Removedmake-error-cause@1.2.2(transitive)
- Removedmime-db@1.52.0(transitive)
- Removedmime-types@2.1.35(transitive)
- Removedprocess-nextick-args@2.0.1(transitive)
- Removedreadable-stream@2.3.8(transitive)
- Removedsafe-buffer@5.1.25.2.1(transitive)
- Removedstring_decoder@1.1.1(transitive)
- Removedtypedarray@0.0.6(transitive)
- Removedutil-deprecate@1.0.2(transitive)
Updated@types/tough-cookie@^2.3.2
Updatedmake-error-cause@^2.1.0