|
1 | | -/* FileSaver.js |
2 | | - * A saveAs() FileSaver implementation. |
3 | | - * 1.3.2 |
4 | | - * 2016-06-16 18:25:19 |
5 | | - * |
6 | | - * By Eli Grey, http://eligrey.com |
7 | | - * License: MIT |
8 | | - * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md |
9 | | - */ |
10 | | - |
11 | | -/*global self */ |
12 | | -/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */ |
13 | | - |
14 | | -/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ |
15 | | - |
16 | | -var saveAs = saveAs || (function(view) { |
17 | | -"use strict"; |
18 | | -// IE <10 is explicitly unsupported |
19 | | -if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) { |
20 | | -return; |
21 | | -} |
22 | | -var |
23 | | - doc = view.document |
24 | | - // only get URL when necessary in case Blob.js hasn't overridden it yet |
25 | | -, get_URL = function() { |
26 | | -return view.URL || view.webkitURL || view; |
27 | | -} |
28 | | -, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a") |
29 | | -, can_use_save_link = "download" in save_link |
30 | | -, click = function(node) { |
31 | | -var event = new MouseEvent("click"); |
32 | | -node.dispatchEvent(event); |
33 | | -} |
34 | | -, is_safari = /constructor/i.test(view.HTMLElement) |
35 | | -, is_chrome_ios =/CriOS\/[\d]+/.test(navigator.userAgent) |
36 | | -, throw_outside = function(ex) { |
37 | | -(view.setImmediate || view.setTimeout)(function() { |
38 | | -throw ex; |
39 | | -}, 0); |
40 | | -} |
41 | | -, force_saveable_type = "application/octet-stream" |
42 | | -// the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to |
43 | | -, arbitrary_revoke_timeout = 1000 * 40 // in ms |
44 | | -, revoke = function(file) { |
45 | | -var revoker = function() { |
46 | | -if (typeof file === "string") { // file is an object URL |
47 | | -get_URL().revokeObjectURL(file); |
48 | | -} else { // file is a File |
49 | | -file.remove(); |
50 | | -} |
51 | | -}; |
52 | | -setTimeout(revoker, arbitrary_revoke_timeout); |
53 | | -} |
54 | | -, dispatch = function(filesaver, event_types, event) { |
55 | | -event_types = [].concat(event_types); |
56 | | -var i = event_types.length; |
57 | | -while (i--) { |
58 | | -var listener = filesaver["on" + event_types[i]]; |
59 | | -if (typeof listener === "function") { |
60 | | -try { |
61 | | -listener.call(filesaver, event || filesaver); |
62 | | -} catch (ex) { |
63 | | -throw_outside(ex); |
64 | | -} |
65 | | -} |
66 | | -} |
67 | | -} |
68 | | -, auto_bom = function(blob) { |
69 | | -// prepend BOM for UTF-8 XML and text/* types (including HTML) |
70 | | -// note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF |
71 | | -if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { |
72 | | -return new Blob([String.fromCharCode(0xFEFF), blob], {type: blob.type}); |
73 | | -} |
74 | | -return blob; |
75 | | -} |
76 | | -, FileSaver = function(blob, name, no_auto_bom) { |
77 | | -if (!no_auto_bom) { |
78 | | -blob = auto_bom(blob); |
79 | | -} |
80 | | -// First try a.download, then web filesystem, then object URLs |
81 | | -var |
82 | | - filesaver = this |
83 | | -, type = blob.type |
84 | | -, force = type === force_saveable_type |
85 | | -, object_url |
86 | | -, dispatch_all = function() { |
87 | | -dispatch(filesaver, "writestart progress write writeend".split(" ")); |
88 | | -} |
89 | | -// on any filesys errors revert to saving with object URLs |
90 | | -, fs_error = function() { |
91 | | -if ((is_chrome_ios || (force && is_safari)) && view.FileReader) { |
92 | | -// Safari doesn't allow downloading of blob urls |
93 | | -var reader = new FileReader(); |
94 | | -reader.onloadend = function() { |
95 | | -var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;'); |
96 | | -var popup = view.open(url, '_blank'); |
97 | | -if(!popup) view.location.href = url; |
98 | | -url=undefined; // release reference before dispatching |
99 | | -filesaver.readyState = filesaver.DONE; |
100 | | -dispatch_all(); |
101 | | -}; |
102 | | -reader.readAsDataURL(blob); |
103 | | -filesaver.readyState = filesaver.INIT; |
104 | | -return; |
105 | | -} |
106 | | -// don't create more object URLs than needed |
107 | | -if (!object_url) { |
108 | | -object_url = get_URL().createObjectURL(blob); |
109 | | -} |
110 | | -if (force) { |
111 | | -view.location.href = object_url; |
112 | | -} else { |
113 | | -var opened = view.open(object_url, "_blank"); |
114 | | -if (!opened) { |
115 | | -// Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html |
116 | | -view.location.href = object_url; |
117 | | -} |
118 | | -} |
119 | | -filesaver.readyState = filesaver.DONE; |
120 | | -dispatch_all(); |
121 | | -revoke(object_url); |
122 | | -} |
123 | | -; |
124 | | -filesaver.readyState = filesaver.INIT; |
125 | | - |
126 | | -if (can_use_save_link) { |
127 | | -object_url = get_URL().createObjectURL(blob); |
128 | | -setTimeout(function() { |
129 | | -save_link.href = object_url; |
130 | | -save_link.download = name; |
131 | | -click(save_link); |
132 | | -dispatch_all(); |
133 | | -revoke(object_url); |
134 | | -filesaver.readyState = filesaver.DONE; |
135 | | -}); |
136 | | -return; |
137 | | -} |
138 | | - |
139 | | -fs_error(); |
140 | | -} |
141 | | -, FS_proto = FileSaver.prototype |
142 | | -, saveAs = function(blob, name, no_auto_bom) { |
143 | | -return new FileSaver(blob, name || blob.name || "download", no_auto_bom); |
144 | | -} |
145 | | -; |
146 | | -// IE 10+ (native saveAs) |
147 | | -if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) { |
148 | | -return function(blob, name, no_auto_bom) { |
149 | | -name = name || blob.name || "download"; |
150 | | - |
151 | | -if (!no_auto_bom) { |
152 | | -blob = auto_bom(blob); |
153 | | -} |
154 | | -return navigator.msSaveOrOpenBlob(blob, name); |
155 | | -}; |
156 | | -} |
157 | | - |
158 | | -FS_proto.abort = function(){}; |
159 | | -FS_proto.readyState = FS_proto.INIT = 0; |
160 | | -FS_proto.WRITING = 1; |
161 | | -FS_proto.DONE = 2; |
162 | | - |
163 | | -FS_proto.error = |
164 | | -FS_proto.onwritestart = |
165 | | -FS_proto.onprogress = |
166 | | -FS_proto.onwrite = |
167 | | -FS_proto.onabort = |
168 | | -FS_proto.onerror = |
169 | | -FS_proto.onwriteend = |
170 | | -null; |
171 | | - |
172 | | -return saveAs; |
173 | | -}( |
174 | | - typeof self !== "undefined" && self |
175 | | -|| typeof window !== "undefined" && window |
176 | | -|| this.content |
177 | | -)); |
178 | | -// `self` is undefined in Firefox for Android content script context |
179 | | -// while `this` is nsIContentFrameMessageManager |
180 | | -// with an attribute `content` that corresponds to the window |
181 | | - |
182 | | -if (typeof module !== "undefined" && module.exports) { |
183 | | - module.exports.saveAs = saveAs; |
184 | | -} else if ((typeof define !== "undefined" && define !== null) && (define.amd !== null)) { |
185 | | - define([], function() { |
186 | | - return saveAs; |
187 | | - }); |
| 1 | +/* |
| 2 | +* FileSaver.js |
| 3 | +* A saveAs() FileSaver implementation. |
| 4 | +* |
| 5 | +* By Eli Grey, http://eligrey.com |
| 6 | +* |
| 7 | +* License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT) |
| 8 | +* source : http://purl.eligrey.com/github/FileSaver.js |
| 9 | +*/ |
| 10 | + |
| 11 | +// The one and only way of getting global scope in all environments |
| 12 | +// https://stackoverflow.com/q/3277182/1008999 |
| 13 | +var _global = typeof window === 'object' && window.window === window |
| 14 | + ? window : typeof self === 'object' && self.self === self |
| 15 | + ? self : typeof global === 'object' && global.global === global |
| 16 | + ? global |
| 17 | + : this |
| 18 | + |
| 19 | +function bom (blob, opts) { |
| 20 | + if (typeof opts === 'undefined') opts = { autoBom: false } |
| 21 | + else if (typeof opts !== 'object') { |
| 22 | + console.warn('Deprecated: Expected third argument to be a object') |
| 23 | + opts = { autoBom: !opts } |
| 24 | + } |
| 25 | + |
| 26 | + // prepend BOM for UTF-8 XML and text/* types (including HTML) |
| 27 | + // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF |
| 28 | + if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { |
| 29 | + return new Blob([String.fromCharCode(0xFEFF), blob], { type: blob.type }) |
| 30 | + } |
| 31 | + return blob |
| 32 | +} |
| 33 | + |
| 34 | +function download (url, name, opts) { |
| 35 | + var xhr = new XMLHttpRequest() |
| 36 | + xhr.open('GET', url) |
| 37 | + xhr.responseType = 'blob' |
| 38 | + xhr.onload = function () { |
| 39 | + saveAs(xhr.response, name, opts) |
| 40 | + } |
| 41 | + xhr.onerror = function () { |
| 42 | + console.error('could not download file') |
| 43 | + } |
| 44 | + xhr.send() |
| 45 | +} |
| 46 | + |
| 47 | +function corsEnabled (url) { |
| 48 | + var xhr = new XMLHttpRequest() |
| 49 | + // use sync to avoid popup blocker |
| 50 | + xhr.open('HEAD', url, false) |
| 51 | + try { |
| 52 | + xhr.send() |
| 53 | + } catch (e) {} |
| 54 | + return xhr.status >= 200 && xhr.status <= 299 |
| 55 | +} |
| 56 | + |
| 57 | +// `a.click()` doesn't work for all browsers (#465) |
| 58 | +function click (node) { |
| 59 | + try { |
| 60 | + node.dispatchEvent(new MouseEvent('click')) |
| 61 | + } catch (e) { |
| 62 | + var evt = document.createEvent('MouseEvents') |
| 63 | + evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, |
| 64 | + 20, false, false, false, false, 0, null) |
| 65 | + node.dispatchEvent(evt) |
| 66 | + } |
| 67 | +} |
| 68 | + |
| 69 | +// Detect WebView inside a native macOS app by ruling out all browsers |
| 70 | +// We just need to check for 'Safari' because all other browsers (besides Firefox) include that too |
| 71 | +// https://www.whatismybrowser.com/guides/the-latest-user-agent/macos |
| 72 | +var isMacOSWebView = _global.navigator && /Macintosh/.test(navigator.userAgent) && /AppleWebKit/.test(navigator.userAgent) && !/Safari/.test(navigator.userAgent) |
| 73 | + |
| 74 | +var saveAs = _global.saveAs || ( |
| 75 | + // probably in some web worker |
| 76 | + (typeof window !== 'object' || window !== _global) |
| 77 | + ? function saveAs () { /* noop */ } |
| 78 | + |
| 79 | + // Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView |
| 80 | + : ('download' in HTMLAnchorElement.prototype && !isMacOSWebView) |
| 81 | + ? function saveAs (blob, name, opts) { |
| 82 | + var URL = _global.URL || _global.webkitURL |
| 83 | + var a = document.createElement('a') |
| 84 | + name = name || blob.name || 'download' |
| 85 | + |
| 86 | + a.download = name |
| 87 | + a.rel = 'noopener' // tabnabbing |
| 88 | + |
| 89 | + // TODO: detect chrome extensions & packaged apps |
| 90 | + // a.target = '_blank' |
| 91 | + |
| 92 | + if (typeof blob === 'string') { |
| 93 | + // Support regular links |
| 94 | + a.href = blob |
| 95 | + if (a.origin !== location.origin) { |
| 96 | + corsEnabled(a.href) |
| 97 | + ? download(blob, name, opts) |
| 98 | + : click(a, a.target = '_blank') |
| 99 | + } else { |
| 100 | + click(a) |
| 101 | + } |
| 102 | + } else { |
| 103 | + // Support blobs |
| 104 | + a.href = URL.createObjectURL(blob) |
| 105 | + setTimeout(function () { URL.revokeObjectURL(a.href) }, 4E4) // 40s |
| 106 | + setTimeout(function () { click(a) }, 0) |
| 107 | + } |
| 108 | + } |
| 109 | + |
| 110 | + // Use msSaveOrOpenBlob as a second approach |
| 111 | + : 'msSaveOrOpenBlob' in navigator |
| 112 | + ? function saveAs (blob, name, opts) { |
| 113 | + name = name || blob.name || 'download' |
| 114 | + |
| 115 | + if (typeof blob === 'string') { |
| 116 | + if (corsEnabled(blob)) { |
| 117 | + download(blob, name, opts) |
| 118 | + } else { |
| 119 | + var a = document.createElement('a') |
| 120 | + a.href = blob |
| 121 | + a.target = '_blank' |
| 122 | + setTimeout(function () { click(a) }) |
| 123 | + } |
| 124 | + } else { |
| 125 | + navigator.msSaveOrOpenBlob(bom(blob, opts), name) |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 129 | + // Fallback to using FileReader and a popup |
| 130 | + : function saveAs (blob, name, opts, popup) { |
| 131 | + // Open a popup immediately do go around popup blocker |
| 132 | + // Mostly only available on user interaction and the fileReader is async so... |
| 133 | + popup = popup || open('', '_blank') |
| 134 | + if (popup) { |
| 135 | + popup.document.title = |
| 136 | + popup.document.body.innerText = 'downloading...' |
| 137 | + } |
| 138 | + |
| 139 | + if (typeof blob === 'string') return download(blob, name, opts) |
| 140 | + |
| 141 | + var force = blob.type === 'application/octet-stream' |
| 142 | + var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari |
| 143 | + var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent) |
| 144 | + |
| 145 | + if ((isChromeIOS || (force && isSafari) || isMacOSWebView) && typeof FileReader !== 'undefined') { |
| 146 | + // Safari doesn't allow downloading of blob URLs |
| 147 | + var reader = new FileReader() |
| 148 | + reader.onloadend = function () { |
| 149 | + var url = reader.result |
| 150 | + url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;') |
| 151 | + if (popup) popup.location.href = url |
| 152 | + else location = url |
| 153 | + popup = null // reverse-tabnabbing #460 |
| 154 | + } |
| 155 | + reader.readAsDataURL(blob) |
| 156 | + } else { |
| 157 | + var URL = _global.URL || _global.webkitURL |
| 158 | + var url = URL.createObjectURL(blob) |
| 159 | + if (popup) popup.location = url |
| 160 | + else location.href = url |
| 161 | + popup = null // reverse-tabnabbing #460 |
| 162 | + setTimeout(function () { URL.revokeObjectURL(url) }, 4E4) // 40s |
| 163 | + } |
| 164 | + } |
| 165 | +) |
| 166 | + |
| 167 | +_global.saveAs = saveAs.saveAs = saveAs |
| 168 | + |
| 169 | +if (typeof module !== 'undefined') { |
| 170 | + module.exports = saveAs; |
188 | 171 | } |
0 commit comments