xhr-shaper
Advanced tools
Comparing version 0.2.1 to 1.0.0
256
index.js
'use strict'; | ||
// This will shim the XHR object in your window and add some custom functionnality on top in the Shaper object | ||
var BaseXHR = require('./src/base-xhr'); | ||
var objectMirrors = require('./src/object-mirrors'); | ||
var XHRShaper = require('./src/shaper'); | ||
var XMLHttpRequestShim = require('./src/shim'); | ||
var XHRShaper = function() { | ||
var _maxBandwidth = Infinity; | ||
var _minLatency = 0; | ||
var _minProgressEvents = 0; | ||
var _randomness = 0; | ||
Object.defineProperty(this, "maxBandwidth", { | ||
get: function() { | ||
return Math.min(_maxBandwidth, XHRShaper.maxBandwidth); | ||
}, | ||
set: function(val) { _maxBandwidth = val } | ||
}); | ||
Object.defineProperty(this, "minLatency", { | ||
get: function() { | ||
return Math.max(_minLatency, XHRShaper.minLatency); | ||
}, | ||
set: function(val) { _minLatency = val } | ||
}); | ||
Object.defineProperty(this, "minProgressEvents", { | ||
get: function() { | ||
return Math.max(_minProgressEvents, XHRShaper.minProgressEvents); | ||
}, | ||
set: function(val) { _minProgressEvents = val } | ||
}); | ||
Object.defineProperty(this, "randomness", { | ||
get: function() { | ||
return Math.max(_randomness, XHRShaper.randomness); | ||
}, | ||
set: function(val) { _randomness = val } | ||
}); | ||
}; | ||
XHRShaper.maxBandwidth = Infinity; | ||
XHRShaper.minLatency = 0; | ||
XHRShaper.minProgressEvents = 0; | ||
XHRShaper.randomness = 0; | ||
var WindowXHR = window.XMLHttpRequest; | ||
function mirrorReadOnlyProp(target, source, prop) { | ||
Object.defineProperty(target, prop, { | ||
get: function() { | ||
return source[prop]; | ||
} | ||
}); | ||
// Export all our stuff via module if we are one | ||
if (typeof module !== 'undefined') { | ||
module.exports = { | ||
BaseXHR: BaseXHR, | ||
XMLHttpRequest: XMLHttpRequestShim, | ||
useGlobal: useGlobal, | ||
objectMirrors: objectMirrors | ||
}; | ||
} | ||
function mirrorRwProp(target, source, prop) { | ||
Object.defineProperty(target, prop, { | ||
get: function() { | ||
return source[prop]; | ||
}, | ||
set: function(val) { | ||
source[prop] = val; | ||
} | ||
}); | ||
function useGlobal() { | ||
// Shim window/global XHR | ||
var global = window || global; | ||
if (typeof global !== 'undefined') { | ||
// Overload native window constructor | ||
global.XMLHttpRequest = XMLHttpRequestShim; | ||
} | ||
} | ||
function mirrorFunc(target, source, func) { | ||
//if (!source[func]) return; // in case someone doesn't implement the full standard bind() will fail here | ||
target[func] = source[func].bind(source); | ||
} | ||
var XMLHttpRequest = function() { | ||
var xhr = new WindowXHR(); | ||
var shaper = new XHRShaper(); | ||
var _onreadystatechange, _onprogress, _onloadend; | ||
var _this = { | ||
shaper: shaper | ||
}; | ||
var openedTs, headersTs, loadingTs, doneTs; | ||
var loaded = 0, total; | ||
var currentBitrateKpbs; | ||
var progressEvents = []; | ||
var progressTimer; | ||
var lastProgressEvent = false; | ||
var loadEndEvent; | ||
xhr.onloadend = function(event) { | ||
loadEndEvent = event; | ||
if (_onloadend && xhr.readyState === 4) { | ||
_onloadend(event); | ||
} | ||
}; | ||
xhr.onreadystatechange = function(event) { | ||
function triggerStateChange(e) { | ||
if (_onreadystatechange) { | ||
_onreadystatechange(e); | ||
} | ||
} | ||
switch (xhr.readyState) { | ||
case 0: // UNSENT | ||
triggerStateChange(event); | ||
break; | ||
case 1: // OPENED | ||
openedTs = Date.now(); | ||
triggerStateChange(event); | ||
break; | ||
case 2: // HEADERS_RECEIVE | ||
headersTs = Date.now(); | ||
triggerStateChange(event); | ||
break; | ||
case 3: // LOADING | ||
loadingTs = Date.now(); | ||
triggerStateChange(event); | ||
break; | ||
case 4: // DONE | ||
var delay1 = 0, delay2 = 0; | ||
doneTs = Date.now(); | ||
var latency = doneTs - openedTs; | ||
if (latency < shaper.minLatency) { | ||
delay1 = shaper.minLatency - latency; | ||
} | ||
if (currentBitrateKpbs > shaper.maxBandwidth) { | ||
delay2 = (currentBitrateKpbs / shaper.maxBandwidth) * latency - latency; | ||
} | ||
if (delay1 || delay2) { | ||
setTimeout(function() { | ||
if (loaded === total && !lastProgressEvent) { | ||
clearTimeout(progressTimer); | ||
_onprogress(progressEvents[progressEvents.length-1]); | ||
} | ||
triggerStateChange(event); | ||
if (loadEndEvent && _onloadend) { | ||
_onloadend(loadEndEvent); | ||
} | ||
}, Math.max(delay1, delay2)); | ||
break; | ||
} | ||
triggerStateChange(event); | ||
break; | ||
} | ||
}; | ||
xhr.onprogress = function(event) { | ||
function triggerProgress(e) { | ||
if (loaded === total) { | ||
lastProgressEvent = true; | ||
} | ||
if (_onprogress) { | ||
_onprogress(e); | ||
} | ||
} | ||
var now = Date.now(); | ||
var duration = now - openedTs; | ||
var delay; | ||
loaded = event.loaded; | ||
total = event.total; | ||
currentBitrateKpbs = 8 * loaded / duration; // kbps | ||
//console.log('current bitrate: ' + Math.round(currentBitrateKpbs) + ' kbps'); | ||
if (currentBitrateKpbs > shaper.maxBandwidth) { | ||
delay = (currentBitrateKpbs / shaper.maxBandwidth) * duration - duration; | ||
progressEvents.push(event); | ||
//console.log('delaying progress event by ' + Math.round(delay) + ' ms'); | ||
progressTimer = setTimeout(function() { | ||
triggerProgress(event); | ||
}, delay); | ||
return; | ||
} | ||
triggerProgress(event); | ||
}; | ||
Object.defineProperty(_this, "onreadystatechange", { | ||
get: function() { | ||
return xhr.onreadystatechange; | ||
}, | ||
set: function(handler) { | ||
_onreadystatechange = handler; | ||
} | ||
}); | ||
Object.defineProperty(_this, "onprogress", { | ||
get: function() { | ||
return xhr.onprogress; | ||
}, | ||
set: function(handler) { | ||
_onprogress = handler; | ||
} | ||
}); | ||
Object.defineProperty(_this, "onloadend", { | ||
get: function() { | ||
return xhr.onloadend; | ||
}, | ||
set: function(handler) { | ||
_onloadend = handler; | ||
} | ||
}); | ||
mirrorRwProp(_this, xhr, "responseType"); | ||
mirrorRwProp(_this, xhr, "timeout"); | ||
mirrorRwProp(_this, xhr, "withCredentials"); | ||
mirrorReadOnlyProp(_this, xhr, "readyState"); | ||
mirrorReadOnlyProp(_this, xhr, "response"); | ||
mirrorReadOnlyProp(_this, xhr, "responseText"); | ||
mirrorReadOnlyProp(_this, xhr, "responseURL"); | ||
mirrorReadOnlyProp(_this, xhr, "responseXML"); | ||
mirrorReadOnlyProp(_this, xhr, "status"); | ||
mirrorReadOnlyProp(_this, xhr, "statusText"); | ||
mirrorReadOnlyProp(_this, xhr, "upload"); | ||
mirrorFunc(_this, xhr, "abort"); | ||
mirrorFunc(_this, xhr, "open"); | ||
mirrorFunc(_this, xhr, "send"); | ||
mirrorFunc(_this, xhr, "setRequestHeader"); | ||
mirrorFunc(_this, xhr, "getResponseHeader"); | ||
mirrorFunc(_this, xhr, "overrideMimeType"); | ||
mirrorFunc(_this, xhr, "getAllResponseHeaders"); | ||
return _this; | ||
}; | ||
XMLHttpRequest.Shaper = XHRShaper; | ||
// Export our XMLHttpRequest mirror constructor as module | ||
if (typeof module !== 'undefined') { | ||
module.exports = XMLHttpRequest; | ||
} | ||
// Overload native window constructor | ||
window.XMLHttpRequest = XMLHttpRequest; | ||
{ | ||
"name": "xhr-shaper", | ||
"version": "0.2.1", | ||
"version": "1.0.0", | ||
"description": "Shapes your XHR requests to a max emulated bandwidth and latency, randomizes frequency of progress events", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
"test": "npm run dev", | ||
"clean": "rm -Rf dist", | ||
"build": "./node_modules/.bin/webpack --entry ./index --output-file dist/build.js --output-library XHRShaper", | ||
"dev": "./node_modules/.bin/webpack-dev-server --entry ./index --output-file ./index.js --output-library XHRShaper" | ||
}, | ||
@@ -21,3 +24,7 @@ "keywords": [ | ||
"author": "Stephan Hesse <tchakabam@gmail.com>", | ||
"license": "MIT" | ||
"license": "MIT", | ||
"devDependencies": { | ||
"webpack": "1.13.1", | ||
"webpack-dev-server": "1.14.1" | ||
} | ||
} |
@@ -10,9 +10,23 @@ # xhr-shaper | ||
As global/window shim | ||
``` | ||
require('xhr-shaper'); // NOTE: you can also include `index.js` as static script into your page | ||
var XHRShaper = require('xhr-shaper'); | ||
XHRShaper.useGlobal(); | ||
// now XMLHttpRequest object is the shim | ||
``` | ||
NOTE: Loading this module will overload the XHR constructor in the `window` to produce a mirror object which will have the exact same behavior, but it's not an instance of the native `XMLHttpRequest` (in case that matters for some reason to you). The mirror XHR has an additional property called `shaper` ... | ||
NOTE: Running `useGlobal` will overload the XHR constructor in the `window` to produce a mirror object which will have the exact same behavior, but it's not an instance of the native `XMLHttpRequest` (in case that matters for some reason to you). The mirror XHR has an additional property called `shaper` ... | ||
Or in a modular way | ||
``` | ||
var XHRShaper = require('xhr-shaper'); | ||
var xhr = XHRShaper.XMLHttpRequest; | ||
``` | ||
NOTE: XHRShaper module exposes some fancy undocumented stuff like `BaseXHR` and `objectMirrors`. These might be moved into their own package in the future. | ||
``` | ||
var xhr = new XMLHttpRequest(); | ||
@@ -56,4 +70,6 @@ xhr.onreadystatechange = function(e) { | ||
Just open `index.html` (eventually via a local server) | ||
Run `npm run dev` | ||
Now go to http://localhost:8080/webpack-dev-server/ | ||
### TODOs: | ||
@@ -60,0 +76,0 @@ |
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
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
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
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
15146
10
259
1
90
2
1