IF YOU WOULD LIKE TO GET AN ACCOUNT, please write an email to s dot adaszewski at gmail dot com. User accounts are meant only to report issues and/or generate pull requests. This is a purpose-specific Git hosting for ADARED projects. Thank you for your understanding!
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

297 строки
9.3KB

  1. /* global chrome location ReadableStream define MessageChannel TransformStream */
  2. ;((name, definition) => {
  3. typeof module !== 'undefined'
  4. ? module.exports = definition()
  5. : typeof define === 'function' && typeof define.amd === 'object'
  6. ? define(definition)
  7. : this[name] = definition()
  8. })('streamSaver', () => {
  9. 'use strict'
  10. let mitmTransporter = null
  11. let supportsTransferable = false
  12. const test = fn => { try { fn() } catch (e) {} }
  13. const ponyfill = window.WebStreamsPolyfill || {}
  14. const isSecureContext = window.isSecureContext
  15. let useBlobFallback = /constructor/i.test(window.HTMLElement) || !!window.safari
  16. const downloadStrategy = isSecureContext || 'MozAppearance' in document.documentElement.style
  17. ? 'iframe'
  18. : 'navigate'
  19. const streamSaver = {
  20. createWriteStream,
  21. WritableStream: window.WritableStream || ponyfill.WritableStream,
  22. supported: true,
  23. version: { full: '2.0.0', major: 2, minor: 0, dot: 0 },
  24. mitm: 'https://jimmywarting.github.io/StreamSaver.js/mitm.html?version=2.0.0'
  25. }
  26. /**
  27. * create a hidden iframe and append it to the DOM (body)
  28. *
  29. * @param {string} src page to load
  30. * @return {HTMLIFrameElement} page to load
  31. */
  32. function makeIframe (src) {
  33. if (!src) throw new Error('meh')
  34. const iframe = document.createElement('iframe')
  35. iframe.hidden = true
  36. iframe.src = src
  37. iframe.loaded = false
  38. iframe.name = 'iframe'
  39. iframe.isIframe = true
  40. iframe.postMessage = (...args) => iframe.contentWindow.postMessage(...args)
  41. iframe.addEventListener('load', () => {
  42. iframe.loaded = true
  43. }, { once: true })
  44. document.body.appendChild(iframe)
  45. return iframe
  46. }
  47. /**
  48. * create a popup that simulates the basic things
  49. * of what a iframe can do
  50. *
  51. * @param {string} src page to load
  52. * @return {object} iframe like object
  53. */
  54. function makePopup (src) {
  55. const options = 'width=200,height=100'
  56. const delegate = document.createDocumentFragment()
  57. const popup = {
  58. frame: window.open(src, 'popup', options),
  59. loaded: false,
  60. isIframe: false,
  61. isPopup: true,
  62. remove () { popup.frame.close() },
  63. addEventListener (...args) { delegate.addEventListener(...args) },
  64. dispatchEvent (...args) { delegate.dispatchEvent(...args) },
  65. removeEventListener (...args) { delegate.removeEventListener(...args) },
  66. postMessage (...args) { popup.frame.postMessage(...args) }
  67. }
  68. const onReady = evt => {
  69. if (evt.source === popup.frame) {
  70. popup.loaded = true
  71. window.removeEventListener('message', onReady)
  72. popup.dispatchEvent(new Event('load'))
  73. }
  74. }
  75. window.addEventListener('message', onReady)
  76. return popup
  77. }
  78. try {
  79. // We can't look for service worker since it may still work on http
  80. new Response(new ReadableStream())
  81. if (isSecureContext && !('serviceWorker' in navigator)) {
  82. useBlobFallback = true
  83. }
  84. } catch (err) {
  85. useBlobFallback = true
  86. }
  87. test(() => {
  88. // Transfariable stream was first enabled in chrome v73 behind a flag
  89. const { readable } = new TransformStream()
  90. const mc = new MessageChannel()
  91. mc.port1.postMessage(readable, [readable])
  92. mc.port1.close()
  93. mc.port2.close()
  94. supportsTransferable = true
  95. // Freeze TransformStream object (can only work with native)
  96. Object.defineProperty(streamSaver, 'TransformStream', {
  97. configurable: false,
  98. writable: false,
  99. value: TransformStream
  100. })
  101. })
  102. function loadTransporter () {
  103. if (!mitmTransporter) {
  104. mitmTransporter = isSecureContext
  105. ? makeIframe(streamSaver.mitm)
  106. : makePopup(streamSaver.mitm)
  107. }
  108. }
  109. /**
  110. * @param {string} filename filename that should be used
  111. * @param {object} options [description]
  112. * @param {number} size depricated
  113. * @return {WritableStream}
  114. */
  115. function createWriteStream (filename, options, size) {
  116. let opts = {
  117. size: null,
  118. pathname: null,
  119. writableStrategy: undefined,
  120. readableStrategy: undefined
  121. }
  122. // normalize arguments
  123. if (Number.isFinite(options)) {
  124. [ size, options ] = [ options, size ]
  125. console.warn('[StreamSaver] Depricated pass an object as 2nd argument when creating a write stream')
  126. opts.size = size
  127. opts.writableStrategy = options
  128. } else if (options && options.highWaterMark) {
  129. console.warn('[StreamSaver] Depricated pass an object as 2nd argument when creating a write stream')
  130. opts.size = size
  131. opts.writableStrategy = options
  132. } else {
  133. opts = options || {}
  134. }
  135. if (!useBlobFallback) {
  136. loadTransporter()
  137. var bytesWritten = 0 // by StreamSaver.js (not the service worker)
  138. var downloadUrl = null
  139. var channel = new MessageChannel()
  140. // Make filename RFC5987 compatible
  141. filename = encodeURIComponent(filename.replace(/\//g, ':'))
  142. .replace(/['()]/g, escape)
  143. .replace(/\*/g, '%2A')
  144. const response = {
  145. transferringReadable: supportsTransferable,
  146. pathname: opts.pathname || Math.random().toString().slice(-6) + '/' + filename,
  147. headers: {
  148. 'Content-Type': (options.contentType ? options.contentType :
  149. 'application/octet-stream; charset=utf-8'),
  150. 'Content-Disposition': (options.inline ? 'inline' :
  151. ("attachment; filename*=UTF-8''" + filename))
  152. }
  153. }
  154. if (opts.size) {
  155. response.headers['Content-Length'] = opts.size
  156. }
  157. const args = [ response, '*', [ channel.port2 ] ]
  158. if (supportsTransferable) {
  159. const transformer = downloadStrategy === 'iframe' ? undefined : {
  160. // This transformer & flush method is only used by insecure context.
  161. transform (chunk, controller) {
  162. bytesWritten += chunk.length
  163. controller.enqueue(chunk)
  164. if (downloadUrl) {
  165. location.href = downloadUrl
  166. downloadUrl = null
  167. }
  168. },
  169. flush () {
  170. if (downloadUrl) {
  171. location.href = downloadUrl
  172. }
  173. }
  174. }
  175. var ts = new streamSaver.TransformStream(
  176. transformer,
  177. opts.writableStrategy,
  178. opts.readableStrategy
  179. )
  180. const readableStream = ts.readable
  181. channel.port1.postMessage({ readableStream }, [ readableStream ])
  182. }
  183. channel.port1.onmessage = evt => {
  184. // Service worker sent us a link that we should open.
  185. if (evt.data.download) {
  186. // Special treatment for popup...
  187. if (downloadStrategy === 'navigate') {
  188. mitmTransporter.remove()
  189. mitmTransporter = null
  190. if (bytesWritten) {
  191. location.href = evt.data.download
  192. } else {
  193. downloadUrl = evt.data.download
  194. }
  195. } else {
  196. if (mitmTransporter.isPopup) {
  197. mitmTransporter.remove()
  198. // Special case for firefox, they can keep sw alive with fetch
  199. if (downloadStrategy === 'iframe') {
  200. makeIframe(streamSaver.mitm)
  201. }
  202. }
  203. // We never remove this iframes b/c it can interrupt saving
  204. makeIframe(evt.data.download)
  205. }
  206. }
  207. }
  208. if (mitmTransporter.loaded) {
  209. mitmTransporter.postMessage(...args)
  210. } else {
  211. mitmTransporter.addEventListener('load', () => {
  212. mitmTransporter.postMessage(...args)
  213. }, { once: true })
  214. }
  215. }
  216. let chunks = []
  217. return (!useBlobFallback && ts && ts.writable) || new streamSaver.WritableStream({
  218. write (chunk) {
  219. if (useBlobFallback) {
  220. // Safari... The new IE6
  221. // https://github.com/jimmywarting/StreamSaver.js/issues/69
  222. //
  223. // even doe it has everything it fails to download anything
  224. // that comes from the service worker..!
  225. chunks.push(chunk)
  226. return
  227. }
  228. // is called when a new chunk of data is ready to be written
  229. // to the underlying sink. It can return a promise to signal
  230. // success or failure of the write operation. The stream
  231. // implementation guarantees that this method will be called
  232. // only after previous writes have succeeded, and never after
  233. // close or abort is called.
  234. // TODO: Kind of important that service worker respond back when
  235. // it has been written. Otherwise we can't handle backpressure
  236. // EDIT: Transfarable streams solvs this...
  237. channel.port1.postMessage(chunk)
  238. bytesWritten += chunk.length
  239. if (downloadUrl) {
  240. location.href = downloadUrl
  241. downloadUrl = null
  242. }
  243. },
  244. close () {
  245. if (useBlobFallback) {
  246. const blob = new Blob(chunks, { type: 'application/octet-stream; charset=utf-8' })
  247. const link = document.createElement('a')
  248. link.href = URL.createObjectURL(blob)
  249. link.download = filename
  250. link.click()
  251. } else {
  252. channel.port1.postMessage('end')
  253. }
  254. },
  255. abort () {
  256. chunks = []
  257. channel.port1.postMessage('abort')
  258. channel.port1.onmessage = null
  259. channel.port1.close()
  260. channel.port2.close()
  261. channel = null
  262. }
  263. }, opts.writableStrategy)
  264. }
  265. return streamSaver
  266. })