27 Commits
Author SHA1 Message Date
Eli GreyandGitHub 648ff96f8b Use FileReader in macOS native apps using WebKit (#613)
Use FileReader in macOS native apps using WebKit
2020-01-30 16:44:01 -08:00
Rajiv Shah 7a8ff3cc6a Switch isWebKit and isSafari conditions to maintain behavior
Using isWebKit before isSafari could cause Safari to use FileReader even if force === false, since the user agent for Safari also includes AppleWebKit
2020-01-30 19:40:57 -05:00
Rajiv Shah 0a2e868b48 Rebuild dist 2020-01-30 19:20:04 -05:00
Rajiv Shah 16a8bb9e8f Bump version to 2.0.3 2020-01-30 19:18:02 -05:00
Rajiv Shah 572f254834 Ensure Chrome iOS still works 2020-01-30 19:00:37 -05:00
Rajiv Shah d3d2bd20d6 Merge branch 'master' into feature/webkit-macos 2020-01-30 18:50:01 -05:00
Susanne GerberandJimmy Wärting 480d547986 fix small spelling error 2020-01-25 23:06:39 +01:00
Rajiv Shah db7880179f Use FileReader in macOS native apps using WebKit 2019-12-21 17:41:53 -05:00
Eli GreyandGitHub 9a0a1e4ae2 Add GitHub Sponsors funding method 2019-10-22 10:42:27 -07:00
Eli GreyandGitHub 1ea2325a75 Bump mixin-deep from 1.3.1 to 1.3.2 (#601)
Bump mixin-deep from 1.3.1 to 1.3.2
2019-10-14 13:13:09 -07:00
dependabot[bot]andGitHub 35e9d8127d Bump mixin-deep from 1.3.1 to 1.3.2
Bumps [mixin-deep](https://github.com/jonschlinkert/mixin-deep) from 1.3.1 to 1.3.2.
- [Release notes](https://github.com/jonschlinkert/mixin-deep/releases)
- [Commits](https://github.com/jonschlinkert/mixin-deep/compare/1.3.1...1.3.2)

Signed-off-by: dependabot[bot] <[email protected]>
2019-10-14 18:22:49 +00:00
Eli GreyandGitHub 538c4b5751 Bump lodash from 4.17.11 to 4.17.14 (#575)
Bump lodash from 4.17.11 to 4.17.14
2019-10-14 11:21:37 -07:00
Eli GreyandGitHub 556db1f6df Merge pull request #577 from chrisdoble/master
Fix downloads in Chrome iOS
2019-07-15 23:34:35 -07:00
Chris Doble b95a82a3ec Check that FileReader exists, not that it's an object
Fixes #576
2019-07-16 16:14:44 +10:00
dependabot[bot]andGitHub 35f0e568d9 Bump lodash from 4.17.11 to 4.17.14
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.11 to 4.17.14.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.11...4.17.14)

Signed-off-by: dependabot[bot] <[email protected]>
2019-07-11 03:41:59 +00:00
Eli GreyandGitHub a7e1ccfc44 Create FUNDING.yml 2019-06-11 22:39:07 -07:00
Jimmy Wärting 1bc9ed39c4 bump minor version 2019-05-14 10:00:17 +02:00
Marcin RybackiandJimmy Wärting 71f266f93e Missing dist build 2019-05-14 09:46:41 +02:00
Marcin RybackiandJimmy Wärting 9bd5261f60 Catching an exception on Send. This happens when request for HEAD returns 405 Method Not Allowed. Instead of error http status, this may throw an exception. Return false and fallback to link download. 2019-05-14 09:46:41 +02:00
Oskar KöökandJimmy Wärting 3caf053bbd fix autoBom documentation 2019-05-14 09:45:57 +02:00
josephroccaandJimmy Wärting 04ca36440d Updated Chrome data 2019-03-01 09:47:36 +01:00
zachary parrishandJimmy Wärting d87b419616 Update README.md
Just made some minor grammar changes
2019-02-17 17:45:48 +01:00
Jimmy WärtingandGitHub 3e46b73052 Update package.json 2019-02-17 15:24:08 +01:00
TypeError64andJimmy Wärting b1ab588946 Update README.md 2019-02-01 11:29:14 +01:00
Nathan Phillip BrinkandJimmy Wärting 4a73fd7a2d Fix a few spelling errors. 2019-01-07 13:02:54 +01:00
Alexander СветлыйandJimmy Wärting 0e905a3e06 Update README.md
Fix import
2018-12-29 11:02:50 +01:00
segoviaandJimmy Wärting 283f438c31 Fix accidental rewrite of global saveAs function
The change is just to put parentheses around the code after `var saveAs = _global.saveAs ||`
and then indent it properly.

closes #498
2018-12-29 11:01:25 +01:00
9 changed files with 2858 additions and 124 deletions
+3
View File
@@ -0,0 +1,3 @@
# These are supported funding model platforms
github: eligrey
+6 -1
View File
@@ -4,9 +4,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.0.2] - 2019-05-14
- Catching an exception on Send (HEAD) ([#534])
## [2.0.0] - 2018-10-17 ## [2.0.0] - 2018-10-17
- Removed eval to resolve CSP [#465] - Removed eval to resolve CSP ([#465])
## [2.0.0-rc.4] - 2018-10-17 ## [2.0.0-rc.4] - 2018-10-17
@@ -53,3 +57,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#469]: https://github.com/eligrey/FileSaver.js/issues/469 [#469]: https://github.com/eligrey/FileSaver.js/issues/469
[#470]: https://github.com/eligrey/FileSaver.js/issues/470 [#470]: https://github.com/eligrey/FileSaver.js/issues/470
[#491]: https://github.com/eligrey/FileSaver.js/issues/491 [#491]: https://github.com/eligrey/FileSaver.js/issues/491
[#534]: https://github.com/eligrey/FileSaver.js/issues/534
+20 -19
View File
@@ -1,4 +1,4 @@
If you need to save really large files bigger then the blob's size limitation or don't have If you need to save really large files bigger than the blob's size limitation or don't have
enough RAM, then have a look at the more advanced [StreamSaver.js][7] enough RAM, then have a look at the more advanced [StreamSaver.js][7]
that can save data directly to the hard drive asynchronously with the power of the new streams API. That will have that can save data directly to the hard drive asynchronously with the power of the new streams API. That will have
support for progress, cancelation and knowing when it's done writing support for progress, cancelation and knowing when it's done writing
@@ -8,7 +8,7 @@ FileSaver.js
FileSaver.js is the solution to saving files on the client-side, and is perfect for FileSaver.js is the solution to saving files on the client-side, and is perfect for
web apps that generates files on the client, However if the file is coming from the web apps that generates files on the client, However if the file is coming from the
server we recommend you to first try to use [Content-Disposition][8] attachment response header as it has more cross-browser compatible server we recommend you to first try to use [Content-Disposition][8] attachment response header as it has more cross-browser compatiblity.
Looking for `canvas.toBlob()` for saving canvases? Check out Looking for `canvas.toBlob()` for saving canvases? Check out
[canvas-toBlob.js][2] for a cross-browser implementation. [canvas-toBlob.js][2] for a cross-browser implementation.
@@ -20,8 +20,8 @@ Supported Browsers
| -------------- | ------------- | ------------ | ------------- | ------------ | | -------------- | ------------- | ------------ | ------------- | ------------ |
| Firefox 20+ | Blob | Yes | 800 MiB | None | | Firefox 20+ | Blob | Yes | 800 MiB | None |
| Firefox < 20 | data: URI | No | n/a | [Blob.js](https://github.com/eligrey/Blob.js) | | Firefox < 20 | data: URI | No | n/a | [Blob.js](https://github.com/eligrey/Blob.js) |
| Chrome | Blob | Yes | [500 MiB][3] | None | | Chrome | Blob | Yes | [2GB][3] | None |
| Chrome for Android | Blob | Yes | [500 MiB][3] | None | | Chrome for Android | Blob | Yes | [RAM/5][3] | None |
| Edge | Blob | Yes | ? | None | | Edge | Blob | Yes | ? | None |
| IE 10+ | Blob | Yes | 600 MiB | None | | IE 10+ | Blob | Yes | 600 MiB | None |
| Opera 15+ | Blob | Yes | 500 MiB | None | | Opera 15+ | Blob | Yes | 500 MiB | None |
@@ -54,21 +54,21 @@ saveAs must be run within a user interaction event such as onTouchDown or onClic
Syntax Syntax
------ ------
### Import saveAs() from file-saver ### Import `saveAs()` from file-saver
```js ```js
import saveAs from 'file-saver'; import { saveAs } from 'file-saver';
``` ```
```js ```js
FileSaver saveAs(Blob/File/Url, optional DOMString filename, optional Object { autoBOM }) FileSaver saveAs(Blob/File/Url, optional DOMString filename, optional Object { autoBom })
``` ```
Pass `{ autoBOM: true }` if you want FileSaver.js to automatically provide Unicode text encoding hints (see: [byte order mark](https://en.wikipedia.org/wiki/Byte_order_mark)). Pass `{ autoBom: true }` if you want FileSaver.js to automatically provide Unicode text encoding hints (see: [byte order mark](https://en.wikipedia.org/wiki/Byte_order_mark)). Note that this is only done if your blob type has `charset=utf-8` set.
Examples Examples
-------- --------
### Saving text using require ### Saving text using `require()`
```js ```js
var FileSaver = require('file-saver'); var FileSaver = require('file-saver');
var blob = new Blob(["Hello, world!"], {type: "text/plain;charset=utf-8"}); var blob = new Blob(["Hello, world!"], {type: "text/plain;charset=utf-8"});
@@ -87,16 +87,15 @@ FileSaver.saveAs(blob, "hello world.txt");
```js ```js
FileSaver.saveAs("https://httpbin.org/image", "image.jpg"); FileSaver.saveAs("https://httpbin.org/image", "image.jpg");
``` ```
Using URLs within the same origin will just use `a[download]` Using URLs within the same origin will just use `a[download]`.
Otherwise, it will first check if it supports cors header with a synchronously head request Otherwise, it will first check if it supports cors header with a synchronous head request.
if it does it will download the data and save it using blob URLs If it does, it will download the data and save using blob URLs.
if not it will try to download it using `a[download]` If not, it will try to download it using `a[download]`.
The standard W3C File API [`Blob`][4] interface is not available in all browsers. The standard W3C File API [`Blob`][4] interface is not available in all browsers.
[Blob.js][5] is a cross-browser `Blob` implementation that solves this. [Blob.js][5] is a cross-browser `Blob` implementation that solves this.
### Saving a canvas ### Saving a canvas
```js ```js
var canvas = document.getElementById("my-canvas"); var canvas = document.getElementById("my-canvas");
canvas.toBlob(function(blob) { canvas.toBlob(function(blob) {
@@ -109,10 +108,10 @@ Note: The standard HTML5 `canvas.toBlob()` method is not available in all browse
### Saving File ### Saving File
You can save a File constructor without specifying a filename. The You can save a File constructor without specifying a filename. If the
File itself already contains a name, There is a hand full of ways to get a file file itself already contains a name, there is a hand full of ways to get a file
instance (from storage, file input, new constructor, clipboard event) instance (from storage, file input, new constructor, clipboard event).
But if you still want to change the name, then you can change it in the 2nd argument If you still want to change the name, then you can change it in the 2nd argument.
```js ```js
// Note: Ie and Edge don't support the new File constructor, // Note: Ie and Edge don't support the new File constructor,
@@ -127,7 +126,7 @@ FileSaver.saveAs(file);
[1]: http://eligrey.com/demos/FileSaver.js/ [1]: http://eligrey.com/demos/FileSaver.js/
[2]: https://github.com/eligrey/canvas-toBlob.js [2]: https://github.com/eligrey/canvas-toBlob.js
[3]: https://code.google.com/p/chromium/issues/detail?id=375297 [3]: https://bugs.chromium.org/p/chromium/issues/detail?id=375297#c107
[4]: https://developer.mozilla.org/en-US/docs/DOM/Blob [4]: https://developer.mozilla.org/en-US/docs/DOM/Blob
[5]: https://github.com/eligrey/Blob.js [5]: https://github.com/eligrey/Blob.js
[6]: https://github.com/eligrey/canvas-toBlob.js [6]: https://github.com/eligrey/canvas-toBlob.js
@@ -138,6 +137,7 @@ Installation
------------------ ------------------
```bash ```bash
# Basic Node.JS installation
npm install file-saver --save npm install file-saver --save
bower install file-saver bower install file-saver
``` ```
@@ -145,5 +145,6 @@ bower install file-saver
Additionally, TypeScript definitions can be installed via: Additionally, TypeScript definitions can be installed via:
```bash ```bash
# Additional typescript definitions
npm install @types/file-saver --save-dev npm install @types/file-saver --save-dev
``` ```
+16 -10
View File
@@ -30,7 +30,7 @@
if (typeof opts === 'undefined') opts = { if (typeof opts === 'undefined') opts = {
autoBom: false autoBom: false
};else if (typeof opts !== 'object') { };else if (typeof opts !== 'object') {
console.warn('Depricated: Expected third argument to be a object'); console.warn('Deprecated: Expected third argument to be a object');
opts = { opts = {
autoBom: !opts autoBom: !opts
}; };
@@ -66,7 +66,11 @@
var xhr = new XMLHttpRequest(); // use sync to avoid popup blocker var xhr = new XMLHttpRequest(); // use sync to avoid popup blocker
xhr.open('HEAD', url, false); xhr.open('HEAD', url, false);
xhr.send();
try {
xhr.send();
} catch (e) {}
return xhr.status >= 200 && xhr.status <= 299; return xhr.status >= 200 && xhr.status <= 299;
} // `a.click()` doesn't work for all browsers (#465) } // `a.click()` doesn't work for all browsers (#465)
@@ -79,13 +83,15 @@
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null); evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null);
node.dispatchEvent(evt); node.dispatchEvent(evt);
} }
} } // Detect WebKit inside a native macOS app
var saveAs = _global.saveAs || // probably in some web worker
var isWebKit = /AppleWebKit/.test(navigator.userAgent);
var saveAs = _global.saveAs || ( // probably in some web worker
typeof window !== 'object' || window !== _global ? function saveAs() {} typeof window !== 'object' || window !== _global ? function saveAs() {}
/* noop */ /* noop */
// Use download attribute first if possible (#193 Lumia mobile) // Use download attribute first if possible (#193 Lumia mobile) unless this is a native macOS app
: 'download' in HTMLAnchorElement.prototype ? function saveAs(blob, name, opts) { : 'download' in HTMLAnchorElement.prototype && !isWebKit ? function saveAs(blob, name, opts) {
var URL = _global.URL || _global.webkitURL; var URL = _global.URL || _global.webkitURL;
var a = document.createElement('a'); var a = document.createElement('a');
name = name || blob.name || 'download'; name = name || blob.name || 'download';
@@ -135,7 +141,7 @@
} // Fallback to using FileReader and a popup } // Fallback to using FileReader and a popup
: function saveAs(blob, name, opts, popup) { : function saveAs(blob, name, opts, popup) {
// Open a popup immediately do go around popup blocker // Open a popup immediately do go around popup blocker
// Mostly only avalible on user interaction and the fileReader is async so... // Mostly only available on user interaction and the fileReader is async so...
popup = popup || open('', '_blank'); popup = popup || open('', '_blank');
if (popup) { if (popup) {
@@ -149,8 +155,8 @@
var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent); var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent);
if ((isChromeIOS || force && isSafari) && typeof FileReader === 'object') { if ((isChromeIOS || force && isSafari || isWebKit) && typeof FileReader !== 'undefined') {
// Safari doesn't allow downloading of blob urls // Safari doesn't allow downloading of blob URLs
var reader = new FileReader(); var reader = new FileReader();
reader.onloadend = function () { reader.onloadend = function () {
@@ -171,7 +177,7 @@
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
}, 4E4); // 40s }, 4E4); // 40s
} }
}; });
_global.saveAs = saveAs.saveAs = saveAs; _global.saveAs = saveAs.saveAs = saveAs;
if (typeof module !== 'undefined') { if (typeof module !== 'undefined') {
+1 -1
View File
@@ -1,3 +1,3 @@
(function(a,b){if("function"==typeof define&&define.amd)define([],b);else if("undefined"!=typeof exports)b();else{b(),a.FileSaver={exports:{}}.exports}})(this,function(){"use strict";function b(a,b){return"undefined"==typeof b?b={autoBom:!1}:"object"!=typeof b&&(console.warn("Depricated: Expected third argument to be a object"),b={autoBom:!b}),b.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type)?new Blob(["\uFEFF",a],{type:a.type}):a}function c(b,c,d){var e=new XMLHttpRequest;e.open("GET",b),e.responseType="blob",e.onload=function(){a(e.response,c,d)},e.onerror=function(){console.error("could not download file")},e.send()}function d(a){var b=new XMLHttpRequest;return b.open("HEAD",a,!1),b.send(),200<=b.status&&299>=b.status}function e(a){try{a.dispatchEvent(new MouseEvent("click"))}catch(c){var b=document.createEvent("MouseEvents");b.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),a.dispatchEvent(b)}}var f="object"==typeof window&&window.window===window?window:"object"==typeof self&&self.self===self?self:"object"==typeof global&&global.global===global?global:void 0,a=f.saveAs||"object"!=typeof window||window!==f?function(){}:"download"in HTMLAnchorElement.prototype?function(b,g,h){var i=f.URL||f.webkitURL,j=document.createElement("a");g=g||b.name||"download",j.download=g,j.rel="noopener","string"==typeof b?(j.href=b,j.origin===location.origin?e(j):d(j.href)?c(b,g,h):e(j,j.target="_blank")):(j.href=i.createObjectURL(b),setTimeout(function(){i.revokeObjectURL(j.href)},4E4),setTimeout(function(){e(j)},0))}:"msSaveOrOpenBlob"in navigator?function(f,g,h){if(g=g||f.name||"download","string"!=typeof f)navigator.msSaveOrOpenBlob(b(f,h),g);else if(d(f))c(f,g,h);else{var i=document.createElement("a");i.href=f,i.target="_blank",setTimeout(function(){e(i)})}}:function(a,b,d,e){if(e=e||open("","_blank"),e&&(e.document.title=e.document.body.innerText="downloading..."),"string"==typeof a)return c(a,b,d);var g="application/octet-stream"===a.type,h=/constructor/i.test(f.HTMLElement)||f.safari,i=/CriOS\/[\d]+/.test(navigator.userAgent);if((i||g&&h)&&"object"==typeof FileReader){var j=new FileReader;j.onloadend=function(){var a=j.result;a=i?a:a.replace(/^data:[^;]*;/,"data:attachment/file;"),e?e.location.href=a:location=a,e=null},j.readAsDataURL(a)}else{var k=f.URL||f.webkitURL,l=k.createObjectURL(a);e?e.location=l:location.href=l,e=null,setTimeout(function(){k.revokeObjectURL(l)},4E4)}};f.saveAs=a.saveAs=a,"undefined"!=typeof module&&(module.exports=a)}); (function(a,b){if("function"==typeof define&&define.amd)define([],b);else if("undefined"!=typeof exports)b();else{b(),a.FileSaver={exports:{}}.exports}})(this,function(){"use strict";function b(a,b){return"undefined"==typeof b?b={autoBom:!1}:"object"!=typeof b&&(console.warn("Deprecated: Expected third argument to be a object"),b={autoBom:!b}),b.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type)?new Blob(["\uFEFF",a],{type:a.type}):a}function c(a,b,c){var d=new XMLHttpRequest;d.open("GET",a),d.responseType="blob",d.onload=function(){g(d.response,b,c)},d.onerror=function(){console.error("could not download file")},d.send()}function d(a){var b=new XMLHttpRequest;b.open("HEAD",a,!1);try{b.send()}catch(a){}return 200<=b.status&&299>=b.status}function e(a){try{a.dispatchEvent(new MouseEvent("click"))}catch(c){var b=document.createEvent("MouseEvents");b.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),a.dispatchEvent(b)}}var f="object"==typeof window&&window.window===window?window:"object"==typeof self&&self.self===self?self:"object"==typeof global&&global.global===global?global:void 0,a=/AppleWebKit/.test(navigator.userAgent),g=f.saveAs||("object"!=typeof window||window!==f?function(){}:"download"in HTMLAnchorElement.prototype&&!a?function(b,g,h){var i=f.URL||f.webkitURL,j=document.createElement("a");g=g||b.name||"download",j.download=g,j.rel="noopener","string"==typeof b?(j.href=b,j.origin===location.origin?e(j):d(j.href)?c(b,g,h):e(j,j.target="_blank")):(j.href=i.createObjectURL(b),setTimeout(function(){i.revokeObjectURL(j.href)},4E4),setTimeout(function(){e(j)},0))}:"msSaveOrOpenBlob"in navigator?function(f,g,h){if(g=g||f.name||"download","string"!=typeof f)navigator.msSaveOrOpenBlob(b(f,h),g);else if(d(f))c(f,g,h);else{var i=document.createElement("a");i.href=f,i.target="_blank",setTimeout(function(){e(i)})}}:function(b,d,e,g){if(g=g||open("","_blank"),g&&(g.document.title=g.document.body.innerText="downloading..."),"string"==typeof b)return c(b,d,e);var h="application/octet-stream"===b.type,i=/constructor/i.test(f.HTMLElement)||f.safari,j=/CriOS\/[\d]+/.test(navigator.userAgent);if((j||h&&i||a)&&"undefined"!=typeof FileReader){var k=new FileReader;k.onloadend=function(){var a=k.result;a=j?a:a.replace(/^data:[^;]*;/,"data:attachment/file;"),g?g.location.href=a:location=a,g=null},k.readAsDataURL(b)}else{var l=f.URL||f.webkitURL,m=l.createObjectURL(b);g?g.location=m:location.href=m,g=null,setTimeout(function(){l.revokeObjectURL(m)},4E4)}});f.saveAs=g.saveAs=g,"undefined"!=typeof module&&(module.exports=g)});
//# sourceMappingURL=FileSaver.min.js.map //# sourceMappingURL=FileSaver.min.js.map
+1 -1
View File
File diff suppressed because one or more lines are too long
+2714
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "file-saver", "name": "file-saver",
"version": "2.0.0", "version": "2.0.3",
"description": "An HTML5 saveAs() FileSaver implementation", "description": "An HTML5 saveAs() FileSaver implementation",
"main": "dist/FileSaver.min.js", "main": "dist/FileSaver.min.js",
"files": [ "files": [
+96 -91
View File
@@ -8,7 +8,6 @@
* source : http://purl.eligrey.com/github/FileSaver.js * source : http://purl.eligrey.com/github/FileSaver.js
*/ */
// The one and only way of getting global scope in all environments // The one and only way of getting global scope in all environments
// https://stackoverflow.com/q/3277182/1008999 // https://stackoverflow.com/q/3277182/1008999
var _global = typeof window === 'object' && window.window === window var _global = typeof window === 'object' && window.window === window
@@ -20,7 +19,7 @@ var _global = typeof window === 'object' && window.window === window
function bom (blob, opts) { function bom (blob, opts) {
if (typeof opts === 'undefined') opts = { autoBom: false } if (typeof opts === 'undefined') opts = { autoBom: false }
else if (typeof opts !== 'object') { else if (typeof opts !== 'object') {
console.warn('Depricated: Expected third argument to be a object') console.warn('Deprecated: Expected third argument to be a object')
opts = { autoBom: !opts } opts = { autoBom: !opts }
} }
@@ -49,12 +48,14 @@ function corsEnabled (url) {
var xhr = new XMLHttpRequest() var xhr = new XMLHttpRequest()
// use sync to avoid popup blocker // use sync to avoid popup blocker
xhr.open('HEAD', url, false) xhr.open('HEAD', url, false)
xhr.send() try {
xhr.send()
} catch (e) {}
return xhr.status >= 200 && xhr.status <= 299 return xhr.status >= 200 && xhr.status <= 299
} }
// `a.click()` doesn't work for all browsers (#465) // `a.click()` doesn't work for all browsers (#465)
function click(node) { function click (node) {
try { try {
node.dispatchEvent(new MouseEvent('click')) node.dispatchEvent(new MouseEvent('click'))
} catch (e) { } catch (e) {
@@ -65,97 +66,101 @@ function click(node) {
} }
} }
var saveAs = _global.saveAs || // Detect WebKit inside a native macOS app
// probably in some web worker var isWebKit = /AppleWebKit/.test(navigator.userAgent)
(typeof window !== 'object' || window !== _global)
? function saveAs () { /* noop */ }
// Use download attribute first if possible (#193 Lumia mobile) var saveAs = _global.saveAs || (
: 'download' in HTMLAnchorElement.prototype // probably in some web worker
? function saveAs (blob, name, opts) { (typeof window !== 'object' || window !== _global)
var URL = _global.URL || _global.webkitURL ? function saveAs () { /* noop */ }
var a = document.createElement('a')
name = name || blob.name || 'download'
a.download = name // Use download attribute first if possible (#193 Lumia mobile) unless this is a native macOS app
a.rel = 'noopener' // tabnabbing : ('download' in HTMLAnchorElement.prototype && !isWebKit)
? function saveAs (blob, name, opts) {
// TODO: detect chrome extensions & packaged apps
// a.target = '_blank'
if (typeof blob === 'string') {
// Support regular links
a.href = blob
if (a.origin !== location.origin) {
corsEnabled(a.href)
? download(blob, name, opts)
: click(a, a.target = '_blank')
} else {
click(a)
}
} else {
// Support blobs
a.href = URL.createObjectURL(blob)
setTimeout(function () { URL.revokeObjectURL(a.href) }, 4E4) // 40s
setTimeout(function () { click(a) }, 0)
}
}
// Use msSaveOrOpenBlob as a second approach
: 'msSaveOrOpenBlob' in navigator
? function saveAs (blob, name, opts) {
name = name || blob.name || 'download'
if (typeof blob === 'string') {
if (corsEnabled(blob)) {
download(blob, name, opts)
} else {
var a = document.createElement('a')
a.href = blob
a.target = '_blank'
setTimeout(function () { click(a) })
}
} else {
navigator.msSaveOrOpenBlob(bom(blob, opts), name)
}
}
// Fallback to using FileReader and a popup
: function saveAs (blob, name, opts, popup) {
// Open a popup immediately do go around popup blocker
// Mostly only avalible on user interaction and the fileReader is async so...
popup = popup || open('', '_blank')
if (popup) {
popup.document.title =
popup.document.body.innerText = 'downloading...'
}
if (typeof blob === 'string') return download(blob, name, opts)
var force = blob.type === 'application/octet-stream'
var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari
var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent)
if ((isChromeIOS || (force && isSafari)) && typeof FileReader === 'object') {
// Safari doesn't allow downloading of blob urls
var reader = new FileReader()
reader.onloadend = function () {
var url = reader.result
url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;')
if (popup) popup.location.href = url
else location = url
popup = null // reverse-tabnabbing #460
}
reader.readAsDataURL(blob)
} else {
var URL = _global.URL || _global.webkitURL var URL = _global.URL || _global.webkitURL
var url = URL.createObjectURL(blob) var a = document.createElement('a')
if (popup) popup.location = url name = name || blob.name || 'download'
else location.href = url
popup = null // reverse-tabnabbing #460 a.download = name
setTimeout(function () { URL.revokeObjectURL(url) }, 4E4) // 40s a.rel = 'noopener' // tabnabbing
// TODO: detect chrome extensions & packaged apps
// a.target = '_blank'
if (typeof blob === 'string') {
// Support regular links
a.href = blob
if (a.origin !== location.origin) {
corsEnabled(a.href)
? download(blob, name, opts)
: click(a, a.target = '_blank')
} else {
click(a)
}
} else {
// Support blobs
a.href = URL.createObjectURL(blob)
setTimeout(function () { URL.revokeObjectURL(a.href) }, 4E4) // 40s
setTimeout(function () { click(a) }, 0)
}
} }
}
// Use msSaveOrOpenBlob as a second approach
: 'msSaveOrOpenBlob' in navigator
? function saveAs (blob, name, opts) {
name = name || blob.name || 'download'
if (typeof blob === 'string') {
if (corsEnabled(blob)) {
download(blob, name, opts)
} else {
var a = document.createElement('a')
a.href = blob
a.target = '_blank'
setTimeout(function () { click(a) })
}
} else {
navigator.msSaveOrOpenBlob(bom(blob, opts), name)
}
}
// Fallback to using FileReader and a popup
: function saveAs (blob, name, opts, popup) {
// Open a popup immediately do go around popup blocker
// Mostly only available on user interaction and the fileReader is async so...
popup = popup || open('', '_blank')
if (popup) {
popup.document.title =
popup.document.body.innerText = 'downloading...'
}
if (typeof blob === 'string') return download(blob, name, opts)
var force = blob.type === 'application/octet-stream'
var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari
var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent)
if ((isChromeIOS || (force && isSafari) || isWebKit) && typeof FileReader !== 'undefined') {
// Safari doesn't allow downloading of blob URLs
var reader = new FileReader()
reader.onloadend = function () {
var url = reader.result
url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;')
if (popup) popup.location.href = url
else location = url
popup = null // reverse-tabnabbing #460
}
reader.readAsDataURL(blob)
} else {
var URL = _global.URL || _global.webkitURL
var url = URL.createObjectURL(blob)
if (popup) popup.location = url
else location.href = url
popup = null // reverse-tabnabbing #460
setTimeout(function () { URL.revokeObjectURL(url) }, 4E4) // 40s
}
}
)
_global.saveAs = saveAs.saveAs = saveAs _global.saveAs = saveAs.saveAs = saveAs