pax_global_header00006660000000000000000000000064130560546460014523gustar00rootroot0000000000000052 comment=d4ed806fdcbdeaef707d27f6c88943f0336a647d fetch-2.0.3/000077500000000000000000000000001305605464600126165ustar00rootroot00000000000000fetch-2.0.3/.gitignore000066400000000000000000000001061305605464600146030ustar00rootroot00000000000000.env bower_components/ node_modules/ sauce_connect/ sauce_connect.log fetch-2.0.3/.jshintrc000066400000000000000000000006241305605464600144450ustar00rootroot00000000000000{ "curly": true, "eqeqeq": true, "es3": true, "immed": true, "indent": 2, "latedef": true, "newcap": true, "noarg": true, "quotmark": true, "undef": true, "unused": true, "strict": true, "trailing": true, "asi": true, "boss": true, "esnext": true, "eqnull": true, "browser": true, "worker": true, "globals": { "JSON": false, "URLSearchParams": false } } fetch-2.0.3/.travis.yml000066400000000000000000000005551305605464600147340ustar00rootroot00000000000000sudo: false language: node_js cache: directories: - phantomjs deploy: provider: npm email: mislav.marohnic@gmail.com api_key: secure: gt9g5/bXhxSKjxfFSPCdpWGJKBrSG8zdGRYgPouUgRqNeD2Ff4Nc8HGQTxp0OLKnP/jJ5FIru5jUur6LWzJCyEd+aNUEvFf5J078m3pzHN9AP2fiWUkKXcc5lKV0PQnI+JDRxJwd/PggtjubrneGfCzyFoys9apRrd/TzTGEtGw= on: tags: true repo: github/fetch fetch-2.0.3/LICENSE000066400000000000000000000020451305605464600136240ustar00rootroot00000000000000Copyright (c) 2014-2016 GitHub, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. fetch-2.0.3/MAINTAINING.md000066400000000000000000000012071305605464600146760ustar00rootroot00000000000000# Maintaining ## Releasing a new version This project follows [semver](http://semver.org/). So if you are making a bug fix, only increment the patch level "1.0.x". If any new files are added, a minor version "1.x.x" bump is in order. ### Make a release commit To prepare the release commit: 1. Update the npm [package.json](https://github.com/github/fetch/blob/master/package.json) `version` value. 2. Make a single commit with the description as "Fetch 2.x.x". 3. Finally, tag the commit with `v2.x.x`. ``` $ git pull $ vim package.json $ git add package.json $ git commit -m "Fetch 1.x.x" $ git tag v1.x.x $ git push $ git push --tags ``` fetch-2.0.3/Makefile000066400000000000000000000013601305605464600142560ustar00rootroot00000000000000test: node_modules/ lint ./script/test lint: node_modules/ ./node_modules/.bin/jshint *.js test/*.js node_modules/: npm install clean: rm -rf ./bower_components ./node_modules ifeq ($(shell uname -s),Darwin) sauce_connect/bin/sc: wget https://saucelabs.com/downloads/sc-4.3.16-osx.zip unzip sc-4.3.16-osx.zip mv sc-4.3.16-osx sauce_connect rm sc-4.3.16-osx.zip else sauce_connect/bin/sc: mkdir -p sauce_connect curl -fsSL http://saucelabs.com/downloads/sc-4.3.16-linux.tar.gz | tar xz -C sauce_connect --strip-components 1 endif phantomjs/bin/phantomjs: mkdir -p phantomjs wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2 -O- | tar xj -C phantomjs --strip-components 1 .PHONY: clean lint test fetch-2.0.3/README.md000066400000000000000000000206641305605464600141050ustar00rootroot00000000000000# window.fetch polyfill The `fetch()` function is a Promise-based mechanism for programmatically making web requests in the browser. This project is a polyfill that implements a subset of the standard [Fetch specification][], enough to make `fetch` a viable replacement for most uses of XMLHttpRequest in traditional web applications. This project adheres to the [Open Code of Conduct][]. By participating, you are expected to uphold this code. ## Table of Contents * [Read this first](#read-this-first) * [Installation](#installation) * [Usage](#usage) * [HTML](#html) * [JSON](#json) * [Response metadata](#response-metadata) * [Post form](#post-form) * [Post JSON](#post-json) * [File upload](#file-upload) * [Caveats](#caveats) * [Handling HTTP error statuses](#handling-http-error-statuses) * [Sending cookies](#sending-cookies) * [Receiving cookies](#receiving-cookies) * [Obtaining the Response URL](#obtaining-the-response-url) * [Browser Support](#browser-support) ## Read this first * If you believe you found a bug with how `fetch` behaves in Chrome or Firefox, please **avoid opening an issue in this repository**. This project is a _polyfill_, and since Chrome and Firefox both implement the `window.fetch` function natively, no code from this project actually takes any effect in these browsers. See [Browser support](#browser-support) for detailed information. * If you have trouble **making a request to another domain** (a different subdomain or port number also constitutes as another domain), please familiarize yourself with all the intricacies and limitations of [CORS][] requests. Because CORS requires participation of the server by implementing specific HTTP response headers, it is often nontrivial to set up or debug. CORS is exclusively handled by the browser's internal mechanisms which this polyfill cannot influence. * If you have trouble **maintaining the user's session** or [CSRF][] protection through `fetch` requests, please ensure that you've read and understood the [Sending cookies](#sending-cookies) section. * If this polyfill **doesn't work under Node.js environments**, that is expected, because this project is meant for web browsers only. You should ensure that your application doesn't try to package and run this on the server. * If you have an idea for a new feature of `fetch`, please understand that we are only ever going to add features and APIs that are a part of the [Fetch specification][]. You should **submit your feature requests** to the [repository of the specification](https://github.com/whatwg/fetch/issues) itself, rather than this repository. ## Installation * `npm install whatwg-fetch --save`; or * `bower install fetch`. You will also need a Promise polyfill for [older browsers](http://caniuse.com/#feat=promises). We recommend [taylorhakes/promise-polyfill](https://github.com/taylorhakes/promise-polyfill) for its small size and Promises/A+ compatibility. For use with webpack, add this package in the `entry` configuration option before your application entry point: ```javascript entry: ['whatwg-fetch', ...] ``` For Babel and ES2015+, make sure to import the file: ```javascript import 'whatwg-fetch' ``` ## Usage For a more comprehensive API reference that this polyfill supports, refer to https://github.github.io/fetch/. ### HTML ```javascript fetch('/users.html') .then(function(response) { return response.text() }).then(function(body) { document.body.innerHTML = body }) ``` ### JSON ```javascript fetch('/users.json') .then(function(response) { return response.json() }).then(function(json) { console.log('parsed json', json) }).catch(function(ex) { console.log('parsing failed', ex) }) ``` ### Response metadata ```javascript fetch('/users.json').then(function(response) { console.log(response.headers.get('Content-Type')) console.log(response.headers.get('Date')) console.log(response.status) console.log(response.statusText) }) ``` ### Post form ```javascript var form = document.querySelector('form') fetch('/users', { method: 'POST', body: new FormData(form) }) ``` ### Post JSON ```javascript fetch('/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Hubot', login: 'hubot', }) }) ``` ### File upload ```javascript var input = document.querySelector('input[type="file"]') var data = new FormData() data.append('file', input.files[0]) data.append('user', 'hubot') fetch('/avatars', { method: 'POST', body: data }) ``` ### Caveats The `fetch` specification differs from `jQuery.ajax()` in mainly two ways that bear keeping in mind: * The Promise returned from `fetch()` **won't reject on HTTP error status** even if the response is an HTTP 404 or 500. Instead, it will resolve normally, and it will only reject on network failure or if anything prevented the request from completing. * By default, `fetch` **won't send or receive any cookies** from the server, resulting in unauthenticated requests if the site relies on maintaining a user session. See [Sending cookies](#sending-cookies) for how to opt into cookie handling. #### Handling HTTP error statuses To have `fetch` Promise reject on HTTP error statuses, i.e. on any non-2xx status, define a custom response handler: ```javascript function checkStatus(response) { if (response.status >= 200 && response.status < 300) { return response } else { var error = new Error(response.statusText) error.response = response throw error } } function parseJSON(response) { return response.json() } fetch('/users') .then(checkStatus) .then(parseJSON) .then(function(data) { console.log('request succeeded with JSON response', data) }).catch(function(error) { console.log('request failed', error) }) ``` #### Sending cookies To automatically send cookies for the current domain, the `credentials` option must be provided: ```javascript fetch('/users', { credentials: 'same-origin' }) ``` The "same-origin" value makes `fetch` behave similarly to XMLHttpRequest with regards to cookies. Otherwise, cookies won't get sent, resulting in these requests not preserving the authentication session. For [CORS][] requests, use the "include" value to allow sending credentials to other domains: ```javascript fetch('https://example.com:1234/users', { credentials: 'include' }) ``` #### Receiving cookies As with XMLHttpRequest, the `Set-Cookie` response header returned from the server is a [forbidden header name][] and therefore can't be programmatically read with `response.headers.get()`. Instead, it's the browser's responsibility to handle new cookies being set (if applicable to the current URL). Unless they are HTTP-only, new cookies will be available through `document.cookie`. Bear in mind that the default behavior of `fetch` is to ignore the `Set-Cookie` header completely. To opt into accepting cookies from the server, you must use the `credentials` option. #### Obtaining the Response URL Due to limitations of XMLHttpRequest, the `response.url` value might not be reliable after HTTP redirects on older browsers. The solution is to configure the server to set the response HTTP header `X-Request-URL` to the current URL after any redirect that might have happened. It should be safe to set it unconditionally. ``` ruby # Ruby on Rails controller example response.headers['X-Request-URL'] = request.url ``` This server workaround is necessary if you need reliable `response.url` in Firefox < 32, Chrome < 37, Safari, or IE. ## Browser Support - Chrome - Firefox - Safari 6.1+ - Internet Explorer 10+ Note: modern browsers such as Chrome, Firefox, and Microsoft Edge contain native implementations of `window.fetch`, therefore the code from this polyfill doesn't have any effect on those browsers. If you believe you've encountered an error with how `window.fetch` is implemented in any of these browsers, you should file an issue with that browser vendor instead of this project. [fetch specification]: https://fetch.spec.whatwg.org [open code of conduct]: http://todogroup.org/opencodeofconduct/#fetch/opensource@github.com [cors]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS "Cross-origin resource sharing" [csrf]: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet "Cross-site request forgery" [forbidden header name]: https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name fetch-2.0.3/bower.json000066400000000000000000000002451305605464600146300ustar00rootroot00000000000000{ "name": "fetch", "main": "fetch.js", "ignore": [ ".*", "*.md", "examples/", "Makefile", "package.json", "script/", "test/" ] } fetch-2.0.3/examples/000077500000000000000000000000001305605464600144345ustar00rootroot00000000000000fetch-2.0.3/examples/index.html000066400000000000000000000007651305605464600164410ustar00rootroot00000000000000 fetch-2.0.3/fetch.js000066400000000000000000000306101305605464600142450ustar00rootroot00000000000000(function(self) { 'use strict'; if (self.fetch) { return } var support = { searchParams: 'URLSearchParams' in self, iterable: 'Symbol' in self && 'iterator' in Symbol, blob: 'FileReader' in self && 'Blob' in self && (function() { try { new Blob() return true } catch(e) { return false } })(), formData: 'FormData' in self, arrayBuffer: 'ArrayBuffer' in self } if (support.arrayBuffer) { var viewClasses = [ '[object Int8Array]', '[object Uint8Array]', '[object Uint8ClampedArray]', '[object Int16Array]', '[object Uint16Array]', '[object Int32Array]', '[object Uint32Array]', '[object Float32Array]', '[object Float64Array]' ] var isDataView = function(obj) { return obj && DataView.prototype.isPrototypeOf(obj) } var isArrayBufferView = ArrayBuffer.isView || function(obj) { return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1 } } function normalizeName(name) { if (typeof name !== 'string') { name = String(name) } if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) { throw new TypeError('Invalid character in header field name') } return name.toLowerCase() } function normalizeValue(value) { if (typeof value !== 'string') { value = String(value) } return value } // Build a destructive iterator for the value list function iteratorFor(items) { var iterator = { next: function() { var value = items.shift() return {done: value === undefined, value: value} } } if (support.iterable) { iterator[Symbol.iterator] = function() { return iterator } } return iterator } function Headers(headers) { this.map = {} if (headers instanceof Headers) { headers.forEach(function(value, name) { this.append(name, value) }, this) } else if (Array.isArray(headers)) { headers.forEach(function(header) { this.append(header[0], header[1]) }, this) } else if (headers) { Object.getOwnPropertyNames(headers).forEach(function(name) { this.append(name, headers[name]) }, this) } } Headers.prototype.append = function(name, value) { name = normalizeName(name) value = normalizeValue(value) var oldValue = this.map[name] this.map[name] = oldValue ? oldValue+','+value : value } Headers.prototype['delete'] = function(name) { delete this.map[normalizeName(name)] } Headers.prototype.get = function(name) { name = normalizeName(name) return this.has(name) ? this.map[name] : null } Headers.prototype.has = function(name) { return this.map.hasOwnProperty(normalizeName(name)) } Headers.prototype.set = function(name, value) { this.map[normalizeName(name)] = normalizeValue(value) } Headers.prototype.forEach = function(callback, thisArg) { for (var name in this.map) { if (this.map.hasOwnProperty(name)) { callback.call(thisArg, this.map[name], name, this) } } } Headers.prototype.keys = function() { var items = [] this.forEach(function(value, name) { items.push(name) }) return iteratorFor(items) } Headers.prototype.values = function() { var items = [] this.forEach(function(value) { items.push(value) }) return iteratorFor(items) } Headers.prototype.entries = function() { var items = [] this.forEach(function(value, name) { items.push([name, value]) }) return iteratorFor(items) } if (support.iterable) { Headers.prototype[Symbol.iterator] = Headers.prototype.entries } function consumed(body) { if (body.bodyUsed) { return Promise.reject(new TypeError('Already read')) } body.bodyUsed = true } function fileReaderReady(reader) { return new Promise(function(resolve, reject) { reader.onload = function() { resolve(reader.result) } reader.onerror = function() { reject(reader.error) } }) } function readBlobAsArrayBuffer(blob) { var reader = new FileReader() var promise = fileReaderReady(reader) reader.readAsArrayBuffer(blob) return promise } function readBlobAsText(blob) { var reader = new FileReader() var promise = fileReaderReady(reader) reader.readAsText(blob) return promise } function readArrayBufferAsText(buf) { var view = new Uint8Array(buf) var chars = new Array(view.length) for (var i = 0; i < view.length; i++) { chars[i] = String.fromCharCode(view[i]) } return chars.join('') } function bufferClone(buf) { if (buf.slice) { return buf.slice(0) } else { var view = new Uint8Array(buf.byteLength) view.set(new Uint8Array(buf)) return view.buffer } } function Body() { this.bodyUsed = false this._initBody = function(body) { this._bodyInit = body if (!body) { this._bodyText = '' } else if (typeof body === 'string') { this._bodyText = body } else if (support.blob && Blob.prototype.isPrototypeOf(body)) { this._bodyBlob = body } else if (support.formData && FormData.prototype.isPrototypeOf(body)) { this._bodyFormData = body } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { this._bodyText = body.toString() } else if (support.arrayBuffer && support.blob && isDataView(body)) { this._bodyArrayBuffer = bufferClone(body.buffer) // IE 10-11 can't handle a DataView body. this._bodyInit = new Blob([this._bodyArrayBuffer]) } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) { this._bodyArrayBuffer = bufferClone(body) } else { throw new Error('unsupported BodyInit type') } if (!this.headers.get('content-type')) { if (typeof body === 'string') { this.headers.set('content-type', 'text/plain;charset=UTF-8') } else if (this._bodyBlob && this._bodyBlob.type) { this.headers.set('content-type', this._bodyBlob.type) } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8') } } } if (support.blob) { this.blob = function() { var rejected = consumed(this) if (rejected) { return rejected } if (this._bodyBlob) { return Promise.resolve(this._bodyBlob) } else if (this._bodyArrayBuffer) { return Promise.resolve(new Blob([this._bodyArrayBuffer])) } else if (this._bodyFormData) { throw new Error('could not read FormData body as blob') } else { return Promise.resolve(new Blob([this._bodyText])) } } this.arrayBuffer = function() { if (this._bodyArrayBuffer) { return consumed(this) || Promise.resolve(this._bodyArrayBuffer) } else { return this.blob().then(readBlobAsArrayBuffer) } } } this.text = function() { var rejected = consumed(this) if (rejected) { return rejected } if (this._bodyBlob) { return readBlobAsText(this._bodyBlob) } else if (this._bodyArrayBuffer) { return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer)) } else if (this._bodyFormData) { throw new Error('could not read FormData body as text') } else { return Promise.resolve(this._bodyText) } } if (support.formData) { this.formData = function() { return this.text().then(decode) } } this.json = function() { return this.text().then(JSON.parse) } return this } // HTTP methods whose capitalization should be normalized var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] function normalizeMethod(method) { var upcased = method.toUpperCase() return (methods.indexOf(upcased) > -1) ? upcased : method } function Request(input, options) { options = options || {} var body = options.body if (input instanceof Request) { if (input.bodyUsed) { throw new TypeError('Already read') } this.url = input.url this.credentials = input.credentials if (!options.headers) { this.headers = new Headers(input.headers) } this.method = input.method this.mode = input.mode if (!body && input._bodyInit != null) { body = input._bodyInit input.bodyUsed = true } } else { this.url = String(input) } this.credentials = options.credentials || this.credentials || 'omit' if (options.headers || !this.headers) { this.headers = new Headers(options.headers) } this.method = normalizeMethod(options.method || this.method || 'GET') this.mode = options.mode || this.mode || null this.referrer = null if ((this.method === 'GET' || this.method === 'HEAD') && body) { throw new TypeError('Body not allowed for GET or HEAD requests') } this._initBody(body) } Request.prototype.clone = function() { return new Request(this, { body: this._bodyInit }) } function decode(body) { var form = new FormData() body.trim().split('&').forEach(function(bytes) { if (bytes) { var split = bytes.split('=') var name = split.shift().replace(/\+/g, ' ') var value = split.join('=').replace(/\+/g, ' ') form.append(decodeURIComponent(name), decodeURIComponent(value)) } }) return form } function parseHeaders(rawHeaders) { var headers = new Headers() rawHeaders.split(/\r?\n/).forEach(function(line) { var parts = line.split(':') var key = parts.shift().trim() if (key) { var value = parts.join(':').trim() headers.append(key, value) } }) return headers } Body.call(Request.prototype) function Response(bodyInit, options) { if (!options) { options = {} } this.type = 'default' this.status = 'status' in options ? options.status : 200 this.ok = this.status >= 200 && this.status < 300 this.statusText = 'statusText' in options ? options.statusText : 'OK' this.headers = new Headers(options.headers) this.url = options.url || '' this._initBody(bodyInit) } Body.call(Response.prototype) Response.prototype.clone = function() { return new Response(this._bodyInit, { status: this.status, statusText: this.statusText, headers: new Headers(this.headers), url: this.url }) } Response.error = function() { var response = new Response(null, {status: 0, statusText: ''}) response.type = 'error' return response } var redirectStatuses = [301, 302, 303, 307, 308] Response.redirect = function(url, status) { if (redirectStatuses.indexOf(status) === -1) { throw new RangeError('Invalid status code') } return new Response(null, {status: status, headers: {location: url}}) } self.Headers = Headers self.Request = Request self.Response = Response self.fetch = function(input, init) { return new Promise(function(resolve, reject) { var request = new Request(input, init) var xhr = new XMLHttpRequest() xhr.onload = function() { var options = { status: xhr.status, statusText: xhr.statusText, headers: parseHeaders(xhr.getAllResponseHeaders() || '') } options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL') var body = 'response' in xhr ? xhr.response : xhr.responseText resolve(new Response(body, options)) } xhr.onerror = function() { reject(new TypeError('Network request failed')) } xhr.ontimeout = function() { reject(new TypeError('Network request failed')) } xhr.open(request.method, request.url, true) if (request.credentials === 'include') { xhr.withCredentials = true } if ('responseType' in xhr && support.blob) { xhr.responseType = 'blob' } request.headers.forEach(function(value, name) { xhr.setRequestHeader(name, value) }) xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit) }) } self.fetch.polyfill = true })(typeof self !== 'undefined' ? self : this); fetch-2.0.3/package.json000066400000000000000000000007071305605464600151100ustar00rootroot00000000000000{ "name": "whatwg-fetch", "description": "A window.fetch polyfill.", "version": "2.0.3", "main": "fetch.js", "repository": "github/fetch", "license": "MIT", "devDependencies": { "chai": "1.10.0", "jshint": "2.8.0", "mocha": "2.1.0", "mocha-phantomjs-core": "2.0.1", "promise-polyfill": "6.0.2", "url-search-params": "0.6.1" }, "files": [ "LICENSE", "fetch.js" ], "scripts": { "test": "make" } } fetch-2.0.3/script/000077500000000000000000000000001305605464600141225ustar00rootroot00000000000000fetch-2.0.3/script/phantomjs000077500000000000000000000013421305605464600160530ustar00rootroot00000000000000#!/bin/bash set -e port=3900 # Find next available port while lsof -i :$((++port)) >/dev/null; do true; done # Spin a test server in the background node ./script/server $port &>/dev/null & server_pid=$! trap "kill $server_pid" INT EXIT STATUS=0 reporter=dot [ -z "$CI" ] || reporter=spec if [ -n "$TRAVIS" ]; then make phantomjs/bin/phantomjs export PATH="$PWD/phantomjs/bin:$PATH" fi run() { phantomjs ./node_modules/mocha-phantomjs-core/mocha-phantomjs-core.js \ "$1" $reporter "{\"useColors\":true, \"hooks\":\"$PWD/test/mocha-phantomjs-hooks.js\"}" \ || STATUS=$? } [ -z "$CI" ] || echo "phantomjs $(phantomjs -v)" run "http://localhost:$port/" run "http://localhost:$port/test/test-worker.html" exit $STATUS fetch-2.0.3/script/saucelabs000077500000000000000000000032651305605464600160200ustar00rootroot00000000000000#!/bin/bash set -e port=8080 # Spin a test server in the background node ./script/server $port &>/dev/null & server_pid=$! trap "kill $server_pid" INT EXIT make sauce_connect/bin/sc sauce_ready="${TMPDIR:-/tmp}/sauce-ready.$$" sauce_connect/bin/sc -u "$SAUCE_USERNAME" -k "$SAUCE_ACCESS_KEY" \ -i "$TRAVIS_JOB_NUMBER" -l sauce_connect.log -f "$sauce_ready" &>/dev/null & sauce_pid=$! trap "kill $sauce_pid" INT EXIT sauce_waited=0 while [ ! -f "$sauce_ready" ]; do if [ "$sauce_waited" -gt 60000 ]; then echo "sauce_connect failed to start within 60 seconds" >&2 exit 1 fi sleep .01 sauce_waited=$((sauce_waited + 10)) done echo "sauce_connect started within $sauce_waited ms" rm -f "$sauce_ready" job="$(./script/saucelabs-api --raw "js-tests" <&2 exit 1 fi grep -q "^completed: true" <<<"$result" && break echo -n "." done echo awk ' /result\.tests:/ { tests+=$(NF) } /result\.passes:/ { passes+=$(NF) } /result\.pending:/ { pending+=$(NF) } /result\.failures:/ { failures+=$(NF) } /\.url:/ { print $(NF) } END { printf "%d passed, %d pending, %d failures\n", passes, pending, failures if (failures > 0 || tests != passes + pending || tests == 0) exit 1 } ' <<<"$result" fetch-2.0.3/script/saucelabs-api000077500000000000000000000012611305605464600165610ustar00rootroot00000000000000#!/bin/bash set -e set -o pipefail raw="" if [ "$1" = "--raw" ]; then raw="1" shift 1 fi endpoint="$1" curl -fsS -X POST "https://saucelabs.com/rest/v1/$SAUCE_USERNAME/${endpoint}" \ -u "$SAUCE_USERNAME:$SAUCE_ACCESS_KEY" \ -H "Content-Type: application/json" -d "@-" | \ { if [ -n "$raw" ]; then cat else ruby -rjson -e ' dump = lambda do |obj, ns| case obj when Array then obj.each_with_index { |v, i| dump.call(v, [ns, i]) } when Hash then obj.each { |k, v| dump.call(v, [ns, k]) } else puts "%s: %s" % [ ns.flatten.compact.join("."), obj.to_s ] end end dump.call JSON.parse(STDIN.read), nil ' fi } fetch-2.0.3/script/server000077500000000000000000000076201305605464600153630ustar00rootroot00000000000000#!/usr/bin/env node var port = Number(process.argv[2] || 3000) var fs = require('fs') var http = require('http'); var url = require('url'); var querystring = require('querystring'); var routes = { '/request': function(res, req) { res.writeHead(200, {'Content-Type': 'application/json'}); var data = '' req.on('data', function(c) { data += c }) req.on('end', function() { res.end(JSON.stringify({ method: req.method, url: req.url, headers: req.headers, data: data })); }) }, '/hello': function(res, req) { res.writeHead(200, { 'Content-Type': 'text/plain', 'X-Request-URL': 'http://' + req.headers.host + req.url }); res.end('hi'); }, '/hello/utf8': function(res) { res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' }); // "hello" var buf = new Buffer([104, 101, 108, 108, 111]); res.end(buf); }, '/hello/utf16le': function(res) { res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-16le' }); // "hello" var buf = new Buffer([104, 0, 101, 0, 108, 0, 108, 0, 111, 0]); res.end(buf); }, '/binary': function(res) { res.writeHead(200, {'Content-Type': 'application/octet-stream'}); var buf = new Buffer(256); for (var i = 0; i < 256; i++) { buf[i] = i; } res.end(buf); }, '/redirect/301': function(res) { res.writeHead(301, {'Location': '/hello'}); res.end(); }, '/redirect/302': function(res) { res.writeHead(302, {'Location': '/hello'}); res.end(); }, '/redirect/303': function(res) { res.writeHead(303, {'Location': '/hello'}); res.end(); }, '/redirect/307': function(res) { res.writeHead(307, {'Location': '/hello'}); res.end(); }, '/redirect/308': function(res) { res.writeHead(308, {'Location': '/hello'}); res.end(); }, '/boom': function(res) { res.writeHead(500, {'Content-Type': 'text/plain'}); res.end('boom'); }, '/empty': function(res) { res.writeHead(204); res.end(); }, '/error': function(res) { res.destroy(); }, '/form': function(res) { res.writeHead(200, {'Content-Type': 'application/x-www-form-urlencoded'}); res.end('number=1&space=one+two&empty=&encoded=a%2Bb&'); }, '/json': function(res) { res.writeHead(200, {'Content-Type': 'application/json'}); res.end(JSON.stringify({name: 'Hubot', login: 'hubot'})); }, '/json-error': function(res) { res.writeHead(200, {'Content-Type': 'application/json'}); res.end('not json {'); }, '/cookie': function(res, req) { var setCookie, cookie var params = querystring.parse(url.parse(req.url).query); if (params.name && params.value) { setCookie = [params.name, params.value].join('='); } if (params.name) { cookie = querystring.parse(req.headers['cookie'], '; ')[params.name]; } res.writeHead(200, {'Content-Type': 'text/plain', 'Set-Cookie': setCookie}); res.end(cookie); }, '/headers': function(res) { res.writeHead(200, { 'Date': 'Mon, 13 Oct 2014 21:02:27 GMT', 'Content-Type': 'text/html; charset=utf-8' }); res.end(); } }; var types = { js: 'application/javascript', css: 'text/css', html: 'text/html', txt: 'text/plain' }; server = http.createServer(function(req, res) { var pathname = url.parse(req.url).pathname; var route = routes[pathname]; if (route) { route(res, req); } else { if (pathname == '/') pathname = '/test/test.html' fs.readFile(__dirname + '/..' + pathname, function(err, data) { if (err) { res.writeHead(404, {'Content-Type': types.txt}); res.end('Not Found'); } else { var ext = (pathname.match(/\.([^\/]+)$/) || [])[1] res.writeHead(200, {'Content-Type': types[ext] || types.txt}); res.end(data); } }); } }); console.warn("Started test server on localhost:" + port); server.listen(port); fetch-2.0.3/script/test000077500000000000000000000001501305605464600150230ustar00rootroot00000000000000#!/bin/bash set -e if [ -n "$SAUCE_BROWSER" ]; then ./script/saucelabs else ./script/phantomjs fi fetch-2.0.3/test/000077500000000000000000000000001305605464600135755ustar00rootroot00000000000000fetch-2.0.3/test/.gitignore000066400000000000000000000000131305605464600155570ustar00rootroot00000000000000server.pid fetch-2.0.3/test/.jshintrc000066400000000000000000000005141305605464600154220ustar00rootroot00000000000000{ "extends": "../.jshintrc", "es3": false, "strict": false, "sub": true, "globals": { "fetch": false, "Headers": false, "Request": false, "Response": false, "mocha": false, "chai": false, "suite": false, "setup": false, "suiteSetup": false, "test": false, "assert": false } } fetch-2.0.3/test/mocha-phantomjs-hooks.js000066400000000000000000000004061305605464600203440ustar00rootroot00000000000000/* globals exports */ exports.beforeStart = function(context) { var originalResourceError = context.page.onResourceError context.page.onResourceError = function(resErr) { if (!/\/boom$/.test(resErr.url)) { originalResourceError(resErr) } } } fetch-2.0.3/test/test-worker.html000066400000000000000000000021141305605464600167470ustar00rootroot00000000000000 Fetch Worker Tests
fetch-2.0.3/test/test.html000066400000000000000000000033421305605464600154440ustar00rootroot00000000000000 Fetch Tests
fetch-2.0.3/test/test.js000066400000000000000000001153441305605464600151220ustar00rootroot00000000000000var support = { searchParams: 'URLSearchParams' in self, url: (function(url) { try { return new URL(url).toString() === url } catch(e) { return false } })('http://example.com/'), blob: 'FileReader' in self && 'Blob' in self && (function() { try { new Blob() return true } catch(e) { return false } })(), formData: 'FormData' in self, arrayBuffer: 'ArrayBuffer' in self, patch: !/PhantomJS/.test(navigator.userAgent), permanentRedirect: !/PhantomJS|Trident/.test(navigator.userAgent) } function readBlobAsText(blob) { if ('FileReader' in self) { return new Promise(function(resolve, reject) { var reader = new FileReader() reader.onload = function() { resolve(reader.result) } reader.onerror = function() { reject(reader.error) } reader.readAsText(blob) }) } else if ('FileReaderSync' in self) { return new FileReaderSync().readAsText(blob) } else { throw new ReferenceError('FileReader is not defined') } } function readBlobAsBytes(blob) { if ('FileReader' in self) { return new Promise(function(resolve, reject) { var reader = new FileReader() reader.onload = function() { var view = new Uint8Array(reader.result) resolve(Array.prototype.slice.call(view)) } reader.onerror = function() { reject(reader.error) } reader.readAsArrayBuffer(blob) }) } else if ('FileReaderSync' in self) { return new FileReaderSync().readAsArrayBuffer(blob) } else { throw new ReferenceError('FileReader is not defined') } } function arrayBufferFromText(text) { var buf = new ArrayBuffer(text.length) var view = new Uint8Array(buf) for (var i = 0; i < text.length; i++) { view[i] = text.charCodeAt(i) } return buf } function readArrayBufferAsText(buf) { var view = new Uint8Array(buf) var chars = new Array(view.length) for (var i = 0; i < view.length; i++) { chars[i] = String.fromCharCode(view[i]) } return chars.join('') } var preservedGlobals = {} var keepGlobals = ['fetch', 'Headers', 'Request', 'Response'] var exercise = ['polyfill'] // If native fetch implementation exists, save it and allow it to be replaced // by the polyfill. Native implementation will be exercised additionally. if (self.fetch) { keepGlobals.forEach(function(name) { preservedGlobals[name] = self[name] }) self.fetch = undefined exercise.push('native') } var slice = Array.prototype.slice function featureDependent(testOrSuite, condition) { (condition ? testOrSuite : testOrSuite.skip).apply(this, slice.call(arguments, 2)) } exercise.forEach(function(exerciseMode) { suite(exerciseMode, function() { if (exerciseMode === 'native') { suiteSetup(function() { keepGlobals.forEach(function(name) { self[name] = preservedGlobals[name] }) }) } var nativeChrome = /Chrome\//.test(navigator.userAgent) && exerciseMode === 'native' var polyfillFirefox = /Firefox\//.test(navigator.userAgent) && exerciseMode === 'polyfill' // https://fetch.spec.whatwg.org/#concept-bodyinit-extract function testBodyExtract(factory) { suite('body extract', function() { var expected = 'Hello World!' var inputs = [['type USVString', expected]] if (support.blob) { inputs.push(['type Blob', new Blob([expected])]) } if (support.arrayBuffer) { inputs = inputs.concat([ ['type ArrayBuffer', arrayBufferFromText(expected)], ['type TypedArray', new Uint8Array(arrayBufferFromText(expected))], ['type DataView', new DataView(arrayBufferFromText(expected))], ]) } inputs.forEach(function(input) { var typeLabel = input[0], body = input[1] suite(typeLabel, function() { featureDependent(test, support.blob, 'consume as blob', function() { var r = factory(body) return r.blob().then(readBlobAsText).then(function(text) { assert.equal(text, expected) }) }) test('consume as text', function() { var r = factory(body) return r.text().then(function(text) { assert.equal(text, expected) }) }) featureDependent(test, support.arrayBuffer, 'consume as array buffer', function() { var r = factory(body) return r.arrayBuffer().then(readArrayBufferAsText).then(function(text) { assert.equal(text, expected) }) }) }) }) }) } // https://fetch.spec.whatwg.org/#headers-class suite('Headers', function() { test('constructor copies headers', function() { var original = new Headers() original.append('Accept', 'application/json') original.append('Accept', 'text/plain') original.append('Content-Type', 'text/html') var headers = new Headers(original) assert.equal(headers.get('Accept'), 'application/json,text/plain') assert.equal(headers.get('Content-type'), 'text/html') }) test('constructor works with arrays', function() { var array = [ ['Content-Type', 'text/xml'], ['Breaking-Bad', '<3'] ] var headers = new Headers(array) assert.equal(headers.get('Content-Type'), 'text/xml') assert.equal(headers.get('Breaking-Bad'), '<3') }) test('headers are case insensitive', function() { var headers = new Headers({'Accept': 'application/json'}) assert.equal(headers.get('ACCEPT'), 'application/json') assert.equal(headers.get('Accept'), 'application/json') assert.equal(headers.get('accept'), 'application/json') }) test('appends to existing', function() { var headers = new Headers({'Accept': 'application/json'}) assert.isFalse(headers.has('Content-Type')) headers.append('Content-Type', 'application/json') assert.isTrue(headers.has('Content-Type')) assert.equal(headers.get('Content-Type'), 'application/json') }) test('appends values to existing header name', function() { var headers = new Headers({'Accept': 'application/json'}) headers.append('Accept', 'text/plain') assert.equal(headers.get('Accept'), 'application/json,text/plain') }) test('sets header name and value', function() { var headers = new Headers() headers.set('Content-Type', 'application/json') assert.equal(headers.get('Content-Type'), 'application/json') }) test('returns null on no header found', function() { var headers = new Headers() assert.isNull(headers.get('Content-Type')) }) test('has headers that are set', function() { var headers = new Headers() headers.set('Content-Type', 'application/json') assert.isTrue(headers.has('Content-Type')) }) test('deletes headers', function() { var headers = new Headers() headers.set('Content-Type', 'application/json') assert.isTrue(headers.has('Content-Type')) headers.delete('Content-Type') assert.isFalse(headers.has('Content-Type')) assert.isNull(headers.get('Content-Type')) }) test('converts field name to string on set and get', function() { var headers = new Headers() headers.set(1, 'application/json') assert.isTrue(headers.has('1')) assert.equal(headers.get(1), 'application/json') }) test('converts field value to string on set and get', function() { var headers = new Headers() headers.set('Content-Type', 1) headers.set('X-CSRF-Token', undefined) assert.equal(headers.get('Content-Type'), '1') assert.equal(headers.get('X-CSRF-Token'), 'undefined') }) test('throws TypeError on invalid character in field name', function() { assert.throws(function() { new Headers({'': 'application/json'}) }, TypeError) assert.throws(function() { new Headers({'Accept:': 'application/json'}) }, TypeError) assert.throws(function() { var headers = new Headers() headers.set({field: 'value'}, 'application/json') }, TypeError) }) test('is iterable with forEach', function() { var headers = new Headers() headers.append('Accept', 'application/json') headers.append('Accept', 'text/plain') headers.append('Content-Type', 'text/html') var results = [] headers.forEach(function(value, key, object) { results.push({value: value, key: key, object: object}) }) assert.equal(results.length, 2) assert.deepEqual({key: 'accept', value: 'application/json,text/plain', object: headers}, results[0]) assert.deepEqual({key: 'content-type', value: 'text/html', object: headers}, results[1]) }) test('forEach accepts second thisArg argument', function() { var headers = new Headers({'Accept': 'application/json'}) var thisArg = 42 headers.forEach(function() { assert.equal(this, thisArg) }, thisArg) }) test('is iterable with keys', function() { var headers = new Headers() headers.append('Accept', 'application/json') headers.append('Accept', 'text/plain') headers.append('Content-Type', 'text/html') var iterator = headers.keys() assert.deepEqual({done: false, value: 'accept'}, iterator.next()) assert.deepEqual({done: false, value: 'content-type'}, iterator.next()) assert.deepEqual({done: true, value: undefined}, iterator.next()) }) test('is iterable with values', function() { var headers = new Headers() headers.append('Accept', 'application/json') headers.append('Accept', 'text/plain') headers.append('Content-Type', 'text/html') var iterator = headers.values() assert.deepEqual({done: false, value: 'application/json,text/plain'}, iterator.next()) assert.deepEqual({done: false, value: 'text/html'}, iterator.next()) assert.deepEqual({done: true, value: undefined}, iterator.next()) }) test('is iterable with entries', function() { var headers = new Headers() headers.append('Accept', 'application/json') headers.append('Accept', 'text/plain') headers.append('Content-Type', 'text/html') var iterator = headers.entries() assert.deepEqual({done: false, value: ['accept', 'application/json,text/plain']}, iterator.next()) assert.deepEqual({done: false, value: ['content-type', 'text/html']}, iterator.next()) assert.deepEqual({done: true, value: undefined}, iterator.next()) }) }) // https://fetch.spec.whatwg.org/#request-class suite('Request', function() { test('construct with string url', function() { var request = new Request('https://fetch.spec.whatwg.org/') assert.equal(request.url, 'https://fetch.spec.whatwg.org/') }) featureDependent(test, support.url, 'construct with URL instance', function() { var url = new URL('https://fetch.spec.whatwg.org/') url.pathname = 'cors' var request = new Request(url) assert.equal(request.url, 'https://fetch.spec.whatwg.org/cors') }) test('construct with non-Request object', function() { var url = { toString: function() { return 'https://fetch.spec.whatwg.org/' } } var request = new Request(url) assert.equal(request.url, 'https://fetch.spec.whatwg.org/') }) test('construct with Request', function() { var request1 = new Request('https://fetch.spec.whatwg.org/', { method: 'post', body: 'I work out', headers: { accept: 'application/json', 'Content-Type': 'text/plain' } }) var request2 = new Request(request1) return request2.text().then(function(body2) { assert.equal(body2, 'I work out') assert.equal(request2.method, 'POST') assert.equal(request2.url, 'https://fetch.spec.whatwg.org/') assert.equal(request2.headers.get('accept'), 'application/json') assert.equal(request2.headers.get('content-type'), 'text/plain') return request1.text().then(function() { assert(false, 'original request body should have been consumed') }, function(error) { assert(error instanceof TypeError, 'expected TypeError for already read body') }) }) }) test('construct with Request and override headers', function() { var request1 = new Request('https://fetch.spec.whatwg.org/', { method: 'post', body: 'I work out', headers: { accept: 'application/json', 'X-Request-ID': '123' } }) var request2 = new Request(request1, { headers: { 'x-test': '42' } }) assert.equal(request2.headers.get('accept'), undefined) assert.equal(request2.headers.get('x-request-id'), undefined) assert.equal(request2.headers.get('x-test'), '42') }) test('construct with Request and override body', function() { var request1 = new Request('https://fetch.spec.whatwg.org/', { method: 'post', body: 'I work out', headers: { 'Content-Type': 'text/plain' } }) var request2 = new Request(request1, { body: '{"wiggles": 5}', headers: { 'Content-Type': 'application/json' } }) return request2.json().then(function(data) { assert.equal(data.wiggles, 5) assert.equal(request2.headers.get('content-type'), 'application/json') }) }) featureDependent(test, !nativeChrome, 'construct with used Request body', function() { var request1 = new Request('https://fetch.spec.whatwg.org/', { method: 'post', body: 'I work out' }) return request1.text().then(function() { assert.throws(function() { new Request(request1) }, TypeError) }) }) test('GET should not have implicit Content-Type', function() { var req = new Request('https://fetch.spec.whatwg.org/') assert.equal(req.headers.get('content-type'), undefined) }) test('POST with blank body should not have implicit Content-Type', function() { var req = new Request('https://fetch.spec.whatwg.org/', { method: 'post' }) assert.equal(req.headers.get('content-type'), undefined) }) test('construct with string body sets Content-Type header', function() { var req = new Request('https://fetch.spec.whatwg.org/', { method: 'post', body: 'I work out' }) assert.equal(req.headers.get('content-type'), 'text/plain;charset=UTF-8') }) featureDependent(test, support.blob, 'construct with Blob body and type sets Content-Type header', function() { var req = new Request('https://fetch.spec.whatwg.org/', { method: 'post', body: new Blob(['test'], { type: 'image/png' }) }) assert.equal(req.headers.get('content-type'), 'image/png') }) test('construct with body and explicit header uses header', function() { var req = new Request('https://fetch.spec.whatwg.org/', { method: 'post', headers: { 'Content-Type': 'image/png' }, body: 'I work out' }) assert.equal(req.headers.get('content-type'), 'image/png') }) featureDependent(test, support.blob, 'construct with Blob body and explicit Content-Type header', function() { var req = new Request('https://fetch.spec.whatwg.org/', { method: 'post', headers: { 'Content-Type': 'image/png' }, body: new Blob(['test'], { type: 'text/plain' }) }) assert.equal(req.headers.get('content-type'), 'image/png') }) featureDependent(test, support.searchParams, 'construct with URLSearchParams body sets Content-Type header', function() { var req = new Request('https://fetch.spec.whatwg.org/', { method: 'post', body: new URLSearchParams('a=1&b=2') }) assert.equal(req.headers.get('content-type'), 'application/x-www-form-urlencoded;charset=UTF-8') }) featureDependent(test, support.searchParams, 'construct with URLSearchParams body and explicit Content-Type header', function() { var req = new Request('https://fetch.spec.whatwg.org/', { method: 'post', headers: { 'Content-Type': 'image/png' }, body: new URLSearchParams('a=1&b=2') }) assert.equal(req.headers.get('content-type'), 'image/png') }) test('clone GET request', function() { var req = new Request('https://fetch.spec.whatwg.org/', { headers: {'content-type': 'text/plain'} }) var clone = req.clone() assert.equal(clone.url, req.url) assert.equal(clone.method, 'GET') assert.equal(clone.headers.get('content-type'), 'text/plain') assert.notEqual(clone.headers, req.headers) assert.isFalse(req.bodyUsed) }) test('clone POST request', function() { var req = new Request('https://fetch.spec.whatwg.org/', { method: 'post', headers: {'content-type': 'text/plain'}, body: 'I work out' }) var clone = req.clone() assert.equal(clone.method, 'POST') assert.equal(clone.headers.get('content-type'), 'text/plain') assert.notEqual(clone.headers, req.headers) assert.equal(req.bodyUsed, false) return Promise.all([clone.text(), req.clone().text()]).then(function(bodies) { assert.deepEqual(bodies, ['I work out', 'I work out']) }) }) featureDependent(test, !nativeChrome, 'clone with used Request body', function() { var req = new Request('https://fetch.spec.whatwg.org/', { method: 'post', body: 'I work out' }) return req.text().then(function() { assert.throws(function() { req.clone() }, TypeError) }) }) testBodyExtract(function(body) { return new Request('', { method: 'POST', body: body }) }) }) // https://fetch.spec.whatwg.org/#response-class suite('Response', function() { test('default status is 200 OK', function() { var res = new Response() assert.equal(res.status, 200) assert.equal(res.statusText, 'OK') assert.isTrue(res.ok) }) testBodyExtract(function(body) { return new Response(body) }) test('creates Headers object from raw headers', function() { var r = new Response('{"foo":"bar"}', {headers: {'content-type': 'application/json'}}) assert.equal(r.headers instanceof Headers, true) return r.json().then(function(json){ assert.equal(json.foo, 'bar') return json }) }) test('always creates a new Headers instance', function() { var headers = new Headers({ 'x-hello': 'world' }) var res = new Response('', {headers: headers}) assert.equal(res.headers.get('x-hello'), 'world') assert.notEqual(res.headers, headers) }) test('clone text response', function() { var res = new Response('{"foo":"bar"}', { headers: {'content-type': 'application/json'} }) var clone = res.clone() assert.notEqual(clone.headers, res.headers, 'headers were cloned') assert.equal(clone.headers.get('content-type'), 'application/json') return Promise.all([clone.json(), res.json()]).then(function(jsons){ assert.deepEqual(jsons[0], jsons[1], 'json of cloned object is the same as original') }) }) featureDependent(test, support.blob, 'clone blob response', function() { var req = new Request(new Blob(['test'])) req.clone() assert.equal(req.bodyUsed, false) }) test('error creates error Response', function() { var r = Response.error() assert(r instanceof Response) assert.equal(r.status, 0) assert.equal(r.statusText, '') assert.equal(r.type, 'error') }) test('redirect creates redirect Response', function() { var r = Response.redirect('https://fetch.spec.whatwg.org/', 301) assert(r instanceof Response) assert.equal(r.status, 301) assert.equal(r.headers.get('Location'), 'https://fetch.spec.whatwg.org/') }) test('construct with string body sets Content-Type header', function() { var r = new Response('I work out') assert.equal(r.headers.get('content-type'), 'text/plain;charset=UTF-8') }) featureDependent(test, support.blob, 'construct with Blob body and type sets Content-Type header', function() { var r = new Response(new Blob(['test'], { type: 'text/plain' })) assert.equal(r.headers.get('content-type'), 'text/plain') }) test('construct with body and explicit header uses header', function() { var r = new Response('I work out', { headers: { 'Content-Type': 'text/plain' }, }) assert.equal(r.headers.get('content-type'), 'text/plain') }) }) // https://fetch.spec.whatwg.org/#body-mixin suite('Body mixin', function() { featureDependent(suite, support.blob, 'arrayBuffer', function() { test('resolves arrayBuffer promise', function() { return fetch('/hello').then(function(response) { return response.arrayBuffer() }).then(function(buf) { assert(buf instanceof ArrayBuffer, 'buf is an ArrayBuffer instance') assert.equal(buf.byteLength, 2) }) }) test('arrayBuffer handles binary data', function() { return fetch('/binary').then(function(response) { return response.arrayBuffer() }).then(function(buf) { assert(buf instanceof ArrayBuffer, 'buf is an ArrayBuffer instance') assert.equal(buf.byteLength, 256, 'buf.byteLength is correct') var view = new Uint8Array(buf) for (var i = 0; i < 256; i++) { assert.equal(view[i], i) } }) }) test('arrayBuffer handles utf-8 data', function() { return fetch('/hello/utf8').then(function(response) { return response.arrayBuffer() }).then(function(buf) { assert(buf instanceof ArrayBuffer, 'buf is an ArrayBuffer instance') assert.equal(buf.byteLength, 5, 'buf.byteLength is correct') var octets = Array.prototype.slice.call(new Uint8Array(buf)) assert.deepEqual(octets, [104, 101, 108, 108, 111]) }) }) test('arrayBuffer handles utf-16le data', function() { return fetch('/hello/utf16le').then(function(response) { return response.arrayBuffer() }).then(function(buf) { assert(buf instanceof ArrayBuffer, 'buf is an ArrayBuffer instance') assert.equal(buf.byteLength, 10, 'buf.byteLength is correct') var octets = Array.prototype.slice.call(new Uint8Array(buf)) assert.deepEqual(octets, [104, 0, 101, 0, 108, 0, 108, 0, 111, 0]) }) }) test('rejects arrayBuffer promise after body is consumed', function() { return fetch('/hello').then(function(response) { assert.equal(response.bodyUsed, false) response.blob() assert.equal(response.bodyUsed, true) return response.arrayBuffer() }).catch(function(error) { assert(error instanceof TypeError, 'Promise rejected after body consumed') }) }) }) featureDependent(suite, support.blob, 'blob', function() { test('resolves blob promise', function() { return fetch('/hello').then(function(response) { return response.blob() }).then(function(blob) { assert(blob instanceof Blob, 'blob is a Blob instance') assert.equal(blob.size, 2) }) }) test('blob handles binary data', function() { return fetch('/binary').then(function(response) { return response.blob() }).then(function(blob) { assert(blob instanceof Blob, 'blob is a Blob instance') assert.equal(blob.size, 256, 'blob.size is correct') }) }) test('blob handles utf-8 data', function() { return fetch('/hello/utf8').then(function(response) { return response.blob() }).then(readBlobAsBytes).then(function(octets) { assert.equal(octets.length, 5, 'blob.size is correct') assert.deepEqual(octets, [104, 101, 108, 108, 111]) }) }) test('blob handles utf-16le data', function() { return fetch('/hello/utf16le').then(function(response) { return response.blob() }).then(readBlobAsBytes).then(function(octets) { assert.equal(octets.length, 10, 'blob.size is correct') assert.deepEqual(octets, [104, 0, 101, 0, 108, 0, 108, 0, 111, 0]) }) }) test('rejects blob promise after body is consumed', function() { return fetch('/hello').then(function(response) { assert(response.blob, 'Body does not implement blob') assert.equal(response.bodyUsed, false) response.text() assert.equal(response.bodyUsed, true) return response.blob() }).catch(function(error) { assert(error instanceof TypeError, 'Promise rejected after body consumed') }) }) }) featureDependent(suite, support.formData, 'formData', function() { test('post sets content-type header', function() { return fetch('/request', { method: 'post', body: new FormData() }).then(function(response) { return response.json() }).then(function(json) { assert.equal(json.method, 'POST') assert(/^multipart\/form-data;/.test(json.headers['content-type'])) }) }) featureDependent(test, !nativeChrome, 'rejects formData promise after body is consumed', function() { return fetch('/json').then(function(response) { assert(response.formData, 'Body does not implement formData') response.formData() return response.formData() }).catch(function(error) { if (error instanceof chai.AssertionError) { throw error } else { assert(error instanceof TypeError, 'Promise rejected after body consumed') } }) }) featureDependent(test, !nativeChrome, 'parses form encoded response', function() { return fetch('/form').then(function(response) { return response.formData() }).then(function(form) { assert(form instanceof FormData, 'Parsed a FormData object') }) }) }) suite('json', function() { test('parses json response', function() { return fetch('/json').then(function(response) { return response.json() }).then(function(json) { assert.equal(json.name, 'Hubot') assert.equal(json.login, 'hubot') }) }) test('rejects json promise after body is consumed', function() { return fetch('/json').then(function(response) { assert(response.json, 'Body does not implement json') assert.equal(response.bodyUsed, false) response.text() assert.equal(response.bodyUsed, true) return response.json() }).catch(function(error) { assert(error instanceof TypeError, 'Promise rejected after body consumed') }) }) featureDependent(test, !polyfillFirefox, 'handles json parse error', function() { return fetch('/json-error').then(function(response) { return response.json() }).catch(function(error) { assert(error instanceof Error, 'JSON exception is an Error instance') assert(error.message, 'JSON exception has an error message') }) }) }) suite('text', function() { test('handles 204 No Content response', function() { return fetch('/empty').then(function(response) { assert.equal(response.status, 204) return response.text() }).then(function(body) { assert.equal(body, '') }) }) test('resolves text promise', function() { return fetch('/hello').then(function(response) { return response.text() }).then(function(text) { assert.equal(text, 'hi') }) }) test('rejects text promise after body is consumed', function() { return fetch('/hello').then(function(response) { assert(response.text, 'Body does not implement text') assert.equal(response.bodyUsed, false) response.text() assert.equal(response.bodyUsed, true) return response.text() }).catch(function(error) { assert(error instanceof TypeError, 'Promise rejected after body consumed') }) }) }) }) suite('fetch method', function() { suite('promise resolution', function() { test('resolves promise on 500 error', function() { return fetch('/boom').then(function(response) { assert.equal(response.status, 500) assert.equal(response.ok, false) return response.text() }).then(function(body) { assert.equal(body, 'boom') }) }) test.skip('rejects promise for network error', function() { return fetch('/error').then(function(response) { assert(false, 'HTTP status ' + response.status + ' was treated as success') }).catch(function(error) { assert(error instanceof TypeError, 'Rejected with Error') }) }) test('rejects when Request constructor throws', function() { return fetch('/request', { method: 'GET', body: 'invalid' }).then(function() { assert(false, 'Invalid Request init was accepted') }).catch(function(error) { assert(error instanceof TypeError, 'Rejected with Error') }) }) }) suite('request', function() { test('sends headers', function() { return fetch('/request', { headers: { 'Accept': 'application/json', 'X-Test': '42' } }).then(function(response) { return response.json() }).then(function(json) { assert.equal(json.headers['accept'], 'application/json') assert.equal(json.headers['x-test'], '42') }) }) test('with Request as argument', function() { var request = new Request('/request', { headers: { 'Accept': 'application/json', 'X-Test': '42' } }) return fetch(request).then(function(response) { return response.json() }).then(function(json) { assert.equal(json.headers['accept'], 'application/json') assert.equal(json.headers['x-test'], '42') }) }) test('reusing same Request multiple times', function() { var request = new Request('/request', { headers: { 'Accept': 'application/json', 'X-Test': '42' } }) var responses = [] return fetch(request).then(function(response) { responses.push(response) return fetch(request) }).then(function(response) { responses.push(response) return fetch(request) }).then(function(response) { responses.push(response) return Promise.all(responses.map(function(r) { return r.json() })) }).then(function(jsons) { jsons.forEach(function(json) { assert.equal(json.headers['accept'], 'application/json') assert.equal(json.headers['x-test'], '42') }) }) }) featureDependent(suite, support.arrayBuffer, 'ArrayBuffer', function() { test('ArrayBuffer body', function() { return fetch('/request', { method: 'post', body: arrayBufferFromText('name=Hubot') }).then(function(response) { return response.json() }).then(function(request) { assert.equal(request.method, 'POST') assert.equal(request.data, 'name=Hubot') }) }) test('DataView body', function() { return fetch('/request', { method: 'post', body: new DataView(arrayBufferFromText('name=Hubot')) }).then(function(response) { return response.json() }).then(function(request) { assert.equal(request.method, 'POST') assert.equal(request.data, 'name=Hubot') }) }) test('TypedArray body', function() { return fetch('/request', { method: 'post', body: new Uint8Array(arrayBufferFromText('name=Hubot')) }).then(function(response) { return response.json() }).then(function(request) { assert.equal(request.method, 'POST') assert.equal(request.data, 'name=Hubot') }) }) }) featureDependent(test, support.searchParams, 'sends URLSearchParams body', function() { return fetch('/request', { method: 'post', body: new URLSearchParams('a=1&b=2') }).then(function(response) { return response.json() }).then(function(request) { assert.equal(request.method, 'POST') assert.equal(request.data, 'a=1&b=2') }) }) }) suite('response', function() { test('populates body', function() { return fetch('/hello').then(function(response) { assert.equal(response.status, 200) assert.equal(response.ok, true) return response.text() }).then(function(body) { assert.equal(body, 'hi') }) }) test('parses headers', function() { return fetch('/headers?' + new Date().getTime()).then(function(response) { assert.equal(response.headers.get('Date'), 'Mon, 13 Oct 2014 21:02:27 GMT') assert.equal(response.headers.get('Content-Type'), 'text/html; charset=utf-8') }) }) }) // https://fetch.spec.whatwg.org/#methods suite('HTTP methods', function() { test('supports HTTP GET', function() { return fetch('/request', { method: 'get', }).then(function(response) { return response.json() }).then(function(request) { assert.equal(request.method, 'GET') assert.equal(request.data, '') }) }) test('GET with body throws TypeError', function() { assert.throw(function() { new Request('', { method: 'get', body: 'invalid' }) }, TypeError) }) test('HEAD with body throws TypeError', function() { assert.throw(function() { new Request('', { method: 'head', body: 'invalid' }) }, TypeError) }) test('supports HTTP POST', function() { return fetch('/request', { method: 'post', body: 'name=Hubot' }).then(function(response) { return response.json() }).then(function(request) { assert.equal(request.method, 'POST') assert.equal(request.data, 'name=Hubot') }) }) test('supports HTTP PUT', function() { return fetch('/request', { method: 'put', body: 'name=Hubot' }).then(function(response) { return response.json() }).then(function(request) { assert.equal(request.method, 'PUT') assert.equal(request.data, 'name=Hubot') }) }) featureDependent(test, support.patch, 'supports HTTP PATCH', function() { return fetch('/request', { method: 'PATCH', body: 'name=Hubot' }).then(function(response) { return response.json() }).then(function(request) { assert.equal(request.method, 'PATCH') assert.equal(request.data, 'name=Hubot') }) }) test('supports HTTP DELETE', function() { return fetch('/request', { method: 'delete', }).then(function(response) { return response.json() }).then(function(request) { assert.equal(request.method, 'DELETE') assert.equal(request.data, '') }) }) }) // https://fetch.spec.whatwg.org/#atomic-http-redirect-handling suite('Atomic HTTP redirect handling', function() { test('handles 301 redirect response', function() { return fetch('/redirect/301').then(function(response) { assert.equal(response.status, 200) assert.equal(response.ok, true) assert.match(response.url, /\/hello/) return response.text() }).then(function(body) { assert.equal(body, 'hi') }) }) test('handles 302 redirect response', function() { return fetch('/redirect/302').then(function(response) { assert.equal(response.status, 200) assert.equal(response.ok, true) assert.match(response.url, /\/hello/) return response.text() }).then(function(body) { assert.equal(body, 'hi') }) }) test('handles 303 redirect response', function() { return fetch('/redirect/303').then(function(response) { assert.equal(response.status, 200) assert.equal(response.ok, true) assert.match(response.url, /\/hello/) return response.text() }).then(function(body) { assert.equal(body, 'hi') }) }) test('handles 307 redirect response', function() { return fetch('/redirect/307').then(function(response) { assert.equal(response.status, 200) assert.equal(response.ok, true) assert.match(response.url, /\/hello/) return response.text() }).then(function(body) { assert.equal(body, 'hi') }) }) featureDependent(test, support.permanentRedirect, 'handles 308 redirect response', function() { return fetch('/redirect/308').then(function(response) { assert.equal(response.status, 200) assert.equal(response.ok, true) assert.match(response.url, /\/hello/) return response.text() }).then(function(body) { assert.equal(body, 'hi') }) }) }) // https://fetch.spec.whatwg.org/#concept-request-credentials-mode suite('credentials mode', function() { setup(function() { return fetch('/cookie?name=foo&value=reset', {credentials: 'same-origin'}) }) featureDependent(suite, exerciseMode === 'native', 'omit', function() { test('request credentials defaults to omit', function() { var request = new Request('') assert.equal(request.credentials, 'omit') }) test('does not accept cookies with implicit omit credentials', function() { return fetch('/cookie?name=foo&value=bar').then(function() { return fetch('/cookie?name=foo', {credentials: 'same-origin'}) }).then(function(response) { return response.text() }).then(function(data) { assert.equal(data, 'reset') }) }) test('does not accept cookies with omit credentials', function() { return fetch('/cookie?name=foo&value=bar', {credentials: 'omit'}).then(function() { return fetch('/cookie?name=foo', {credentials: 'same-origin'}) }).then(function(response) { return response.text() }).then(function(data) { assert.equal(data, 'reset') }) }) test('does not send cookies with implicit omit credentials', function() { return fetch('/cookie?name=foo&value=bar', {credentials: 'same-origin'}).then(function() { return fetch('/cookie?name=foo') }).then(function(response) { return response.text() }).then(function(data) { assert.equal(data, '') }) }) test('does not send cookies with omit credentials', function() { return fetch('/cookie?name=foo&value=bar').then(function() { return fetch('/cookie?name=foo', {credentials: 'omit'}) }).then(function(response) { return response.text() }).then(function(data) { assert.equal(data, '') }) }) }) suite('same-origin', function() { test('request credentials uses inits member', function() { var request = new Request('', {credentials: 'same-origin'}) assert.equal(request.credentials, 'same-origin') }) test('send cookies with same-origin credentials', function() { return fetch('/cookie?name=foo&value=bar', {credentials: 'same-origin'}).then(function() { return fetch('/cookie?name=foo', {credentials: 'same-origin'}) }).then(function(response) { return response.text() }).then(function(data) { assert.equal(data, 'bar') }) }) }) suite('include', function() { test('send cookies with include credentials', function() { return fetch('/cookie?name=foo&value=bar', {credentials: 'include'}).then(function() { return fetch('/cookie?name=foo', {credentials: 'include'}) }).then(function(response) { return response.text() }).then(function(data) { assert.equal(data, 'bar') }) }) }) }) }) }) }) fetch-2.0.3/test/worker.js000066400000000000000000000015161305605464600154470ustar00rootroot00000000000000importScripts('/node_modules/chai/chai.js') importScripts('/node_modules/mocha/mocha.js') mocha.setup('tdd') self.assert = chai.assert importScripts('/node_modules/promise-polyfill/promise.js') importScripts('/test/test.js') importScripts('/fetch.js') function title(test) { return test.fullTitle().replace(/#/g, ''); } function reporter(runner) { runner.on('pending', function(test){ self.postMessage({name: 'pending', title: title(test)}); }); runner.on('pass', function(test){ self.postMessage({name: 'pass', title: title(test)}); }); runner.on('fail', function(test, err){ self.postMessage({ name: 'fail', title: title(test), message: err.message, stack: err.stack }); }); runner.on('end', function(){ self.postMessage({name: 'end'}); }); } mocha.reporter(reporter).run()