Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

popsicle

Package Overview
Dependencies
Maintainers
1
Versions
99
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

popsicle - npm Package Compare versions

Comparing version 0.1.1 to 0.2.0

2

bower.json
{
"name": "popsicle",
"main": "popsicle.js",
"version": "0.1.1",
"version": "0.2.0",
"homepage": "https://github.com/blakeembrey/popsicle",

@@ -6,0 +6,0 @@ "authors": [

{
"name": "popsicle",
"version": "0.1.1",
"version": "0.2.0",
"description": "Simple HTTP requests for node and the browser",

@@ -5,0 +5,0 @@ "main": "popsicle.js",

@@ -41,2 +41,45 @@ (function (root) {

/**
* Create a function to set progress properties on a request instance.
*
* @param {String} property
* @return {Function}
*/
function setProgress (property) {
return function (num) {
if (this[property] === num) {
return;
}
this[property] = num;
this._emitProgress();
};
}
/**
* Calculate the length of the current request as a percent of the total.
*
* @param {Number} length
* @param {Number} total
* @return {Number}
*/
function calc (length, total) {
if (total == null) {
return 0;
}
return total === length ? 1 : length / total;
}
/**
* Return zero if the number is `Infinity`.
*
* @param {Number} num
* @return {Number}
*/
function infinity (num) {
return num === Infinity ? 0 : num;
}
/**
* Create a stream error instance.

@@ -473,15 +516,53 @@ *

* @param {Request} self
* @param {request} request
*/
function trackResponseProgress (self) {
self._request.on('response', function (res) {
self._responseTotal = Number(res.headers['content-length']);
});
function trackRequestProgress (self, request) {
var write = request.write;
self._request.on('data', function (data) {
self._responseLength += data.length;
});
self._request = request;
self._request.on('end', function () {
self._responseTotal = self._responseLength;
});
// Override `Request.prototype.write` to track written data.
request.write = function (data) {
self._setRequestLength(self._requestLength + data.length);
return write.apply(request, arguments);
};
function onRequestEnd () {
self._setRequestTotal(self._requestLength);
}
function onRequest () {
self._setRequestTotal(Number(request.headers['content-length']) || 0);
request.req.on('finish', onRequestEnd);
}
function onResponse (response) {
self._responseTotal = Number(response.headers['content-length']);
}
function onResponseData (data) {
self._setResponseLength(self._responseLength + data.length);
}
function onResponseEnd () {
self._setResponseTotal(self._responseLength);
removeListeners();
self._completed();
}
function removeListeners () {
request.removeListener('request', onRequest);
request.removeListener('response', onResponse);
request.removeListener('data', onResponseData);
request.removeListener('end', onResponseEnd);
request.req.removeListener('finish', onRequestEnd);
}
request.on('request', onRequest);
request.on('response', onResponse);
request.on('data', onResponseData);
request.on('end', onResponseEnd);
request.on('error', removeListeners);
}

@@ -702,4 +783,6 @@

// Request options.
this.body = options.body;
this.url = options.url;
this.method = (options.method || 'GET').toUpperCase();
this.query = assign({}, options.query);

@@ -710,6 +793,5 @@ this.timeout = options.timeout;

// Default to GET and uppercase anything else.
this.method = (options.method || 'GET').toUpperCase();
// Initialize the response length.
// Progress properties.
this._requestTotal = null;
this._requestLength = 0;
this._responseTotal = null;

@@ -721,2 +803,6 @@ this._responseLength = 0;

// Request state.
this.aborted = false;
this.completed = false;
// Parse query strings already set.

@@ -754,16 +840,96 @@ var queryIndex = options.url.indexOf('?');

/**
* Check how far something has been downloaded.
* Check how far the request has been uploaded.
*
* @return {Number}
*/
Request.prototype.uploaded = function () {
return calc(this._requestLength, this._requestTotal);
};
/**
* Check how far the request has been downloaded.
*
* @return {Number}
*/
Request.prototype.downloaded = function () {
if (this._responseTotal == null) {
return 0;
return calc(this._responseLength, this._responseTotal);
};
/**
* Track request completion progress.
*
* @param {Function} fn
* @return {Request}
*/
Request.prototype.progress = function (fn) {
if (this.completed) {
return this;
}
return this._responseLength / this._responseTotal;
this._progressFns = this._progressFns || [];
this._progressFns.push(fn);
return this;
};
/**
* Set various progress properties.
*
* @private
* @param {Number} num
*/
Request.prototype._setRequestTotal = setProgress('_requestTotal');
Request.prototype._setRequestLength = setProgress('_requestLength');
Request.prototype._setResponseTotal = setProgress('_responseTotal');
Request.prototype._setResponseLength = setProgress('_responseLength');
/**
* Emit a request progress event (upload or download).
*/
Request.prototype._emitProgress = function () {
var fns = this._progressFns;
if (!fns) {
return;
}
var self = this;
var aborted = this.aborted;
var uploaded = this.uploaded();
var downloaded = this.downloaded();
var total = (infinity(uploaded) + infinity(downloaded)) / 2;
function emitProgress () {
try {
fns.forEach(function (fn) {
// Emit new progress object every iteration to avoid issues if a
// function mutates the object.
fn({
uploaded: uploaded,
downloaded: downloaded,
total: total,
aborted: aborted
});
});
} catch (e) {
self._errored(e);
}
}
emitProgress();
};
/**
* Complete the request.
*/
Request.prototype._completed = function () {
this.completed = true;
delete this._progressFns;
};
/**
* Allows request plugins.
*
* @return {Request}
*/

@@ -777,5 +943,3 @@ Request.prototype.use = function (fn) {

/**
* Setup and create the request instance.
*
* @return {Promise}
* Setup the request instance (promises and streams).
*/

@@ -812,3 +976,2 @@ Request.prototype._setup = function () {

// Promises buffer and parse the full response.
this._promise = this._create().then(parseResponse);

@@ -832,3 +995,12 @@ }

this.aborted = true;
// Reset progress and complete progress events.
this._requestTotal = 0;
this._requestLength = 0;
this._responseTotal = 0;
this._responseLength = 0;
this._emitProgress();
this._abort();
this._completed();
clearTimeout(this._timer);

@@ -840,2 +1012,12 @@

/**
* Trigger a request-related error that should break requests.
*
* @param {Error} err
*/
Request.prototype._errored = function (err) {
this._error = err;
this.abort();
};
/**
* Create a popsicle error instance.

@@ -906,5 +1088,10 @@ *

self._request = request(opts, function (err, response) {
var req = request(opts, function (err, response) {
if (err) {
return reject(unavailableError(self));
// Node.js core error (ECONNRESET, EPIPE).
if (typeof err.code === 'string') {
return reject(unavailableError(self));
}
return reject(err);
}

@@ -923,7 +1110,11 @@

self._request.on('abort', function () {
req.on('abort', function () {
if (self._error) {
return reject(self._error);
}
return reject(abortError(self));
});
trackResponseProgress(self);
trackRequestProgress(self, req);
});

@@ -978,4 +1169,5 @@ };

Request.prototype._create = function ( ) {
var self = this;
var url = this.fullUrl();
var self = this;
var url = self.fullUrl();
var method = self.method;

@@ -998,3 +1190,3 @@ return new Promise(function (resolve, reject) {

if (xhr.readyState === 3) {
self._responseLength = xhr.responseText.length;
self._setResponseLength(xhr.responseText.length);
}

@@ -1005,4 +1197,13 @@

// in case the content length header was not available before.
self._responseTotal = self._responseLength;
self._setResponseTotal(self._responseLength);
// Clean up listeners.
delete xhr.onreadystatechange;
delete xhr.upload.onprogress;
self._completed();
if (self._error) {
return reject(self._error);
}
// Handle the aborted state internally, PhantomJS doesn't reset

@@ -1030,5 +1231,18 @@ // `xhr.status` to zero on abort.

// No upload will occur with these requests.
if (method === 'GET' || method === 'HEAD' || !xhr.upload) {
xhr.upload = {};
self._setRequestTotal(0);
self._setRequestLength(0);
} else {
xhr.upload.onprogress = function (e) {
self._setRequestTotal(e.total);
self._setRequestLength(e.loaded);
};
}
// XHR can fail to open when site CSP is set.
try {
xhr.open(self.method, url);
xhr.open(method, url);
} catch (e) {

@@ -1035,0 +1249,0 @@ return reject(cspError(self, e));

@@ -6,3 +6,3 @@ # ![Popsicle](https://cdn.rawgit.com/blakeembrey/popsicle/master/logo.svg)

**Popsicle** is designed to be easiest way for making HTTP requests, offering a consistant and intuitive API that works on both node and the browser.
**Popsicle** is designed to be easiest way for making HTTP requests, offering a consistent and intuitive API that works on both node and the browser.

@@ -20,2 +20,3 @@ ```javascript

npm install popsicle --save
bower install popsicle --save
```

@@ -26,3 +27,4 @@

```bash
npm install es6-promise
npm install es6-promise --save
bower install es6-promise --save
```

@@ -123,11 +125,22 @@

#### Download Progress
#### Progress
The request object can also be used to check progress at any time. However, the URL must have responded with a `Content-Length` header for this to work properly.
The request object can also be used to check progress at any time.
`Request#uploaded` can be used to check the upload progress (between `0` and `1`) of the current request. If the request is being streamed (node), the length may incorrectly return `Infinity` to signify the number can't be calculated.
`Request#downloaded` can be used to check the download progress (between `0` and `1`) of the current request instance. If the response did not respond with a `Content-Length` header this can not be calculated properly and will return `Infinity`.
`Request#progress(fn)` can be used to register a progress event listener. It'll emit on upload and download progress events, which makes it useful for SPA progress bars. The function is called with an object (`{ uploaded: 0, downloaded: 0, total: 0, aborted: false }`).
```javascript
var req = request('http://example.com');
req.uploaded(); //=> 0
req.downloaded(); //=> 0
req.progress(function (e) {
console.log(e); //=> { uploaded: 1, downloaded: 0, total: 0.5, aborted: false }
});
req.then(function (res) {

@@ -173,4 +186,6 @@ console.log(req.downloaded()); //=> 1

On node, you can also chain using streams. However, the API is currently incomplete.
**Incomplete: Emits incorrect errors**
On node, you can also chain using streams.
```javascript

@@ -209,7 +224,10 @@ request('/users')

A simple plugin interface is exposed through `Request#use`.
A simple plugin interface is exposed through `Request#use` and promises.
#### Existing Plugins
None yet. Will you be the first?
* [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
* [Basic Auth](https://github.com/blakeembrey/popsicle-basic-auth) - Add basic authentication to requests
* [Prefix](https://github.com/blakeembrey/popsicle-prefix) - Automatically prefix all HTTP requests

@@ -242,2 +260,8 @@ #### Using Plugins

## Related Projects
* [Superagent](https://github.com/visionmedia/superagent) - HTTP requests on node and browser
* [Fetch](https://github.com/github/fetch) - Browser polyfill for promise-based HTTP requests
* [Axios](https://github.com/mzabriskie/axios) - Similar API based on Angular's $http service
## License

@@ -244,0 +268,0 @@

@@ -329,2 +329,4 @@ var REMOTE_URL = 'http://localhost:4567';

expect(err.message).to.equal('Request aborted');
expect(err.abort).to.be.true;
expect(err.popsicle).to.be.an.instanceOf(popsicle.Request);
})

@@ -337,24 +339,106 @@ .then(function () {

describe('download progress', function () {
it('should check download progress', function () {
var req = popsicle(REMOTE_URL + '/download');
var assert = false;
describe('progress', function () {
describe('download', function () {
it('should check download progress', function () {
var req = popsicle(REMOTE_URL + '/download');
var assert = false;
// Before the request has started.
expect(req.downloaded()).to.equal(0);
// Before the request has started.
expect(req.downloaded()).to.equal(0);
// Check halfway into the response.
setTimeout(function () {
assert = req.downloaded() === 0.5;
}, 100);
// Check halfway into the response.
setTimeout(function () {
assert = req.downloaded() === 0.5;
}, 100);
return req.then(function () {
// Can't consistently test progress in browsers.
if (typeof window === 'undefined') {
expect(assert).to.be.true;
}
return req
.then(function () {
// Can't consistently test progress in browsers.
if (typeof window === 'undefined') {
expect(assert).to.be.true;
}
expect(req.downloaded()).to.equal(1);
expect(req.downloaded()).to.equal(1);
});
});
});
describe('event', function () {
it('should emit progress events', function () {
var req = popsicle({
url: REMOTE_URL + '/echo',
body: EXAMPLE_BODY,
method: 'POST'
});
var asserted = 0;
var expected = 0;
req.progress(function (e) {
// Fix for PhantomJS tests (doesn't return `Content-Length` header).
if (req._xhr && e.downloaded === Infinity) {
console.warn('Browser does not support "Content-Length" header');
return;
}
expect(e.total).to.equal(expected);
asserted += 1;
expected += 0.5;
});
return req
.then(function (res) {
expect(asserted).to.equal(3);
expect(res.body).to.deep.equal(EXAMPLE_BODY);
});
});
it('should error when the progress callback errors', function () {
var req = popsicle(REMOTE_URL + '/echo');
var errored = false;
req.progress(function () {
throw new Error('Testing');
});
return req
.catch(function (err) {
errored = true;
expect(err.message).to.equal('Testing');
expect(err.popsicle).to.not.exist;
})
.then(function () {
expect(errored).to.be.true;
});
});
it('should emit a final event on abort', function () {
var req = popsicle(REMOTE_URL + '/echo');
var errored = false;
var progressed = 0;
req.progress(function (e) {
expect(e.total).to.equal(1);
expect(e.aborted).to.be.true;
progressed++;
});
req.abort();
return req
.catch(function (err) {
errored = true;
expect(err.abort).to.be.true;
})
.then(function () {
expect(errored).to.be.true;
expect(progressed).to.equal(1);
});
});
});
});

@@ -361,0 +445,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc