|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296 |
- /* global chrome location ReadableStream define MessageChannel TransformStream */
-
- ;((name, definition) => {
- typeof module !== 'undefined'
- ? module.exports = definition()
- : typeof define === 'function' && typeof define.amd === 'object'
- ? define(definition)
- : this[name] = definition()
- })('streamSaver', () => {
- 'use strict'
-
- let mitmTransporter = null
- let supportsTransferable = false
- const test = fn => { try { fn() } catch (e) {} }
- const ponyfill = window.WebStreamsPolyfill || {}
- const isSecureContext = window.isSecureContext
- let useBlobFallback = /constructor/i.test(window.HTMLElement) || !!window.safari
- const downloadStrategy = isSecureContext || 'MozAppearance' in document.documentElement.style
- ? 'iframe'
- : 'navigate'
-
- const streamSaver = {
- createWriteStream,
- WritableStream: window.WritableStream || ponyfill.WritableStream,
- supported: true,
- version: { full: '2.0.0', major: 2, minor: 0, dot: 0 },
- mitm: 'https://jimmywarting.github.io/StreamSaver.js/mitm.html?version=2.0.0'
- }
-
- /**
- * create a hidden iframe and append it to the DOM (body)
- *
- * @param {string} src page to load
- * @return {HTMLIFrameElement} page to load
- */
- function makeIframe (src) {
- if (!src) throw new Error('meh')
- const iframe = document.createElement('iframe')
- iframe.hidden = true
- iframe.src = src
- iframe.loaded = false
- iframe.name = 'iframe'
- iframe.isIframe = true
- iframe.postMessage = (...args) => iframe.contentWindow.postMessage(...args)
- iframe.addEventListener('load', () => {
- iframe.loaded = true
- }, { once: true })
- document.body.appendChild(iframe)
- return iframe
- }
-
- /**
- * create a popup that simulates the basic things
- * of what a iframe can do
- *
- * @param {string} src page to load
- * @return {object} iframe like object
- */
- function makePopup (src) {
- const options = 'width=200,height=100'
- const delegate = document.createDocumentFragment()
- const popup = {
- frame: window.open(src, 'popup', options),
- loaded: false,
- isIframe: false,
- isPopup: true,
- remove () { popup.frame.close() },
- addEventListener (...args) { delegate.addEventListener(...args) },
- dispatchEvent (...args) { delegate.dispatchEvent(...args) },
- removeEventListener (...args) { delegate.removeEventListener(...args) },
- postMessage (...args) { popup.frame.postMessage(...args) }
- }
-
- const onReady = evt => {
- if (evt.source === popup.frame) {
- popup.loaded = true
- window.removeEventListener('message', onReady)
- popup.dispatchEvent(new Event('load'))
- }
- }
-
- window.addEventListener('message', onReady)
-
- return popup
- }
-
- try {
- // We can't look for service worker since it may still work on http
- new Response(new ReadableStream())
- if (isSecureContext && !('serviceWorker' in navigator)) {
- useBlobFallback = true
- }
- } catch (err) {
- useBlobFallback = true
- }
-
- test(() => {
- // Transfariable stream was first enabled in chrome v73 behind a flag
- const { readable } = new TransformStream()
- const mc = new MessageChannel()
- mc.port1.postMessage(readable, [readable])
- mc.port1.close()
- mc.port2.close()
- supportsTransferable = true
- // Freeze TransformStream object (can only work with native)
- Object.defineProperty(streamSaver, 'TransformStream', {
- configurable: false,
- writable: false,
- value: TransformStream
- })
- })
-
- function loadTransporter () {
- if (!mitmTransporter) {
- mitmTransporter = isSecureContext
- ? makeIframe(streamSaver.mitm)
- : makePopup(streamSaver.mitm)
- }
- }
-
- /**
- * @param {string} filename filename that should be used
- * @param {object} options [description]
- * @param {number} size depricated
- * @return {WritableStream}
- */
- function createWriteStream (filename, options, size) {
- let opts = {
- size: null,
- pathname: null,
- writableStrategy: undefined,
- readableStrategy: undefined
- }
-
- // normalize arguments
- if (Number.isFinite(options)) {
- [ size, options ] = [ options, size ]
- console.warn('[StreamSaver] Depricated pass an object as 2nd argument when creating a write stream')
- opts.size = size
- opts.writableStrategy = options
- } else if (options && options.highWaterMark) {
- console.warn('[StreamSaver] Depricated pass an object as 2nd argument when creating a write stream')
- opts.size = size
- opts.writableStrategy = options
- } else {
- opts = options || {}
- }
- if (!useBlobFallback) {
- loadTransporter()
-
- var bytesWritten = 0 // by StreamSaver.js (not the service worker)
- var downloadUrl = null
- var channel = new MessageChannel()
-
- // Make filename RFC5987 compatible
- filename = encodeURIComponent(filename.replace(/\//g, ':'))
- .replace(/['()]/g, escape)
- .replace(/\*/g, '%2A')
-
- const response = {
- transferringReadable: supportsTransferable,
- pathname: opts.pathname || Math.random().toString().slice(-6) + '/' + filename,
- headers: {
- 'Content-Type': (options.contentType ? options.contentType :
- 'application/octet-stream; charset=utf-8'),
- 'Content-Disposition': (options.inline ? 'inline' :
- ("attachment; filename*=UTF-8''" + filename))
- }
- }
-
- if (opts.size) {
- response.headers['Content-Length'] = opts.size
- }
-
- const args = [ response, '*', [ channel.port2 ] ]
-
- if (supportsTransferable) {
- const transformer = downloadStrategy === 'iframe' ? undefined : {
- // This transformer & flush method is only used by insecure context.
- transform (chunk, controller) {
- bytesWritten += chunk.length
- controller.enqueue(chunk)
-
- if (downloadUrl) {
- location.href = downloadUrl
- downloadUrl = null
- }
- },
- flush () {
- if (downloadUrl) {
- location.href = downloadUrl
- }
- }
- }
- var ts = new streamSaver.TransformStream(
- transformer,
- opts.writableStrategy,
- opts.readableStrategy
- )
- const readableStream = ts.readable
-
- channel.port1.postMessage({ readableStream }, [ readableStream ])
- }
-
- channel.port1.onmessage = evt => {
- // Service worker sent us a link that we should open.
- if (evt.data.download) {
- // Special treatment for popup...
- if (downloadStrategy === 'navigate') {
- mitmTransporter.remove()
- mitmTransporter = null
- if (bytesWritten) {
- location.href = evt.data.download
- } else {
- downloadUrl = evt.data.download
- }
- } else {
- if (mitmTransporter.isPopup) {
- mitmTransporter.remove()
- // Special case for firefox, they can keep sw alive with fetch
- if (downloadStrategy === 'iframe') {
- makeIframe(streamSaver.mitm)
- }
- }
-
- // We never remove this iframes b/c it can interrupt saving
- makeIframe(evt.data.download)
- }
- }
- }
-
- if (mitmTransporter.loaded) {
- mitmTransporter.postMessage(...args)
- } else {
- mitmTransporter.addEventListener('load', () => {
- mitmTransporter.postMessage(...args)
- }, { once: true })
- }
- }
-
- let chunks = []
-
- return (!useBlobFallback && ts && ts.writable) || new streamSaver.WritableStream({
- write (chunk) {
- if (useBlobFallback) {
- // Safari... The new IE6
- // https://github.com/jimmywarting/StreamSaver.js/issues/69
- //
- // even doe it has everything it fails to download anything
- // that comes from the service worker..!
- chunks.push(chunk)
- return
- }
-
- // is called when a new chunk of data is ready to be written
- // to the underlying sink. It can return a promise to signal
- // success or failure of the write operation. The stream
- // implementation guarantees that this method will be called
- // only after previous writes have succeeded, and never after
- // close or abort is called.
-
- // TODO: Kind of important that service worker respond back when
- // it has been written. Otherwise we can't handle backpressure
- // EDIT: Transfarable streams solvs this...
- channel.port1.postMessage(chunk)
- bytesWritten += chunk.length
-
- if (downloadUrl) {
- location.href = downloadUrl
- downloadUrl = null
- }
- },
- close () {
- if (useBlobFallback) {
- const blob = new Blob(chunks, { type: 'application/octet-stream; charset=utf-8' })
- const link = document.createElement('a')
- link.href = URL.createObjectURL(blob)
- link.download = filename
- link.click()
- } else {
- channel.port1.postMessage('end')
- }
- },
- abort () {
- chunks = []
- channel.port1.postMessage('abort')
- channel.port1.onmessage = null
- channel.port1.close()
- channel.port2.close()
- channel = null
- }
- }, opts.writableStrategy)
- }
-
- return streamSaver
- })
|