Compare commits

..

10 Commits

Author SHA1 Message Date
John Hiesey f2341d0598 All browsers again 2018-05-09 18:56:07 -07:00
John Hiesey c348b1494b All except firefox 2018-05-09 18:33:39 -07:00
John Hiesey f03225fa0c Only safari and firefox 2018-05-09 18:26:04 -07:00
John Hiesey a78b00ff6e All except safari and firefox 2018-05-09 18:12:51 -07:00
John Hiesey ebed5847c8 All except chrome 2018-05-09 17:59:27 -07:00
John Hiesey 2515f6420d Second half of browser list 2018-05-09 17:51:39 -07:00
John Hiesey 204b5abba7 Edge only 2018-05-09 17:48:53 -07:00
John Hiesey a72bc9b825 All except edge 2018-05-09 17:28:42 -07:00
John Hiesey a1a075d6ab Only android 2018-05-09 17:24:28 -07:00
John Hiesey 6c795acf4e Enable airtap debug 2018-05-09 16:45:24 -07:00
15 changed files with 3898 additions and 3668 deletions
+3 -2
View File
@@ -9,13 +9,14 @@ browsers:
- name: MicrosoftEdge
version: 13..latest
- name: ie
version: 11
version: 9..latest
- name: iphone
version: '9.3..latest'
- name: android
version: '6.0..6.0' # TODO: change this back to latest once https://github.com/airtap/browsers/issues/3 is fixed
version: '4.4..6.0' # TODO: change this back to latest once https://github.com/airtap/browsers/issues/3 is fixed
server: ./test/server/index.js
scripts:
- "/ie8-polyfill.js"
- "/test-polyfill.js"
browserify:
- options:
+18 -10
View File
@@ -22,13 +22,23 @@ has to be held in memory at once:
* Chrome >= 43 (using the `fetch` API)
* Firefox >= 9 (using `moz-chunked-arraybuffer` responseType with xhr)
All other supported browsers support pseudo-streaming, where the data is available before
the request finishes, but the entire response must be held in memory. This works for both
text and binary data.
The following browsers support pseudo-streaming, where the data is available before the
request finishes, but the entire response must be held in memory:
* Chrome
* Safari >= 5, and maybe older
* IE >= 10
* Most other Webkit-based browsers, including the default Android browser
### IE note:
As of version 3.0.0, IE10 and below are no longer supported. IE11 support will remain for
now.
All browsers newer than IE8 support binary responses. All of the above browsers that
support true streaming or pseudo-streaming support that for binary data as well
except for IE10. Old (presto-based) Opera also does not support binary streaming either.
### IE8 note:
As of version 2.0.0, IE8 support requires the user to supply polyfills for
`Object.keys`, `Array.prototype.forEach`, and `Array.prototype.indexOf`. Example
implementations are provided in [ie8-polyfill.js](ie8-polyfill.js); alternately,
you may want to consider using [es5-shim](https://github.com/es-shims/es5-shim).
All browsers with full ES5 support shouldn't require any polyfills.
## How do you use it?
@@ -122,14 +132,12 @@ that run in the browser (found in `test/browser`). Normally the browser tests ru
Running `npm test` will run both sets of tests, but in order for the Sauce Labs tests to run
you will need to sign up for an account (free for open source projects) and put the
credentials in a [`.airtaprc` file](https://github.com/airtap/airtap/blob/master/doc/airtaprc.md).
You will also need to run a [Sauce Connect Proxy](https://wiki.saucelabs.com/display/DOCS/Sauce+Connect+Proxy)
with the same credentials.
credentials in a [`.zuulrc` file](https://github.com/defunctzombie/zuul/wiki/zuulrc).
To run just the Node tests, run `npm run test-node`.
To run the browser tests locally, run `npm run test-browser-local` and point your browser to
the link shown in your terminal.
`http://localhost:8080/__zuul`
## License
+168
View File
@@ -0,0 +1,168 @@
// These polyfills taken from MDN (developer.mozilla.org)
// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
if (!Object.keys) {
Object.keys = (function() {
'use strict';
var hasOwnProperty = Object.prototype.hasOwnProperty,
hasDontEnumBug = !({ toString: null }).propertyIsEnumerable('toString'),
dontEnums = [
'toString',
'toLocaleString',
'valueOf',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'constructor'
],
dontEnumsLength = dontEnums.length;
return function(obj) {
if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
throw new TypeError('Object.keys called on non-object');
}
var result = [], prop, i;
for (prop in obj) {
if (hasOwnProperty.call(obj, prop)) {
result.push(prop);
}
}
if (hasDontEnumBug) {
for (i = 0; i < dontEnumsLength; i++) {
if (hasOwnProperty.call(obj, dontEnums[i])) {
result.push(dontEnums[i]);
}
}
}
return result;
};
}());
}
// Production steps of ECMA-262, Edition 5, 15.4.4.18
// Reference: http://es5.github.io/#x15.4.4.18
if (!Array.prototype.forEach) {
Array.prototype.forEach = function(callback, thisArg) {
var T, k;
if (this == null) {
throw new TypeError(' this is null or not defined');
}
// 1. Let O be the result of calling ToObject passing the |this| value as the argument.
var O = Object(this);
// 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
// 3. Let len be ToUint32(lenValue).
var len = O.length >>> 0;
// 4. If IsCallable(callback) is false, throw a TypeError exception.
// See: http://es5.github.com/#x9.11
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function');
}
// 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (arguments.length > 1) {
T = thisArg;
}
// 6. Let k be 0
k = 0;
// 7. Repeat, while k < len
while (k < len) {
var kValue;
// a. Let Pk be ToString(k).
// This is implicit for LHS operands of the in operator
// b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
// This step can be combined with c
// c. If kPresent is true, then
if (k in O) {
// i. Let kValue be the result of calling the Get internal method of O with argument Pk.
kValue = O[k];
// ii. Call the Call internal method of callback with T as the this value and
// argument list containing kValue, k, and O.
callback.call(T, kValue, k, O);
}
// d. Increase k by 1.
k++;
}
// 8. return undefined
};
}
// Production steps of ECMA-262, Edition 5, 15.4.4.14
// Reference: http://es5.github.io/#x15.4.4.14
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function(searchElement, fromIndex) {
var k;
// 1. Let O be the result of calling ToObject passing
// the this value as the argument.
if (this == null) {
throw new TypeError('"this" is null or not defined');
}
var O = Object(this);
// 2. Let lenValue be the result of calling the Get
// internal method of O with the argument "length".
// 3. Let len be ToUint32(lenValue).
var len = O.length >>> 0;
// 4. If len is 0, return -1.
if (len === 0) {
return -1;
}
// 5. If argument fromIndex was passed let n be
// ToInteger(fromIndex); else let n be 0.
var n = +fromIndex || 0;
if (Math.abs(n) === Infinity) {
n = 0;
}
// 6. If n >= len, return -1.
if (n >= len) {
return -1;
}
// 7. If n >= 0, then Let k be n.
// 8. Else, n<0, Let k be len - abs(n).
// If k is less than 0, then let k be 0.
k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
// 9. Repeat, while k < len
while (k < len) {
// a. Let Pk be ToString(k).
// This is implicit for LHS operands of the in operator
// b. Let kPresent be the result of calling the
// HasProperty internal method of O with argument Pk.
// This step can be combined with c
// c. If kPresent is true, then
// i. Let elementK be the result of calling the Get
// internal method of O with the argument ToString(k).
// ii. Let same be the result of applying the
// Strict Equality Comparison Algorithm to
// searchElement and elementK.
// iii. If same is true, return k.
if (k in O && O[k] === searchElement) {
return k;
}
k++;
}
return -1;
};
}
+17 -3
View File
@@ -4,6 +4,12 @@ exports.writableStream = isFunction(global.WritableStream)
exports.abortController = isFunction(global.AbortController)
exports.blobConstructor = false
try {
new Blob([new ArrayBuffer(1)])
exports.blobConstructor = true
} catch (e) {}
// The xhr request to example.com may violate some restrictive CSP configurations,
// so if we're running in a browser that supports `fetch`, avoid calling getXHR()
// and assume support for certain features below.
@@ -39,19 +45,27 @@ function checkTypeSupport (type) {
return false
}
// For some strange reason, Safari 7.0 reports typeof global.ArrayBuffer === 'object'.
// Safari 7.1 appears to have fixed this bug.
var haveArrayBuffer = typeof global.ArrayBuffer !== 'undefined'
var haveSlice = haveArrayBuffer && isFunction(global.ArrayBuffer.prototype.slice)
// If fetch is supported, then arraybuffer will be supported too. Skip calling
// checkTypeSupport(), since that calls getXHR().
exports.arraybuffer = exports.fetch || checkTypeSupport('arraybuffer')
exports.arraybuffer = exports.fetch || (haveArrayBuffer && checkTypeSupport('arraybuffer'))
// These next two tests unavoidably show warnings in Chrome. Since fetch will always
// be used if it's available, just return false for these to avoid the warnings.
exports.msstream = !exports.fetch && checkTypeSupport('ms-stream')
exports.mozchunkedarraybuffer = !exports.fetch && checkTypeSupport('moz-chunked-arraybuffer')
exports.msstream = !exports.fetch && haveSlice && checkTypeSupport('ms-stream')
exports.mozchunkedarraybuffer = !exports.fetch && haveArrayBuffer &&
checkTypeSupport('moz-chunked-arraybuffer')
// If fetch is supported, then overrideMimeType will be supported too. Skip calling
// getXHR().
exports.overrideMimeType = exports.fetch || (getXHR() ? isFunction(getXHR().overrideMimeType) : false)
exports.vbArray = isFunction(global.VBArray)
function isFunction (value) {
return typeof value === 'function'
}
+19 -6
View File
@@ -2,6 +2,7 @@ var capability = require('./capability')
var inherits = require('inherits')
var response = require('./response')
var stream = require('readable-stream')
var toArrayBuffer = require('to-arraybuffer')
var IncomingMessage = response.IncomingMessage
var rStates = response.readyStates
@@ -15,6 +16,8 @@ function decideMode (preferBinary, useFetch) {
return 'ms-stream'
} else if (capability.arraybuffer && preferBinary) {
return 'arraybuffer'
} else if (capability.vbArray && preferBinary) {
return 'text:vbarray'
} else {
return 'text'
}
@@ -28,7 +31,7 @@ var ClientRequest = module.exports = function (opts) {
self._body = []
self._headers = {}
if (opts.auth)
self.setHeader('Authorization', 'Basic ' + Buffer.from(opts.auth).toString('base64'))
self.setHeader('Authorization', 'Basic ' + new Buffer(opts.auth).toString('base64'))
Object.keys(opts.headers).forEach(function (name) {
self.setHeader(name, opts.headers[name])
})
@@ -99,10 +102,19 @@ ClientRequest.prototype._onFinish = function () {
var headersObj = self._headers
var body = null
if (opts.method !== 'GET' && opts.method !== 'HEAD') {
body = new Blob(self._body, {
type: (headersObj['content-type'] || {}).value || ''
});
}
if (capability.arraybuffer) {
body = toArrayBuffer(Buffer.concat(self._body))
} else if (capability.blobConstructor) {
body = new global.Blob(self._body.map(function (buffer) {
return toArrayBuffer(buffer)
}), {
type: (headersObj['content-type'] || {}).value || ''
})
} else {
// get utf8 string
body = Buffer.concat(self._body).toString()
}
}
// create flattened list of headers
var headersList = []
@@ -163,7 +175,7 @@ ClientRequest.prototype._onFinish = function () {
// Can't set responseType on really old browsers
if ('responseType' in xhr)
xhr.responseType = self._mode
xhr.responseType = self._mode.split(':')[0]
if ('withCredentials' in xhr)
xhr.withCredentials = !!opts.withCredentials
@@ -311,5 +323,6 @@ var unsafeHeaders = [
'trailer',
'transfer-encoding',
'upgrade',
'user-agent',
'via'
]
+24 -7
View File
@@ -46,7 +46,7 @@ var IncomingMessage = exports.IncomingMessage = function (xhr, response, mode, f
return new Promise(function (resolve, reject) {
if (self._destroyed) {
reject()
} else if(self.push(Buffer.from(chunk))) {
} else if(self.push(new Buffer(chunk))) {
resolve()
} else {
self._resumeFetch = resolve
@@ -84,7 +84,7 @@ var IncomingMessage = exports.IncomingMessage = function (xhr, response, mode, f
self.push(null)
return
}
self.push(Buffer.from(result.value))
self.push(new Buffer(result.value))
read()
}).catch(function (err) {
global.clearTimeout(fetchTimer)
@@ -153,12 +153,29 @@ IncomingMessage.prototype._onXHRProgress = function () {
var response = null
switch (self._mode) {
case 'text:vbarray': // For IE9
if (xhr.readyState !== rStates.DONE)
break
try {
// This fails in IE8
response = new global.VBArray(xhr.responseBody).toArray()
} catch (e) {}
if (response !== null) {
self.push(new Buffer(response))
break
}
// Falls through in IE8
case 'text':
response = xhr.responseText
try { // This will fail when readyState = 3 in IE9. Switch mode and wait for readyState = 4
response = xhr.responseText
} catch (e) {
self._mode = 'text:vbarray'
break
}
if (response.length > self._pos) {
var newData = response.substr(self._pos)
if (self._charset === 'x-user-defined') {
var buffer = Buffer.alloc(newData.length)
var buffer = new Buffer(newData.length)
for (var i = 0; i < newData.length; i++)
buffer[i] = newData.charCodeAt(i) & 0xff
@@ -173,13 +190,13 @@ IncomingMessage.prototype._onXHRProgress = function () {
if (xhr.readyState !== rStates.DONE || !xhr.response)
break
response = xhr.response
self.push(Buffer.from(new Uint8Array(response)))
self.push(new Buffer(new Uint8Array(response)))
break
case 'moz-chunked-arraybuffer': // take whole
response = xhr.response
if (xhr.readyState !== rStates.LOADING || !response)
break
self.push(Buffer.from(new Uint8Array(response)))
self.push(new Buffer(new Uint8Array(response)))
break
case 'ms-stream':
response = xhr.response
@@ -188,7 +205,7 @@ IncomingMessage.prototype._onXHRProgress = function () {
var reader = new global.MSStreamReader()
reader.onprogress = function () {
if (reader.result.byteLength > self._pos) {
self.push(Buffer.from(new Uint8Array(reader.result.slice(self._pos))))
self.push(new Buffer(new Uint8Array(reader.result.slice(self._pos))))
self._pos = reader.result.byteLength
}
}
+3576 -3625
View File
File diff suppressed because it is too large Load Diff
+7 -6
View File
@@ -1,6 +1,6 @@
{
"name": "stream-http",
"version": "3.0.0",
"version": "2.8.1",
"description": "Streaming http in the browser",
"main": "index.js",
"repository": {
@@ -10,7 +10,7 @@
"scripts": {
"test": "npm run test-node && ([ -n \"${TRAVIS_PULL_REQUEST}\" -a \"${TRAVIS_PULL_REQUEST}\" != 'false' ] || npm run test-browser)",
"test-node": "tape test/node/*.js",
"test-browser": "airtap --loopback airtap.local -- test/browser/*.js",
"test-browser": "DEBUG=airtap airtap --loopback airtap.local -- test/browser/*.js",
"test-browser-local": "airtap --no-instrument --local 8080 -- test/browser/*.js"
},
"author": "John Hiesey",
@@ -29,13 +29,14 @@
"dependencies": {
"builtin-status-codes": "^3.0.0",
"inherits": "^2.0.1",
"readable-stream": "^3.0.6",
"readable-stream": "^2.3.6",
"to-arraybuffer": "^1.0.0",
"xtend": "^4.0.0"
},
"devDependencies": {
"airtap": "^0.1.0",
"basic-auth": "^2.0.1",
"brfs": "^2.0.1",
"airtap": "^0.0.5",
"basic-auth": "^2.0.0",
"brfs": "^1.6.1",
"cookie-parser": "^1.4.3",
"express": "^4.16.3",
"tape": "^4.9.0",
+25 -4
View File
@@ -5,7 +5,18 @@ var UAParser = require('ua-parser-js')
var http = require('../..')
var COPIES = 20
var browser = (new UAParser()).setUA(navigator.userAgent).getBrowser()
var browserName = browser.name
var browserVersion = browser.major
// Binary streaming doesn't work in IE10 or below
var skipStreamingCheck = (browserName === 'IE' && browserVersion <= 10)
// Binary data gets corrupted in IE8 or below
var skipVerification = (browserName === 'IE' && browserVersion <= 8)
// IE8 tends to throw up modal dialogs complaining about scripts running too long
// Since streaming doesn't actually work there anyway, just use one copy
var COPIES = skipVerification ? 1 : 20
var MIN_PIECES = 2
var referenceOnce = fs.readFileSync(__dirname + '/../server/static/browserify.png')
@@ -21,8 +32,15 @@ test('binary streaming', function (t) {
}, function (res) {
var buffers = []
res.on('end', function () {
t.ok(reference.equals(Buffer.concat(buffers)), 'contents match')
t.ok(buffers.length >= MIN_PIECES, 'received in multiple parts')
if (skipVerification)
t.skip('binary data not preserved on IE <= 8')
else
t.ok(reference.equals(Buffer.concat(buffers)), 'contents match')
if (skipStreamingCheck)
t.skip('streaming not available on IE <= 8')
else
t.ok(buffers.length >= MIN_PIECES, 'received in multiple parts')
t.end()
})
@@ -39,7 +57,10 @@ test('large binary request without streaming', function (t) {
}, function (res) {
var buffers = []
res.on('end', function () {
t.ok(reference.equals(Buffer.concat(buffers)), 'contents match')
if (skipVerification)
t.skip('binary data not preserved on IE <= 8')
else
t.ok(reference.equals(Buffer.concat(buffers)), 'contents match')
t.end()
})
+10 -1
View File
@@ -5,6 +5,12 @@ var UAParser = require('ua-parser-js')
var http = require('../..')
var browser = (new UAParser()).setUA(navigator.userAgent).getBrowser()
var browserName = browser.name
var browserVersion = browser.major
// Binary data gets corrupted in IE8 or below
var skipVerification = (browserName === 'IE' && browserVersion <= 8)
var reference = fs.readFileSync(__dirname + '/../server/static/browserify.png')
test('binary download', function (t) {
@@ -12,7 +18,10 @@ test('binary download', function (t) {
var buffers = []
res.on('end', function () {
t.ok(reference.equals(Buffer.concat(buffers)), 'contents match')
if (skipVerification)
t.skip('binary data not preserved on IE <= 8')
else
t.ok(reference.equals(Buffer.concat(buffers)), 'contents match')
t.end()
})
+13 -1
View File
@@ -5,6 +5,15 @@ var UAParser = require('ua-parser-js')
var http = require('../..')
var browser = (new UAParser()).setUA(navigator.userAgent).getBrowser()
var browserName = browser.name
var browserVersion = browser.major
// Binary request bodies don't work in a bunch of browsers
var skipVerification = ((browserName === 'IE' && browserVersion <= 10) ||
(browserName === 'Safari' && browserVersion <= 5) ||
(browserName === 'WebKit' && browserVersion <= 534) || // Old mobile safari
(browserName === 'Android Browser' && browserVersion <= 4))
var reference = fs.readFileSync(__dirname + '/../server/static/browserify.png')
test('post binary', function (t) {
@@ -15,7 +24,10 @@ test('post binary', function (t) {
var buffers = []
res.on('end', function () {
t.ok(reference.equals(Buffer.concat(buffers)), 'echoed contents match')
if (skipVerification)
t.skip('binary data not preserved on this browser')
else
t.ok(reference.equals(Buffer.concat(buffers)), 'echoed contents match')
t.end()
})
+4 -1
View File
@@ -28,7 +28,10 @@ test('text streaming', function (t) {
var buffers = []
res.on('end', function () {
t.ok(buffers.length >= MIN_PIECES, 'received in multiple parts')
if (skipStreamingCheck)
t.skip('streaming not available on IE <= 8')
else
t.ok(buffers.length >= MIN_PIECES, 'received in multiple parts')
t.ok(reference.equals(Buffer.concat(buffers)), 'contents match')
t.end()
})
+1
View File
@@ -15,6 +15,7 @@ var skipResponseUrl = ((browserName === 'IE') ||
(browserName === 'Chrome' && browserVersion <= 36) ||
(browserName === 'Firefox' && browserVersion <= 31) ||
((browserName === 'Safari' || browserName === 'Mobile Safari') && browserVersion <= 8) ||
(browserName === 'WebKit') || // Old mobile safari
(browserName === 'Android Browser' && browserVersion <= 4))
var reference = fs.readFileSync(__dirname + '/../server/static/basic.txt')
+12 -2
View File
@@ -4,10 +4,20 @@ var UAParser = require('ua-parser-js')
var url = require('url')
var work = require('webworkify')
var browser = (new UAParser()).setUA(navigator.userAgent).getBrowser()
var browserName = browser.name
var browserVersion = browser.major
// Skip browsers with poor or nonexistant WebWorker support
var skip = ((browserName === 'IE' && browserVersion <= 10) ||
(browserName === 'Safari' && browserVersion <= 5) ||
(browserName === 'WebKit' && browserVersion <= 534) || // Old mobile safari
(browserName === 'Android Browser' && browserVersion <= 4))
var reference = fs.readFileSync(__dirname + '/../server/static/browserify.png')
// Temporarily disabled due to https://github.com/browserify/webworkify/issues/41
test.skip('binary download in WebWorker', function (t) {
test('binary download in WebWorker', {
skip: skip
}, function (t) {
// We have to use a global url, since webworkify puts the worker in a Blob,
// which doesn't have a proper location
var testUrl = url.resolve(global.location.href, '/browserify.png')
+1
View File
@@ -0,0 +1 @@
../../../ie8-polyfill.js