StreamSaver

streamsaver@2.0.5/StreamSaver.js

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.gf.qytechs.cn/scripts/469054/1208001/StreamSaver.js

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址