├── .gitignore
├── .jshintrc
├── .senv
├── .travis.yml
├── LICENSE
├── MAINTAINING.md
├── Makefile
├── README.md
├── bower.json
├── examples
└── index.html
├── fetch.js
├── package.json
├── script
├── phantomjs
├── saucelabs
├── saucelabs-result
├── saucelabs-start
├── saucelabs-status
├── server
└── test
├── src
└── assert.js
└── test
├── .gitignore
├── .jshintrc
├── test-worker.html
├── test.html
├── test.js
└── worker.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | bower_components/
3 | node_modules/
4 | test/assert.js
5 | test/es5shimsham.js
6 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "curly": true,
3 | "eqeqeq": true,
4 | "es3": true,
5 | "immed": true,
6 | "indent": 2,
7 | "latedef": true,
8 | "newcap": true,
9 | "noarg": true,
10 | "quotmark": true,
11 | "undef": true,
12 | "unused": true,
13 | "strict": true,
14 | "trailing": true,
15 | "asi": true,
16 | "boss": true,
17 | "esnext": true,
18 | "eqnull": true,
19 | "browser": true,
20 | "worker": true
21 | }
22 |
--------------------------------------------------------------------------------
/.senv:
--------------------------------------------------------------------------------
1 | hqhBWY7QnNkyaM/nUOnpYMUWhjMyWw8bl6EJOPSxHlgopMEfoIOestV3mhVEwEKTpTnHtx9pv+zl62lYr6kBrVfSaDg7uF+WEMXlMo/HecmE3Qz21JLkBDZYc4JAPvjF9dObU1WSVAHI4uFsVcCrylzulR4DnV3zWum4l+TvJ0rcXBCISeNPLj/YQxyJMDm+QSSDY6Sg1LXNHWUU6ORLKrW4XrIZmL4WxEIm7IXThK7gNslojDlz7krRJ8XIS5nCx/kI7L7ZfJwd3VG+d56kW7a97U1OJcjNXVL1L3OwX+oqKAu4ojOehkTBepmnOtu62LRHd1Jgr07faT++hvL5sA==
2 | SWg+ILWFcdwg0rU+8fYUoZSjxd1MWmRBJGS5EpbFvrD0Lzdv2lGmrfTMfDGjV0nmpnZhSkAXcWJENw7J9XRQAMmbGq93NL8K0Tv1B49xhuIffIkJlmRP3fosIJGrpHBZWa8v/87Jtirqgg58xisjh7LZRrhrHiw3BPeZumv2t7YO1YzAGfcxthcdtxPs+jBl8iufvhO03/oE8Gd7d3FjfnOBDJwyoTcYTNvTXdh85GINJRKhYhSdq1pq+ASDNxIvfEBUhLzwvQsG4ScuDcUpC7T63dfH+BhtakT2EkC/8w5vRw9jK7DM2UevOSnLZroC05CQDawz453X6CxgLlwJJg==
3 | ZaPgL6eMy+Gg1ti+ZYH09tXH+pUA0oEaPMb5vTsxUye0Hqam2zRtJ5AYlZQO1lTp0IuJjFTTuxve07tfy0MPgP9Bntq9YkB06+kRMVJPRB1kSAoq/tTGv1binbe4g8bN9B7LFu1/ODPyNVPEOaisy4WjRBym8t5n59cQFl4keQqqoB+fWUf4DVXN+7Xi84wy1gSanwH3jKCVtOhBzaWIKY+pLk2qYYLUUuKlB5SVk9ywEzFOoStVlZXGemOVVlXrtOg3wW83cce5zAmdbrrQz+nAquPuZepDmVvmg1mDdQ30BQ3p6BJixwyvsBRtdVQAJEr98nRMpnkyylaQNKAlcw==
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | before_script: make
3 | script: make test
4 | env:
5 | global:
6 | - SAUCE_USERNAME=github-fetch
7 | - SAUCE_ACCESS_KEY=c3d37f93-0c2e-4834-9da5-eddc0d8c6299
8 | matrix:
9 | - PHANTOMJS=1
10 | - SAUCE_PLATFORM="Windows 7" SAUCE_BROWSER="googlechrome" SAUCE_VERSION=""
11 | - SAUCE_PLATFORM="Windows 7" SAUCE_BROWSER="internet explorer" SAUCE_VERSION="11"
12 | - SAUCE_PLATFORM="Windows 7" SAUCE_BROWSER="internet explorer" SAUCE_VERSION="10"
13 | - SAUCE_PLATFORM="Windows 7" SAUCE_BROWSER="internet explorer" SAUCE_VERSION="9"
14 | addons:
15 | sauce_connect: true
16 | deploy:
17 | provider: npm
18 | email: matt@mattandre.ws
19 | api_key:
20 | secure: FcQZz0HCJhrz8FZyyfkuXj4cwoBP+PeQ7LzOVU1bG7v3kX7Z33n8glD+32QScT2Uu369exdjkk3mzCaCMsfZTTvRm9STnuJIrPtdB2/FwfaWiyJiB1oZ2UCd5UQM0zMiQrtg+gR8FUBBgi3GICOkzAqTbso+C7P2IJtvpP9RTTI=
21 | on:
22 | all_branches: true
23 | tags: true
24 | repo: github/fetch
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 GitHub, Inc.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/MAINTAINING.md:
--------------------------------------------------------------------------------
1 | # Maintaining
2 |
3 | ## Releasing a new version
4 |
5 | This project follows [semver](http://semver.org/). So if you are making a bug
6 | fix, only increment the patch level "1.0.x". If any new files are added, a
7 | minor version "1.x.x" bump is in order.
8 |
9 | ### Make a release commit
10 |
11 | To prepare the release commit:
12 |
13 | 1. Edit the [bower.json](https://github.com/github/fetch/blob/master/bower.json)
14 | `version` value.
15 | 2. Change the npm [package.json](https://github.com/github/fetch/blob/master/package.json)
16 | `version` value to match.
17 | 3. Make a single commit with the description as "Fetch 1.x.x".
18 | 4. Finally, tag the commit with `v1.x.x`.
19 |
20 | ```
21 | $ git pull
22 | $ vim bower.json
23 | $ vim package.json
24 | $ git add bower.json package.json
25 | $ git commit -m "Fetch 1.x.x"
26 | $ git tag v1.x.x
27 | $ git push
28 | $ git push --tags
29 | ```
30 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | build: node_modules/ assert es5shimsham
2 |
3 | es5shimsham:
4 | (curl https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.1.1/es5-shim.min.js; echo ''; curl https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.1.1/es5-sham.min.js) > test/es5shimsham.js
5 |
6 | test: node_modules/ build lint
7 | ./script/test
8 |
9 | lint: node_modules/
10 | ./node_modules/.bin/jshint *.js test/*.js
11 |
12 | assert: node_modules/
13 | ./node_modules/.bin/browserify src/assert.js -s assert > test/assert.js
14 |
15 | node_modules/:
16 | npm install
17 |
18 | clean:
19 | rm -rf ./node_modules
20 |
21 | .PHONY: build clean lint test saucelabs travis
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # window.fetch polyfill
2 |
3 | **This fork supports IE8 with es5-shim, es5-sham and es6-promise**
4 |
5 | **This module is out of maintanance, use [fetch-ie8](https://www.npmjs.com/package/fetch-ie8) instead. or you can try [promisingagent](https://www.npmjs.com/package/promisingagent) if you want to use promise api for ajax and don't need ServiceWorker**
6 |
7 | The global `fetch` function is an easier way to make web requests and handle
8 | responses than using an XMLHttpRequest. This polyfill is written as closely as
9 | possible to the standard Fetch specification at https://fetch.spec.whatwg.org.
10 |
11 | ## Installation
12 |
13 | Available on [Bower](http://bower.io) as **fetch-polyfill**.
14 |
15 | ```sh
16 | $ bower install fetch-polyfill
17 | ```
18 |
19 | You'll also need a Promise polyfill for older browsers.
20 |
21 | ```sh
22 | $ bower install es6-promise
23 | ```
24 |
25 | This can also be installed with `npm`.
26 |
27 | ```sh
28 | $ npm install fetch-polyfill --save
29 | ```
30 |
31 | (For a node.js implementation, try [node-fetch](https://github.com/bitinn/node-fetch))
32 |
33 | ## Usage
34 |
35 | The `fetch` function supports any HTTP method. We'll focus on GET and POST
36 | example requests.
37 |
38 | ### HTML
39 |
40 | ```javascript
41 | fetch('/users.html')
42 | .then(function(response) {
43 | return response.text()
44 | }).then(function(body) {
45 | document.body.innerHTML = body
46 | })
47 | ```
48 |
49 | ### JSON
50 |
51 | ```javascript
52 | fetch('/users.json')
53 | .then(function(response) {
54 | return response.json()
55 | }).then(function(json) {
56 | console.log('parsed json', json)
57 | }).catch(function(ex) {
58 | console.log('parsing failed', ex)
59 | })
60 | ```
61 |
62 | ### Response metadata
63 |
64 | ```javascript
65 | fetch('/users.json').then(function(response) {
66 | console.log(response.headers.get('Content-Type'))
67 | console.log(response.headers.get('Date'))
68 | console.log(response.status)
69 | console.log(response.statusText)
70 | })
71 | ```
72 |
73 | ### Post form
74 |
75 | ```javascript
76 | var form = document.querySelector('form')
77 |
78 | fetch('/query', {
79 | method: 'post',
80 | body: new FormData(form)
81 | })
82 | ```
83 |
84 | ### Post JSON
85 |
86 | ```javascript
87 | fetch('/users', {
88 | method: 'post',
89 | headers: {
90 | 'Accept': 'application/json',
91 | 'Content-Type': 'application/json'
92 | },
93 | body: JSON.stringify({
94 | name: 'Hubot',
95 | login: 'hubot',
96 | })
97 | })
98 | ```
99 |
100 | ### File upload
101 |
102 | ```javascript
103 | var input = document.querySelector('input[type="file"]')
104 |
105 | var form = new FormData()
106 | form.append('file', input.files[0])
107 | form.append('user', 'hubot')
108 |
109 | fetch('/avatars', {
110 | method: 'post',
111 | body: form
112 | })
113 | ```
114 |
115 | ### Success and error handlers
116 |
117 | This causes `fetch` to behave like jQuery's `$.ajax` by rejecting the `Promise`
118 | on HTTP failure status codes like 404, 500, etc. The response `Promise` is
119 | resolved only on successful, 200 level, status codes.
120 |
121 | ```javascript
122 | function status(response) {
123 | if (response.status >= 200 && response.status < 300) {
124 | return response
125 | }
126 | throw new Error(response.statusText)
127 | }
128 |
129 | function json(response) {
130 | return response.json()
131 | }
132 |
133 | fetch('/users')
134 | .then(status)
135 | .then(json)
136 | .then(function(json) {
137 | console.log('request succeeded with json response', json)
138 | }).catch(function(error) {
139 | console.log('request failed', error)
140 | })
141 | ```
142 |
143 | ### Response URL caveat
144 |
145 | The `Response` object has a URL attribute for the final responded resource.
146 | Usually this is the same as the `Request` url, but in the case of a redirect,
147 | its all transparent. Newer versions of XHR include a `responseURL` attribute
148 | that returns this value. But not every browser supports this. The compromise
149 | requires setting a special server side header to tell the browser what URL it
150 | just requested (yeah, I know browsers).
151 |
152 | ``` ruby
153 | response.headers['X-Request-URL'] = request.url
154 | ```
155 |
156 | If you want `response.url` to be reliable, you'll want to set this header. The
157 | day that you ditch this polyfill and use native fetch only, you can remove the
158 | header hack.
159 |
160 | ## Browser Support
161 |
162 |  |  |  |  | 
163 | --- | --- | --- | --- | --- |
164 | Latest ✔ | Latest ✔ | 8+ ✔ | Latest ✔ | 6.1+ ✔ |
165 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fetch-polyfill",
3 | "version": "0.8.1",
4 | "main": "fetch.js",
5 | "devDependencies": {
6 | "es6-promise": "2.1.0"
7 | },
8 | "ignore": [
9 | ".*",
10 | "*.md",
11 | "examples/",
12 | "Makefile",
13 | "package.json",
14 | "script/",
15 | "test/"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/fetch.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | if (self.fetch) {
5 | return
6 | }
7 |
8 | function normalizeName(name) {
9 | if (typeof name !== 'string') {
10 | name = name.toString();
11 | }
12 | if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) {
13 | throw new TypeError('Invalid character in header field name')
14 | }
15 | return name.toLowerCase()
16 | }
17 |
18 | function normalizeValue(value) {
19 | if (typeof value !== 'string') {
20 | value = value.toString();
21 | }
22 | return value
23 | }
24 |
25 | function Headers(headers) {
26 | this.map = {}
27 |
28 | var self = this
29 | if (headers instanceof Headers) {
30 | headers.forEach(function(name, values) {
31 | values.forEach(function(value) {
32 | self.append(name, value)
33 | })
34 | })
35 |
36 | } else if (headers) {
37 | Object.getOwnPropertyNames(headers).forEach(function(name) {
38 | self.append(name, headers[name])
39 | })
40 | }
41 | }
42 |
43 | Headers.prototype.append = function(name, value) {
44 | name = normalizeName(name)
45 | value = normalizeValue(value)
46 | var list = this.map[name]
47 | if (!list) {
48 | list = []
49 | this.map[name] = list
50 | }
51 | list.push(value)
52 | }
53 |
54 | Headers.prototype['delete'] = function(name) {
55 | delete this.map[normalizeName(name)]
56 | }
57 |
58 | Headers.prototype.get = function(name) {
59 | var values = this.map[normalizeName(name)]
60 | return values ? values[0] : null
61 | }
62 |
63 | Headers.prototype.getAll = function(name) {
64 | return this.map[normalizeName(name)] || []
65 | }
66 |
67 | Headers.prototype.has = function(name) {
68 | return this.map.hasOwnProperty(normalizeName(name))
69 | }
70 |
71 | Headers.prototype.set = function(name, value) {
72 | this.map[normalizeName(name)] = [normalizeValue(value)]
73 | }
74 |
75 | // Instead of iterable for now.
76 | Headers.prototype.forEach = function(callback) {
77 | var self = this
78 | Object.getOwnPropertyNames(this.map).forEach(function(name) {
79 | callback(name, self.map[name])
80 | })
81 | }
82 |
83 | function consumed(body) {
84 | if (body.bodyUsed) {
85 | return fetch.Promise.reject(new TypeError('Already read'))
86 | }
87 | body.bodyUsed = true
88 | }
89 |
90 | function fileReaderReady(reader) {
91 | return new fetch.Promise(function(resolve, reject) {
92 | reader.onload = function() {
93 | resolve(reader.result)
94 | }
95 | reader.onerror = function() {
96 | reject(reader.error)
97 | }
98 | })
99 | }
100 |
101 | function readBlobAsArrayBuffer(blob) {
102 | var reader = new FileReader()
103 | reader.readAsArrayBuffer(blob)
104 | return fileReaderReady(reader)
105 | }
106 |
107 | function readBlobAsText(blob) {
108 | var reader = new FileReader()
109 | reader.readAsText(blob)
110 | return fileReaderReady(reader)
111 | }
112 |
113 | var support = {
114 | blob: 'FileReader' in self && 'Blob' in self && (function() {
115 | try {
116 | new Blob();
117 | return true
118 | } catch(e) {
119 | return false
120 | }
121 | })(),
122 | formData: 'FormData' in self
123 | }
124 |
125 | function Body() {
126 | this.bodyUsed = false
127 |
128 |
129 | this._initBody = function(body) {
130 | this._bodyInit = body
131 | if (typeof body === 'string') {
132 | this._bodyText = body
133 | } else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
134 | this._bodyBlob = body
135 | } else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
136 | this._bodyFormData = body
137 | } else if (!body) {
138 | this._bodyText = ''
139 | } else {
140 | throw new Error('unsupported BodyInit type')
141 | }
142 | }
143 |
144 | if (support.blob) {
145 | this.blob = function() {
146 | var rejected = consumed(this)
147 | if (rejected) {
148 | return rejected
149 | }
150 |
151 | if (this._bodyBlob) {
152 | return fetch.Promise.resolve(this._bodyBlob)
153 | } else if (this._bodyFormData) {
154 | throw new Error('could not read FormData body as blob')
155 | } else {
156 | return fetch.Promise.resolve(new Blob([this._bodyText]))
157 | }
158 | }
159 |
160 | this.arrayBuffer = function() {
161 | return this.blob().then(readBlobAsArrayBuffer)
162 | }
163 |
164 | this.text = function() {
165 | var rejected = consumed(this)
166 | if (rejected) {
167 | return rejected
168 | }
169 |
170 | if (this._bodyBlob) {
171 | return readBlobAsText(this._bodyBlob)
172 | } else if (this._bodyFormData) {
173 | throw new Error('could not read FormData body as text')
174 | } else {
175 | return fetch.Promise.resolve(this._bodyText)
176 | }
177 | }
178 | } else {
179 | this.text = function() {
180 | var rejected = consumed(this)
181 | return rejected ? rejected : fetch.Promise.resolve(this._bodyText)
182 | }
183 | }
184 |
185 | if (support.formData) {
186 | this.formData = function() {
187 | return this.text().then(decode)
188 | }
189 | }
190 |
191 | this.json = function() {
192 | return this.text().then(function (text) {
193 | return JSON.parse(text);
194 | });
195 | }
196 |
197 | return this
198 | }
199 |
200 | // HTTP methods whose capitalization should be normalized
201 | var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT']
202 |
203 | function normalizeMethod(method) {
204 | var upcased = method.toUpperCase()
205 | return (methods.indexOf(upcased) > -1) ? upcased : method
206 | }
207 |
208 | function Request(url, options) {
209 | options = options || {}
210 | this.url = url
211 |
212 | this.credentials = options.credentials || 'omit'
213 | this.headers = new Headers(options.headers)
214 | this.method = normalizeMethod(options.method || 'GET')
215 | this.mode = options.mode || null
216 | this.referrer = null
217 |
218 | if ((this.method === 'GET' || this.method === 'HEAD') && options.body) {
219 | throw new TypeError('Body not allowed for GET or HEAD requests')
220 | }
221 | this._initBody(options.body)
222 | }
223 |
224 | function decode(body) {
225 | var form = new FormData()
226 | body.trim().split('&').forEach(function(bytes) {
227 | if (bytes) {
228 | var split = bytes.split('=')
229 | var name = split.shift().replace(/\+/g, ' ')
230 | var value = split.join('=').replace(/\+/g, ' ')
231 | form.append(decodeURIComponent(name), decodeURIComponent(value))
232 | }
233 | })
234 | return form
235 | }
236 |
237 | function headers(xhr) {
238 | var head = new Headers()
239 | var pairs = xhr.getAllResponseHeaders().trim().split('\n')
240 | pairs.forEach(function(header) {
241 | var split = header.trim().split(':')
242 | var key = split.shift().trim()
243 | var value = split.join(':').trim()
244 | head.append(key, value)
245 | })
246 | return head
247 | }
248 |
249 | var noXhrPatch =
250 | typeof window !== 'undefined' && !!window.ActiveXObject &&
251 | !(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent);
252 |
253 | function getXhr() {
254 | // from backbone.js 1.1.2
255 | // https://github.com/jashkenas/backbone/blob/1.1.2/backbone.js#L1181
256 | if (noXhrPatch && !(/^(get|post|head|put|delete|options)$/i.test(this.method))) {
257 | this.usingActiveXhr = true;
258 | return new ActiveXObject("Microsoft.XMLHTTP");
259 | }
260 | return new XMLHttpRequest();
261 | }
262 |
263 | Body.call(Request.prototype)
264 |
265 | function Response(bodyInit, options) {
266 | if (!options) {
267 | options = {}
268 | }
269 |
270 | this._initBody(bodyInit)
271 | this.type = 'default'
272 | this.url = null
273 | this.status = options.status
274 | this.ok = this.status >= 200 && this.status < 300
275 | this.statusText = options.statusText
276 | this.headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers)
277 | this.url = options.url || ''
278 | }
279 |
280 | Body.call(Response.prototype)
281 |
282 | self.Headers = Headers;
283 | self.Request = Request;
284 | self.Response = Response;
285 |
286 | self.fetch = function(input, init) {
287 | // TODO: Request constructor should accept input, init
288 | var request
289 | if (Request.prototype.isPrototypeOf(input) && !init) {
290 | request = input
291 | } else {
292 | request = new Request(input, init)
293 | }
294 |
295 | return new fetch.Promise(function(resolve, reject) {
296 | var xhr = getXhr();
297 | if (request.credentials === 'cors') {
298 | xhr.withCredentials = true;
299 | }
300 |
301 | function responseURL() {
302 | if ('responseURL' in xhr) {
303 | return xhr.responseURL
304 | }
305 |
306 | // Avoid security warnings on getResponseHeader when not allowed by CORS
307 | if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) {
308 | return xhr.getResponseHeader('X-Request-URL')
309 | }
310 |
311 | return;
312 | }
313 |
314 | function onload() {
315 | if (xhr.readyState !== 4) {
316 | return
317 | }
318 | var status = (xhr.status === 1223) ? 204 : xhr.status
319 | if (status < 100 || status > 599) {
320 | reject(new TypeError('Network request failed'))
321 | return
322 | }
323 | var options = {
324 | status: status,
325 | statusText: xhr.statusText,
326 | headers: headers(xhr),
327 | url: responseURL()
328 | }
329 | var body = 'response' in xhr ? xhr.response : xhr.responseText;
330 | resolve(new Response(body, options))
331 | }
332 | xhr.onreadystatechange = onload;
333 | if (!self.usingActiveXhr) {
334 | xhr.onload = onload;
335 | xhr.onerror = function() {
336 | reject(new TypeError('Network request failed'))
337 | }
338 | }
339 |
340 | xhr.open(request.method, request.url, true)
341 |
342 | if ('responseType' in xhr && support.blob) {
343 | xhr.responseType = 'blob'
344 | }
345 |
346 | request.headers.forEach(function(name, values) {
347 | values.forEach(function(value) {
348 | xhr.setRequestHeader(name, value)
349 | })
350 | })
351 |
352 | xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit)
353 | })
354 | }
355 | fetch.Promise = self.Promise; // you could change it to your favorite alternative
356 | self.fetch.polyfill = true
357 | })();
358 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fetch-polyfill",
3 | "version": "0.8.2",
4 | "main": "fetch.js",
5 | "repository": "undoZen/fetch",
6 | "licenses": [
7 | {
8 | "type": "MIT",
9 | "url": "http://mit-license.org/undozen"
10 | }
11 | ],
12 | "devDependencies": {
13 | "browserify": "^9.0.8",
14 | "es6-promise": "^2.1.1",
15 | "isarray": "0.0.1",
16 | "jshint": "2.5.2",
17 | "mocha": "2.1.0"
18 | },
19 | "files": [
20 | "LICENSE",
21 | "fetch.js"
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/script/phantomjs:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | port=3900
6 |
7 | # Find next available port
8 | while lsof -i :$((++port)) >/dev/null; do true; done
9 |
10 | # Spin a test server in the background
11 | node ./script/server $port &>/dev/null &
12 | server_pid=$!
13 | trap "kill $server_pid" INT EXIT
14 |
15 | node ./node_modules/.bin/mocha-phantomjs -s localToRemoteUrlAccessEnabled=true -s webSecurityEnabled=false "http://localhost:$port/test/test.html"
16 | node ./node_modules/.bin/mocha-phantomjs -s localToRemoteUrlAccessEnabled=true -s webSecurityEnabled=false "http://localhost:$port/test/test-worker.html"
17 |
--------------------------------------------------------------------------------
/script/saucelabs:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | port=8080
6 |
7 | # Spin a test server in the background
8 | node ./script/server $port &>/dev/null &
9 | server_pid=$!
10 | trap "kill $server_pid" INT EXIT
11 |
12 | job=$(./script/saucelabs-start "http://localhost:$port/test/test.html")
13 |
14 | while true
15 | do
16 | result=$(echo "$job" | ./script/saucelabs-status)
17 | [[ $result == *"\"completed\": true"* ]] && break
18 | sleep 1
19 | echo -n "."
20 | done
21 |
22 | echo -n ""
23 |
24 | echo "$result" | ./script/saucelabs-result
25 |
--------------------------------------------------------------------------------
/script/saucelabs-result:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require 'json'
4 |
5 | obj = JSON.parse(ARGF.read)
6 |
7 | test = obj['js tests'][0]
8 |
9 | warn test['url']
10 | warn test['platform']
11 | warn test['result'].inspect
12 |
13 | if test['result'] && (test['result']['passes'] + test['result']['pending']) == test['result']['tests']
14 | exit 0
15 | else
16 | exit 1
17 | end
18 |
--------------------------------------------------------------------------------
/script/saucelabs-start:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | url="https://saucelabs.com/rest/v1/$SAUCE_USERNAME/js-tests"
6 | auth="$SAUCE_USERNAME:$SAUCE_ACCESS_KEY"
7 | header="Content-Type: application/json"
8 |
9 | data=$(cat <
2 |
3 |
4 |
5 | Fetch Worker Tests
6 |
7 |
8 |
9 |
10 |
11 |
12 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/test/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Fetch Tests
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | function readBlobAsText(blob) {
2 | if ('FileReader' in self) {
3 | return new Promise(function(resolve, reject) {
4 | var reader = new FileReader()
5 | reader.onload = function() {
6 | resolve(reader.result)
7 | }
8 | reader.onerror = function() {
9 | reject(reader.error)
10 | }
11 | reader.readAsText(blob)
12 | })
13 | } else if ('FileReaderSync' in self) {
14 | return new FileReaderSync().readAsText(blob)
15 | } else {
16 | throw new ReferenceError('FileReader is not defined')
17 | }
18 | }
19 |
20 | function readBlobAsBytes(blob) {
21 | if ('FileReader' in self) {
22 | return new Promise(function(resolve, reject) {
23 | var reader = new FileReader()
24 | reader.onload = function() {
25 | var view = new Uint8Array(reader.result)
26 | resolve(Array.prototype.slice.call(view))
27 | }
28 | reader.onerror = function() {
29 | reject(reader.error)
30 | }
31 | reader.readAsArrayBuffer(blob)
32 | })
33 | } else if ('FileReaderSync' in self) {
34 | return new FileReaderSync().readAsArrayBuffer(blob)
35 | } else {
36 | throw new ReferenceError('FileReader is not defined')
37 | }
38 | }
39 |
40 | test('resolves promise on 500 error', function() {
41 | return fetch('/boom').then(function(response) {
42 | assert.equal(response.status, 500)
43 | assert.equal(response.ok, false)
44 | return response.text()
45 | }).then(function(body) {
46 | assert.equal(body, 'boom')
47 | })
48 | })
49 |
50 | test.skip('rejects promise for network error', function() {
51 | return fetch('/error').then(function(response) {
52 | assert(false, 'HTTP status ' + response.status + ' was treated as success')
53 | })['catch'](function(error) {
54 | assert(error instanceof TypeError, 'Rejected with Error')
55 | })
56 | })
57 |
58 | // https://fetch.spec.whatwg.org/#headers-class
59 | suite('Headers', function() {
60 | test('headers are case insensitive', function() {
61 | var headers = new Headers({'Accept': 'application/json'})
62 | assert.equal(headers.get('ACCEPT'), 'application/json')
63 | assert.equal(headers.get('Accept'), 'application/json')
64 | assert.equal(headers.get('accept'), 'application/json')
65 | })
66 | test('appends to existing', function() {
67 | var headers = new Headers({'Accept': 'application/json'})
68 | assert.isFalse(headers.has('Content-Type'))
69 | headers.append('Content-Type', 'application/json')
70 | assert.isTrue(headers.has('Content-Type'))
71 | assert.equal(headers.get('Content-Type'), 'application/json')
72 | })
73 | test('appends values to existing header name', function() {
74 | var headers = new Headers({'Accept': 'application/json'})
75 | headers.append('Accept', 'text/plain')
76 | assert.equal(headers.getAll('Accept').length, 2)
77 | assert.equal(headers.getAll('Accept')[0], 'application/json')
78 | assert.equal(headers.getAll('Accept')[1], 'text/plain')
79 | })
80 | test('sets header name and value', function() {
81 | var headers = new Headers()
82 | headers.set('Content-Type', 'application/json')
83 | assert.equal(headers.get('Content-Type'), 'application/json')
84 | })
85 | test('returns null on no header found', function() {
86 | var headers = new Headers()
87 | assert.isNull(headers.get('Content-Type'))
88 | })
89 | test('has headers that are set', function() {
90 | var headers = new Headers()
91 | headers.set('Content-Type', 'application/json')
92 | assert.isTrue(headers.has('Content-Type'))
93 | })
94 | test('deletes headers', function() {
95 | var headers = new Headers()
96 | headers.set('Content-Type', 'application/json')
97 | assert.isTrue(headers.has('Content-Type'))
98 | headers['delete']('Content-Type')
99 | assert.isFalse(headers.has('Content-Type'))
100 | assert.isNull(headers.get('Content-Type'))
101 | })
102 | test('returns list on getAll when header found', function() {
103 | var headers = new Headers({'Content-Type': 'application/json'})
104 | assert.isArray(headers.getAll('Content-Type'))
105 | assert.equal(headers.getAll('Content-Type').length, 1)
106 | assert.equal(headers.getAll('Content-Type')[0], 'application/json')
107 | })
108 | test('returns empty list on getAll when no header found', function() {
109 | var headers = new Headers()
110 | assert.isArray(headers.getAll('Content-Type'))
111 | assert.equal(headers.getAll('Content-Type').length, 0)
112 | })
113 | test('converts field name to string on set and get', function() {
114 | var headers = new Headers()
115 | headers.set(1, 'application/json')
116 | assert.equal(headers.get(1), 'application/json')
117 | })
118 | test('converts field value to string on set and get', function() {
119 | var headers = new Headers()
120 | headers.set('Content-Type', 1)
121 | assert.equal(headers.get('Content-Type'), '1')
122 | })
123 | test('throws TypeError on invalid character in field name', function() {
124 | assert.throws(function() { new Headers({'': ['application/json']}) }, TypeError)
125 | assert.throws(function() { new Headers({'Accept:': ['application/json']}) }, TypeError)
126 | assert.throws(function() {
127 | var headers = new Headers();
128 | headers.set({field: 'value'}, 'application/json');
129 | }, TypeError)
130 | })
131 | })
132 |
133 | // https://fetch.spec.whatwg.org/#request-class
134 | suite('Request', function() {
135 | test('sends request headers', function() {
136 | return fetch('/request', {
137 | headers: {
138 | 'Accept': 'application/json',
139 | 'X-Test': '42'
140 | }
141 | }).then(function(response) {
142 | return response.json()
143 | }).then(function(json) {
144 | assert.equal(json.headers['accept'], 'application/json')
145 | assert.equal(json.headers['x-test'], '42')
146 | })
147 | })
148 |
149 | test('fetch request', function() {
150 | var request = new Request('/request', {
151 | headers: {
152 | 'Accept': 'application/json',
153 | 'X-Test': '42'
154 | }
155 | })
156 |
157 | return fetch(request).then(function(response) {
158 | return response.json()
159 | }).then(function(json) {
160 | assert.equal(json.headers['accept'], 'application/json')
161 | assert.equal(json.headers['x-test'], '42')
162 | })
163 | })
164 |
165 | test('construct with url', function() {
166 | var request = new Request('https://fetch.spec.whatwg.org/')
167 | assert.equal(request.url, 'https://fetch.spec.whatwg.org/')
168 | })
169 |
170 | // https://fetch.spec.whatwg.org/#concept-bodyinit-extract
171 | suite('BodyInit extract', function() {
172 | ;(Request.prototype.blob ? suite : suite.skip)('type Blob', function() {
173 | test('consume as blob', function() {
174 | var request = new Request(null, {method: 'POST', body: new Blob(['hello'])})
175 | return request.blob().then(readBlobAsText).then(function(text) {
176 | assert.equal(text, 'hello')
177 | })
178 | })
179 |
180 | test('consume as text', function() {
181 | var request = new Request(null, {method: 'POST', body: new Blob(['hello'])})
182 | return request.text().then(function(text) {
183 | assert.equal(text, 'hello')
184 | })
185 | })
186 | })
187 |
188 | suite('type USVString', function() {
189 | test('consume as text', function() {
190 | var request = new Request(null, {method: 'POST', body: 'hello'})
191 | return request.text().then(function(text) {
192 | assert.equal(text, 'hello')
193 | })
194 | })
195 |
196 | ;(Request.prototype.blob ? test : test.skip)('consume as blob', function() {
197 | var request = new Request(null, {method: 'POST', body: 'hello'})
198 | return request.blob().then(readBlobAsText).then(function(text) {
199 | assert.equal(text, 'hello')
200 | })
201 | })
202 | })
203 | })
204 | })
205 |
206 | // https://fetch.spec.whatwg.org/#response-class
207 | suite('Response', function() {
208 | // https://fetch.spec.whatwg.org/#concept-bodyinit-extract
209 | suite('BodyInit extract', function() {
210 | ;(Response.prototype.blob ? suite : suite.skip)('type Blob', function() {
211 | test('consume as blob', function() {
212 | var response = new Response(new Blob(['hello']))
213 | return response.blob().then(readBlobAsText).then(function(text) {
214 | assert.equal(text, 'hello')
215 | })
216 | })
217 |
218 | test('consume as text', function() {
219 | var response = new Response(new Blob(['hello']))
220 | return response.text().then(function(text) {
221 | assert.equal(text, 'hello')
222 | })
223 | })
224 | })
225 |
226 | suite('type USVString', function() {
227 | test('consume as text', function() {
228 | var response = new Response('hello')
229 | return response.text().then(function(text) {
230 | assert.equal(text, 'hello')
231 | })
232 | })
233 |
234 | ;(Response.prototype.blob ? test : test.skip)('consume as blob', function() {
235 | var response = new Response('hello')
236 | return response.blob().then(readBlobAsText).then(function(text) {
237 | assert.equal(text, 'hello')
238 | })
239 | })
240 | })
241 | })
242 |
243 | test('populates response body', function() {
244 | return fetch('/hello').then(function(response) {
245 | assert.equal(response.status, 200)
246 | assert.equal(response.ok, true)
247 | return response.text()
248 | }).then(function(body) {
249 | assert.equal(body, 'hi')
250 | })
251 | })
252 |
253 | test('parses response headers', function() {
254 | return fetch('/headers?' + new Date().getTime()).then(function(response) {
255 | assert.equal(response.headers.get('Date'), 'Mon, 13 Oct 2014 21:02:27 GMT')
256 | assert.equal(response.headers.get('Content-Type'), 'text/html; charset=utf-8')
257 | })
258 | })
259 |
260 | test('creates Headers object from raw headers', function() {
261 | var r = new Response('{"foo":"bar"}', {headers: {'content-type': 'application/json'}});
262 | assert.equal(r.headers instanceof Headers, true);
263 | return r.json().then(function(json){
264 | assert(json.foo, 'bar');
265 | return json;
266 | })
267 | })
268 | })
269 |
270 | // https://fetch.spec.whatwg.org/#body-mixin
271 | suite('Body mixin', function() {
272 | ;(Response.prototype.arrayBuffer ? suite : suite.skip)('arrayBuffer', function() {
273 | test('resolves arrayBuffer promise', function() {
274 | return fetch('/hello').then(function(response) {
275 | return response.arrayBuffer()
276 | }).then(function(buf) {
277 | assert(buf instanceof ArrayBuffer, 'buf is an ArrayBuffer instance')
278 | assert.equal(buf.byteLength, 2)
279 | })
280 | })
281 |
282 | test('arrayBuffer handles binary data', function() {
283 | return fetch('/binary').then(function(response) {
284 | return response.arrayBuffer()
285 | }).then(function(buf) {
286 | assert(buf instanceof ArrayBuffer, 'buf is an ArrayBuffer instance')
287 | assert.equal(buf.byteLength, 256, 'buf.byteLength is correct')
288 | var view = new Uint8Array(buf)
289 | for (var i = 0; i < 256; i++) {
290 | assert.equal(view[i], i)
291 | }
292 | })
293 | })
294 |
295 | test('arrayBuffer handles utf-8 data', function() {
296 | return fetch('/hello/utf8').then(function(response) {
297 | return response.arrayBuffer()
298 | }).then(function(buf) {
299 | assert(buf instanceof ArrayBuffer, 'buf is an ArrayBuffer instance')
300 | assert.equal(buf.byteLength, 5, 'buf.byteLength is correct')
301 | var octets = Array.prototype.slice.call(new Uint8Array(buf))
302 | assert.deepEqual(octets, [104, 101, 108, 108, 111])
303 | })
304 | })
305 |
306 | test('arrayBuffer handles utf-16le data', function() {
307 | return fetch('/hello/utf16le').then(function(response) {
308 | return response.arrayBuffer()
309 | }).then(function(buf) {
310 | assert(buf instanceof ArrayBuffer, 'buf is an ArrayBuffer instance')
311 | assert.equal(buf.byteLength, 10, 'buf.byteLength is correct')
312 | var octets = Array.prototype.slice.call(new Uint8Array(buf))
313 | assert.deepEqual(octets, [104, 0, 101, 0, 108, 0, 108, 0, 111, 0])
314 | })
315 | })
316 |
317 | test('rejects arrayBuffer promise after body is consumed', function() {
318 | return fetch('/hello').then(function(response) {
319 | assert(response.arrayBuffer, 'Body does not implement arrayBuffer')
320 | assert.equal(response.bodyUsed, false)
321 | response.blob()
322 | assert.equal(response.bodyUsed, true)
323 | return response.arrayBuffer()
324 | })['catch'](function(error) {
325 | assert(error instanceof TypeError, 'Promise rejected after body consumed')
326 | })
327 | })
328 | })
329 |
330 | ;(Response.prototype.blob ? suite : suite.skip)('blob', function() {
331 | test('resolves blob promise', function() {
332 | return fetch('/hello').then(function(response) {
333 | return response.blob()
334 | }).then(function(blob) {
335 | assert(blob instanceof Blob, 'blob is a Blob instance')
336 | assert.equal(blob.size, 2)
337 | })
338 | })
339 |
340 | test('blob handles binary data', function() {
341 | return fetch('/binary').then(function(response) {
342 | return response.blob()
343 | }).then(function(blob) {
344 | assert(blob instanceof Blob, 'blob is a Blob instance')
345 | assert.equal(blob.size, 256, 'blob.size is correct')
346 | })
347 | })
348 |
349 | test('blob handles utf-8 data', function() {
350 | return fetch('/hello/utf8').then(function(response) {
351 | return response.blob()
352 | }).then(readBlobAsBytes).then(function(octets) {
353 | assert.equal(octets.length, 5, 'blob.size is correct')
354 | assert.deepEqual(octets, [104, 101, 108, 108, 111])
355 | })
356 | })
357 |
358 | test('blob handles utf-16le data', function() {
359 | return fetch('/hello/utf16le').then(function(response) {
360 | return response.blob()
361 | }).then(readBlobAsBytes).then(function(octets) {
362 | assert.equal(octets.length, 10, 'blob.size is correct')
363 | assert.deepEqual(octets, [104, 0, 101, 0, 108, 0, 108, 0, 111, 0])
364 | })
365 | })
366 |
367 | test('rejects blob promise after body is consumed', function() {
368 | return fetch('/hello').then(function(response) {
369 | assert(response.blob, 'Body does not implement blob')
370 | assert.equal(response.bodyUsed, false)
371 | response.text()
372 | assert.equal(response.bodyUsed, true)
373 | return response.blob()
374 | })['catch'](function(error) {
375 | assert(error instanceof TypeError, 'Promise rejected after body consumed')
376 | })
377 | })
378 | })
379 |
380 | ;(Response.prototype.formData ? suite : suite.skip)('formData', function() {
381 | test('post sets content-type header', function() {
382 | return fetch('/request', {
383 | method: 'post',
384 | body: new FormData()
385 | }).then(function(response) {
386 | return response.json()
387 | }).then(function(json) {
388 | assert.equal(json.method, 'POST')
389 | assert(/^multipart\/form-data;/.test(json.headers['content-type']))
390 | })
391 | })
392 |
393 | test('rejects formData promise after body is consumed', function() {
394 | return fetch('/json').then(function(response) {
395 | assert(response.formData, 'Body does not implement formData')
396 | response.formData()
397 | return response.formData()
398 | })['catch'](function(error) {
399 | assert(error instanceof TypeError, 'Promise rejected after body consumed')
400 | })
401 | })
402 |
403 | test('parses form encoded response', function() {
404 | return fetch('/form').then(function(response) {
405 | return response.formData()
406 | }).then(function(form) {
407 | assert(form instanceof FormData, 'Parsed a FormData object')
408 | })
409 | })
410 | })
411 |
412 | suite('json', function() {
413 | test('parses json response', function() {
414 | return fetch('/json').then(function(response) {
415 | return response.json()
416 | }).then(function(json) {
417 | assert.equal(json.name, 'Hubot')
418 | assert.equal(json.login, 'hubot')
419 | })
420 | })
421 |
422 | test('rejects json promise after body is consumed', function() {
423 | return fetch('/json').then(function(response) {
424 | assert(response.json, 'Body does not implement json')
425 | assert.equal(response.bodyUsed, false)
426 | response.text()
427 | assert.equal(response.bodyUsed, true)
428 | return response.json()
429 | })['catch'](function(error) {
430 | assert(error instanceof TypeError, 'Promise rejected after body consumed')
431 | })
432 | })
433 |
434 | test('handles json parse error', function() {
435 | return fetch('/json-error').then(function(response) {
436 | return response.json()
437 | })['catch'](function(error) {
438 | assert(error instanceof Error, 'JSON exception is an Error instance')
439 | assert(error.message, 'JSON exception has an error message')
440 | })
441 | })
442 | })
443 |
444 | suite('text', function() {
445 | test('handles 204 No Content response', function() {
446 | return fetch('/empty').then(function(response) {
447 | assert.equal(response.status, 204)
448 | return response.text()
449 | }).then(function(body) {
450 | assert.equal(body, '')
451 | })
452 | })
453 |
454 | test('resolves text promise', function() {
455 | return fetch('/hello').then(function(response) {
456 | return response.text()
457 | }).then(function(text) {
458 | assert.equal(text, 'hi')
459 | })
460 | })
461 |
462 | test('rejects text promise after body is consumed', function() {
463 | return fetch('/hello').then(function(response) {
464 | assert(response.text, 'Body does not implement text')
465 | assert.equal(response.bodyUsed, false)
466 | response.text()
467 | assert.equal(response.bodyUsed, true)
468 | return response.text()
469 | })['catch'](function(error) {
470 | assert(error instanceof TypeError, 'Promise rejected after body consumed')
471 | })
472 | })
473 | })
474 | })
475 |
476 | // https://fetch.spec.whatwg.org/#methods
477 | suite('Methods', function() {
478 | test('supports HTTP GET', function() {
479 | return fetch('/request', {
480 | method: 'get',
481 | }).then(function(response) {
482 | return response.json()
483 | }).then(function(request) {
484 | assert.equal(request.method, 'GET')
485 | assert.equal(request.data, '')
486 | })
487 | })
488 |
489 | // TODO: Waiting to verify behavior
490 | test.skip('GET with body throws TypeError', function() {
491 | assert.throws(function() {
492 | new Request('', {
493 | method: 'get',
494 | body: 'invalid'
495 | })
496 | }, TypeError)
497 | })
498 |
499 | test.skip('HEAD with body throws TypeError', function() {
500 | assert.throws(function() {
501 | new Request('', {
502 | method: 'head',
503 | body: 'invalid'
504 | })
505 | }, TypeError)
506 | })
507 |
508 | test('supports HTTP POST', function() {
509 | return fetch('/request', {
510 | method: 'post',
511 | body: 'name=Hubot'
512 | }).then(function(response) {
513 | return response.json()
514 | }).then(function(request) {
515 | assert.equal(request.method, 'POST')
516 | assert.equal(request.data, 'name=Hubot')
517 | })
518 | })
519 |
520 | test('supports HTTP PUT', function() {
521 | return fetch('/request', {
522 | method: 'put',
523 | body: 'name=Hubot'
524 | }).then(function(response) {
525 | return response.json()
526 | }).then(function(request) {
527 | assert.equal(request.method, 'PUT')
528 | assert.equal(request.data, 'name=Hubot')
529 | })
530 | })
531 |
532 | var patchSupported = !/PhantomJS/.test(navigator.userAgent)
533 |
534 | ;(patchSupported ? test : test.skip)('supports HTTP PATCH', function() {
535 | return fetch('/request', {
536 | method: 'PATCH',
537 | body: 'name=Hubot'
538 | }).then(function(response) {
539 | return response.json()
540 | }).then(function(request) {
541 | assert.equal(request.method, 'PATCH')
542 | assert.equal(request.data, 'name=Hubot')
543 | })
544 | })
545 |
546 | test('supports HTTP DELETE', function() {
547 | return fetch('/request', {
548 | method: 'delete',
549 | }).then(function(response) {
550 | return response.json()
551 | }).then(function(request) {
552 | assert.equal(request.method, 'DELETE')
553 | assert.equal(request.data, '')
554 | })
555 | })
556 | })
557 |
558 | // https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
559 | suite('Atomic HTTP redirect handling', function() {
560 | test('handles 301 redirect response', function() {
561 | return fetch('/redirect/301').then(function(response) {
562 | assert.equal(response.status, 200)
563 | assert.equal(response.ok, true)
564 | assert.match(response.url, /\/hello/)
565 | return response.text()
566 | }).then(function(body) {
567 | assert.equal(body, 'hi')
568 | })
569 | })
570 |
571 | test('handles 302 redirect response', function() {
572 | return fetch('/redirect/302').then(function(response) {
573 | assert.equal(response.status, 200)
574 | assert.equal(response.ok, true)
575 | assert.match(response.url, /\/hello/)
576 | return response.text()
577 | }).then(function(body) {
578 | assert.equal(body, 'hi')
579 | })
580 | })
581 |
582 | test('handles 303 redirect response', function() {
583 | return fetch('/redirect/303').then(function(response) {
584 | assert.equal(response.status, 200)
585 | assert.equal(response.ok, true)
586 | assert.match(response.url, /\/hello/)
587 | return response.text()
588 | }).then(function(body) {
589 | assert.equal(body, 'hi')
590 | })
591 | })
592 |
593 | test('handles 307 redirect response', function() {
594 | return fetch('/redirect/307').then(function(response) {
595 | assert.equal(response.status, 200)
596 | assert.equal(response.ok, true)
597 | assert.match(response.url, /\/hello/)
598 | return response.text()
599 | }).then(function(body) {
600 | assert.equal(body, 'hi')
601 | })
602 | })
603 |
604 | var permanentRedirectSupported = !/PhantomJS|Trident/.test(navigator.userAgent)
605 |
606 | ;(permanentRedirectSupported ? test : test.skip)('handles 308 redirect response', function() {
607 | return fetch('/redirect/308').then(function(response) {
608 | assert.equal(response.status, 200)
609 | assert.equal(response.ok, true)
610 | assert.match(response.url, /\/hello/)
611 | return response.text()
612 | }).then(function(body) {
613 | assert.equal(body, 'hi')
614 | })
615 | })
616 | })
617 |
618 | // https://fetch.spec.whatwg.org/#concept-request-credentials-mode
619 | suite('credentials mode', function() {
620 | var omitSupported = !self.fetch.polyfill
621 |
622 | setup(function() {
623 | return fetch('/cookie?name=foo&value=reset', {credentials: 'same-origin'});
624 | })
625 |
626 | ;(omitSupported ? suite : suite.skip)('omit', function() {
627 | test('request credentials defaults to omit', function() {
628 | var request = new Request('')
629 | assert.equal(request.credentials, 'omit')
630 | })
631 |
632 | test('does not accept cookies with implicit omit credentials', function() {
633 | return fetch('/cookie?name=foo&value=bar').then(function() {
634 | return fetch('/cookie?name=foo', {credentials: 'same-origin'});
635 | }).then(function(response) {
636 | return response.text()
637 | }).then(function(data) {
638 | assert.equal(data, 'reset')
639 | })
640 | })
641 |
642 | test('does not accept cookies with omit credentials', function() {
643 | return fetch('/cookie?name=foo&value=bar', {credentials: 'omit'}).then(function() {
644 | return fetch('/cookie?name=foo', {credentials: 'same-origin'});
645 | }).then(function(response) {
646 | return response.text()
647 | }).then(function(data) {
648 | assert.equal(data, 'reset')
649 | })
650 | })
651 |
652 | test('does not send cookies with implicit omit credentials', function() {
653 | return fetch('/cookie?name=foo&value=bar', {credentials: 'same-origin'}).then(function() {
654 | return fetch('/cookie?name=foo');
655 | }).then(function(response) {
656 | return response.text()
657 | }).then(function(data) {
658 | assert.equal(data, '')
659 | })
660 | })
661 |
662 | test('does not send cookies with omit credentials', function() {
663 | return fetch('/cookie?name=foo&value=bar').then(function() {
664 | return fetch('/cookie?name=foo', {credentials: 'omit'})
665 | }).then(function(response) {
666 | return response.text()
667 | }).then(function(data) {
668 | assert.equal(data, '')
669 | })
670 | })
671 | })
672 |
673 | suite('same-origin', function() {
674 | test('request credentials uses inits member', function() {
675 | var request = new Request('', {credentials: 'same-origin'})
676 | assert.equal(request.credentials, 'same-origin')
677 | })
678 |
679 | test('send cookies with same-origin credentials', function() {
680 | return fetch('/cookie?name=foo&value=bar', {credentials: 'same-origin'}).then(function() {
681 | return fetch('/cookie?name=foo', {credentials: 'same-origin'})
682 | }).then(function(response) {
683 | return response.text()
684 | }).then(function(data) {
685 | assert.equal(data, 'bar')
686 | })
687 | })
688 | })
689 |
690 | suite('include', function() {
691 | test('send cookies with include credentials', function() {
692 | return fetch('/cookie?name=foo&value=bar', {credentials: 'include'}).then(function() {
693 | return fetch('/cookie?name=foo', {credentials: 'include'})
694 | }).then(function(response) {
695 | return response.text()
696 | }).then(function(data) {
697 | assert.equal(data, 'bar')
698 | })
699 | })
700 | })
701 | })
702 |
--------------------------------------------------------------------------------
/test/worker.js:
--------------------------------------------------------------------------------
1 | importScripts('../node_modules/chai/chai.js')
2 | importScripts('../node_modules/mocha/mocha.js')
3 |
4 | mocha.setup('tdd')
5 | self.assert = chai.assert
6 |
7 | importScripts('../bower_components/es6-promise/promise.js')
8 | importScripts('../fetch.js')
9 |
10 | importScripts('test.js')
11 |
12 | function title(test) {
13 | return test.fullTitle().replace(/#/g, '');
14 | }
15 |
16 | function reporter(runner) {
17 | runner.on('pending', function(test){
18 | self.postMessage({name: 'pending', title: title(test)});
19 | });
20 |
21 | runner.on('pass', function(test){
22 | self.postMessage({name: 'pass', title: title(test)});
23 | });
24 |
25 | runner.on('fail', function(test, err){
26 | self.postMessage({
27 | name: 'fail',
28 | title: title(test),
29 | message: err.message,
30 | stack: err.stack
31 | });
32 | });
33 |
34 | runner.on('end', function(){
35 | self.postMessage({name: 'end'});
36 | });
37 | }
38 |
39 | mocha.reporter(reporter).run()
40 |
--------------------------------------------------------------------------------