mirror of
https://github.com/SrIzan10/vdo.ninja.git
synced 2026-05-01 11:05:24 +00:00
Add files via upload
This commit is contained in:
317
thirdparty/StreamSaver.js
vendored
317
thirdparty/StreamSaver.js
vendored
@@ -1,4 +1,313 @@
|
|||||||
/* global chrome location ReadableStream define MessageChannel TransformStream */
|
/*! streamsaver. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||||
// https://github.com/jimmywarting/StreamSaver.js
|
|
||||||
// MIT License
|
/* global chrome location ReadableStream define MessageChannel TransformStream */
|
||||||
((e,t)=>{"undefined"!=typeof module?module.exports=t():"function"==typeof define&&"object"==typeof define.amd?define(t):this.streamSaver=t()})(0,()=>{"use strict";const e="object"==typeof window?window:this;e.HTMLElement||console.warn("streamsaver is meant to run on browsers main thread");let t=null,a=!1;const r=e.WebStreamsPolyfill||{},n=e.isSecureContext;let o=/constructor/i.test(e.HTMLElement)||!!e.safari||!!e.WebKitPoint;const s=n||"MozAppearance"in document.documentElement.style?"iframe":"navigate",i={createWriteStream:function(r,m,d){let c={size:null,pathname:null,writableStrategy:void 0,readableStrategy:void 0},p=0,f=null,u=null,w=null;Number.isFinite(m)?([d,m]=[m,d],console.warn("[StreamSaver] Depricated pass an object as 2nd argument when creating a write stream"),c.size=d,c.writableStrategy=m):m&&m.highWaterMark?(console.warn("[StreamSaver] Depricated pass an object as 2nd argument when creating a write stream"),c.size=d,c.writableStrategy=m):c=m||{};if(!o){t||(t=n?l(i.mitm):function(t){const a=document.createDocumentFragment(),r={frame:e.open(t,"popup","width=200,height=100"),loaded:!1,isIframe:!1,isPopup:!0,remove(){r.frame.close()},addEventListener(...e){a.addEventListener(...e)},dispatchEvent(...e){a.dispatchEvent(...e)},removeEventListener(...e){a.removeEventListener(...e)},postMessage(...e){r.frame.postMessage(...e)}},n=t=>{t.source===r.frame&&(r.loaded=!0,e.removeEventListener("message",n),r.dispatchEvent(new Event("load")))};return e.addEventListener("message",n),r}(i.mitm)),u=new MessageChannel,r=encodeURIComponent(r.replace(/\//g,":")).replace(/['()]/g,escape).replace(/\*/g,"%2A");const o={transferringReadable:a,pathname:c.pathname||Math.random().toString().slice(-6)+"/"+r,headers:{"Content-Type":"application/octet-stream; charset=utf-8","Content-Disposition":"attachment; filename*=UTF-8''"+r}};c.size&&(o.headers["Content-Length"]=c.size);const m=[o,"*",[u.port2]];if(a){const e="iframe"===s?void 0:{transform(e,t){if(!(e instanceof Uint8Array))throw new TypeError("Can only wirte Uint8Arrays");p+=e.length,t.enqueue(e),f&&(location.href=f,f=null)},flush(){f&&(location.href=f)}},t=(w=new i.TransformStream(e,c.writableStrategy,c.readableStrategy)).readable;u.port1.postMessage({readableStream:t},[t])}u.port1.onmessage=(e=>{e.data.download&&("navigate"===s?(t.remove(),t=null,p?location.href=e.data.download:f=e.data.download):(t.isPopup&&(t.remove(),t=null,"iframe"===s&&l(i.mitm)),l(e.data.download)))}),t.loaded?t.postMessage(...m):t.addEventListener("load",()=>{t.postMessage(...m)},{once:!0})}let g=[];return!o&&w&&w.writable||new i.WritableStream({write(e){if(!(e instanceof Uint8Array))throw new TypeError("Can only wirte Uint8Arrays");o?g.push(e):(u.port1.postMessage(e),p+=e.length,f&&(location.href=f,f=null))},close(){if(o){const e=new Blob(g,{type:"application/octet-stream; charset=utf-8"}),t=document.createElement("a");t.href=URL.createObjectURL(e),t.download=r,t.click()}else u.port1.postMessage("end")},abort(){g=[],u.port1.postMessage("abort"),u.port1.onmessage=null,u.port1.close(),u.port2.close(),u=null}},c.writableStrategy)},WritableStream:e.WritableStream||r.WritableStream,supported:!0,version:{full:"2.0.5",major:2,minor:0,dot:5},mitm:"./thirdparty/mitm.html?version=2.0.0"};function l(e){if(!e)throw new Error("meh");const t=document.createElement("iframe");return t.hidden=!0,t.src=e,t.loaded=!1,t.name="iframe",t.isIframe=!0,t.postMessage=((...e)=>t.contentWindow.postMessage(...e)),t.addEventListener("load",()=>{t.loaded=!0},{once:!0}),document.body.appendChild(t),t}try{new Response(new ReadableStream),!n||"serviceWorker"in navigator||(o=!0)}catch(e){o=!0}return(e=>{try{e()}catch(e){}})(()=>{const{readable:e}=new TransformStream,t=new MessageChannel;t.port1.postMessage(e,[e]),t.port1.close(),t.port2.close(),a=!0,Object.defineProperty(i,"TransformStream",{configurable:!1,writable:!1,value:TransformStream})}),i});
|
|
||||||
|
;((name, definition) => {
|
||||||
|
typeof module !== 'undefined'
|
||||||
|
? module.exports = definition()
|
||||||
|
: typeof define === 'function' && typeof define.amd === 'object'
|
||||||
|
? define(definition)
|
||||||
|
: this[name] = definition()
|
||||||
|
})('streamSaver', () => {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const global = typeof window === 'object' ? window : this
|
||||||
|
if (!global.HTMLElement) console.warn('streamsaver is meant to run on browsers main thread')
|
||||||
|
|
||||||
|
let mitmTransporter = null
|
||||||
|
let supportsTransferable = false
|
||||||
|
const test = fn => { try { fn() } catch (e) {} }
|
||||||
|
const ponyfill = global.WebStreamsPolyfill || {}
|
||||||
|
const isSecureContext = global.isSecureContext
|
||||||
|
// TODO: Must come up with a real detection test (#69)
|
||||||
|
let useBlobFallback = /constructor/i.test(global.HTMLElement) || !!global.safari || !!global.WebKitPoint
|
||||||
|
const downloadStrategy = isSecureContext || 'MozAppearance' in document.documentElement.style
|
||||||
|
? 'iframe'
|
||||||
|
: 'navigate'
|
||||||
|
|
||||||
|
const streamSaver = {
|
||||||
|
createWriteStream,
|
||||||
|
WritableStream: global.WritableStream || ponyfill.WritableStream,
|
||||||
|
supported: true,
|
||||||
|
version: { full: '2.0.6', major: 2, minor: 0, dot: 6 },
|
||||||
|
mitm: 'https://steveseguin.github.io/StreamSaver.js/mitm.html?version=2.0.6'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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: global.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
|
||||||
|
global.removeEventListener('message', onReady)
|
||||||
|
popup.dispatchEvent(new Event('load'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global.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(() => {
|
||||||
|
// Transferable 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 deprecated
|
||||||
|
* @return {WritableStream<Uint8Array>}
|
||||||
|
*/
|
||||||
|
function createWriteStream (filename, stopStream){
|
||||||
|
let opts = {
|
||||||
|
size: null,
|
||||||
|
pathname: null,
|
||||||
|
writableStrategy: undefined,
|
||||||
|
readableStrategy: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytesWritten = 0 // by StreamSaver.js (not the service worker)
|
||||||
|
let downloadUrl = null
|
||||||
|
let channel = null
|
||||||
|
let ts = null
|
||||||
|
|
||||||
|
if (!useBlobFallback) {
|
||||||
|
loadTransporter()
|
||||||
|
|
||||||
|
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': 'application/octet-stream; charset=utf-8',
|
||||||
|
'Content-Disposition': "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) {
|
||||||
|
if (!(chunk instanceof Uint8Array)) {
|
||||||
|
throw new TypeError('Can only write Uint8Arrays')
|
||||||
|
}
|
||||||
|
bytesWritten += chunk.length
|
||||||
|
controller.enqueue(chunk)
|
||||||
|
|
||||||
|
if (downloadUrl) {
|
||||||
|
location.href = downloadUrl
|
||||||
|
downloadUrl = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
flush () {
|
||||||
|
if (downloadUrl) {
|
||||||
|
location.href = downloadUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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()
|
||||||
|
mitmTransporter = null
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
} else if (evt.data.abort) {
|
||||||
|
stopStream(false, true);
|
||||||
|
chunks = []
|
||||||
|
channel.port1.postMessage('abort') //send back so controller is aborted
|
||||||
|
channel.port1.onmessage = null
|
||||||
|
|
||||||
|
setTimeout(function(channel){
|
||||||
|
channel.port1.close()
|
||||||
|
channel.port2.close()
|
||||||
|
channel = null
|
||||||
|
},1300,channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (!(chunk instanceof Uint8Array)) {
|
||||||
|
throw new TypeError('Can only write Uint8Arrays')
|
||||||
|
}
|
||||||
|
if (useBlobFallback) {
|
||||||
|
// Safari... The new IE6
|
||||||
|
// https://github.com/jimmywarting/StreamSaver.js/issues/69
|
||||||
|
//
|
||||||
|
// even though 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: Transferable streams solves this...
|
||||||
|
try {
|
||||||
|
channel.port1.postMessage(chunk)
|
||||||
|
} catch(e){
|
||||||
|
|
||||||
|
};
|
||||||
|
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
|
||||||
|
setTimeout(function(channel){
|
||||||
|
channel.port1.close()
|
||||||
|
channel.port2.close()
|
||||||
|
channel = null
|
||||||
|
},1300,channel);
|
||||||
|
}
|
||||||
|
}, opts.writableStrategy)
|
||||||
|
}
|
||||||
|
|
||||||
|
return streamSaver
|
||||||
|
})
|
||||||
|
|||||||
165
thirdparty/mitm.html
vendored
165
thirdparty/mitm.html
vendored
@@ -1,7 +1,166 @@
|
|||||||
<!--
|
<!--
|
||||||
https://github.com/jimmywarting/StreamSaver.js/blob/master/mitm.html
|
mitm.html is the lite "man in the middle"
|
||||||
// MIT License
|
|
||||||
|
This is only meant to signal the opener's messageChannel to
|
||||||
|
the service worker - when that is done this mitm can be closed
|
||||||
|
but it's better to keep it alive since this also stops the sw
|
||||||
|
from restarting
|
||||||
|
|
||||||
|
The service worker is capable of intercepting all request and fork their
|
||||||
|
own "fake" response - wish we are going to craft
|
||||||
|
when the worker then receives a stream then the worker will tell the opener
|
||||||
|
to open up a link that will start the download
|
||||||
-->
|
-->
|
||||||
<script>
|
<script>
|
||||||
let keepAlive=()=>{keepAlive=(()=>{});var e=location.href.substr(0,location.href.lastIndexOf("/"))+"/ping",a=setInterval(()=>{sw?sw.postMessage("ping"):fetch(e).then(e=>e.text(!e.ok&&clearInterval(a)))},1e4)},messages=[];window.onmessage=(e=>messages.push(e));let sw=null,scope="";function registerWorker(){return navigator.serviceWorker.getRegistration("./").then(e=>e||navigator.serviceWorker.register("sw.js",{scope:"./thirdparty/"})).then(e=>{const a=e.installing||e.waiting;return scope=e.scope,(sw=e.active)||new Promise(r=>{a.addEventListener("statechange",fn=(()=>{"activated"===a.state&&(a.removeEventListener("statechange",fn),sw=e.active,r())}))})})}function onMessage(e){let{data:a,ports:r,origin:t}=e;if(!r||!r.length)throw new TypeError("[StreamSaver] You didn't send a messageChannel");if("object"!=typeof a)throw new TypeError("[StreamSaver] You didn't send a object");a.origin=t,a.referrer=a.referrer||document.referrer||t,a.streamSaverVersion=new URLSearchParams(location.search).get("version"),"1.2.0"===a.streamSaverVersion&&console.warn("[StreamSaver] please update streamsaver"),a.headers?new Headers(a.headers):console.warn("[StreamSaver] pass `data.headers` that you would like to pass along to the service worker\nit should be a 2D array or a key/val object that fetch's Headers api accepts"),"string"==typeof a.filename&&(console.warn("[StreamSaver] You shouldn't send `data.filename` anymore. It should be included in the Content-Disposition header option"),a.filename=a.filename.replace(/\//g,":")),a.size&&console.warn("[StreamSaver] You shouldn't send `data.size` anymore. It should be included in the content-length header option"),a.readableStream&&console.warn("[StreamSaver] You should send the readableStream in the messageChannel, not throught mitm"),a.pathname||(console.warn("[StreamSaver] Please send `data.pathname` (eg: /pictures/summer.jpg)"),a.pathname=Math.random().toString().slice(-6)+"/"+a.filename),a.pathname=a.pathname.replace(/^\/+/g,"");let n=t.replace(/(^\w+:|^)\/\//,"");if(a.url=new URL(`${scope+n}/${a.pathname}`).toString(),!a.url.startsWith(`${scope+n}/`))throw new TypeError("[StreamSaver] bad `data.pathname`");const s=a.readableStream?[r[0],a.readableStream]:[r[0]];return a.readableStream||a.transferringReadable||keepAlive(),sw.postMessage(a,s)}window.opener&&window.opener.postMessage("StreamSaver::loadedPopup","*"),navigator.serviceWorker?registerWorker().then(()=>{window.onmessage=onMessage,messages.forEach(window.onmessage)}):keepAlive();
|
// This will prevent the sw from restarting
|
||||||
|
let keepAlive = () => {
|
||||||
|
keepAlive = () => {}
|
||||||
|
var ping = location.href.substr(0, location.href.lastIndexOf('/')) + '/ping'
|
||||||
|
var interval = setInterval(() => {
|
||||||
|
if (sw) {
|
||||||
|
sw.postMessage('ping')
|
||||||
|
} else {
|
||||||
|
fetch(ping).then(res => res.text(!res.ok && clearInterval(interval)))
|
||||||
|
}
|
||||||
|
}, 10000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// message event is the first thing we need to setup a listner for
|
||||||
|
// don't want the opener to do a random timeout - instead they can listen for
|
||||||
|
// the ready event
|
||||||
|
// but since we need to wait for the Service Worker registration, we store the
|
||||||
|
// message for later
|
||||||
|
let messages = []
|
||||||
|
window.onmessage = evt => messages.push(evt)
|
||||||
|
|
||||||
|
let sw = null
|
||||||
|
let scope = ''
|
||||||
|
|
||||||
|
function registerWorker() {
|
||||||
|
return navigator.serviceWorker.getRegistration('./').then(swReg => {
|
||||||
|
return swReg || navigator.serviceWorker.register('sw.js', { scope: './' })
|
||||||
|
}).then(swReg => {
|
||||||
|
const swRegTmp = swReg.installing || swReg.waiting
|
||||||
|
|
||||||
|
scope = swReg.scope
|
||||||
|
|
||||||
|
return (sw = swReg.active) || new Promise(resolve => {
|
||||||
|
swRegTmp.addEventListener('statechange', fn = () => {
|
||||||
|
if (swRegTmp.state === 'activated') {
|
||||||
|
swRegTmp.removeEventListener('statechange', fn)
|
||||||
|
sw = swReg.active
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that we have the Service Worker registered we can process messages
|
||||||
|
function onMessage (event) {
|
||||||
|
let { data, ports, origin } = event
|
||||||
|
|
||||||
|
// It's important to have a messageChannel, don't want to interfere
|
||||||
|
// with other simultaneous downloads
|
||||||
|
if (!ports || !ports.length) {
|
||||||
|
throw new TypeError("[StreamSaver] You didn't send a messageChannel")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof data !== 'object') {
|
||||||
|
throw new TypeError("[StreamSaver] You didn't send a object")
|
||||||
|
}
|
||||||
|
|
||||||
|
// the default public service worker for StreamSaver is shared among others.
|
||||||
|
// so all download links needs to be prefixed to avoid any other conflict
|
||||||
|
data.origin = origin
|
||||||
|
|
||||||
|
// if we ever (in some feature versoin of streamsaver) would like to
|
||||||
|
// redirect back to the page of who initiated a http request
|
||||||
|
data.referrer = data.referrer || document.referrer || origin
|
||||||
|
|
||||||
|
// pass along version for possible backwards compatibility in sw.js
|
||||||
|
data.streamSaverVersion = new URLSearchParams(location.search).get('version')
|
||||||
|
|
||||||
|
if (data.streamSaverVersion === '1.2.0') {
|
||||||
|
console.warn('[StreamSaver] please update streamsaver')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @since v2.0.0 */
|
||||||
|
if (!data.headers) {
|
||||||
|
console.warn("[StreamSaver] pass `data.headers` that you would like to pass along to the service worker\nit should be a 2D array or a key/val object that fetch's Headers api accepts")
|
||||||
|
} else {
|
||||||
|
// test if it's correct
|
||||||
|
// should thorw a typeError if not
|
||||||
|
new Headers(data.headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @since v2.0.0 */
|
||||||
|
if (typeof data.filename === 'string') {
|
||||||
|
console.warn("[StreamSaver] You shouldn't send `data.filename` anymore. It should be included in the Content-Disposition header option")
|
||||||
|
// Do what File constructor do with fileNames
|
||||||
|
data.filename = data.filename.replace(/\//g, ':')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @since v2.0.0 */
|
||||||
|
if (data.size) {
|
||||||
|
console.warn("[StreamSaver] You shouldn't send `data.size` anymore. It should be included in the content-length header option")
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @since v2.0.0 */
|
||||||
|
if (data.readableStream) {
|
||||||
|
console.warn("[StreamSaver] You should send the readableStream in the messageChannel, not throught mitm")
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @since v2.0.0 */
|
||||||
|
if (!data.pathname) {
|
||||||
|
console.warn("[StreamSaver] Please send `data.pathname` (eg: /pictures/summer.jpg)")
|
||||||
|
data.pathname = Math.random().toString().slice(-6) + '/' + data.filename
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove all leading slashes
|
||||||
|
data.pathname = data.pathname.replace(/^\/+/g, '')
|
||||||
|
|
||||||
|
// remove protocol
|
||||||
|
let org = origin.replace(/(^\w+:|^)\/\//, '')
|
||||||
|
|
||||||
|
// set the absolute pathname to the download url.
|
||||||
|
data.url = new URL(`${scope + org}/${data.pathname}`).toString()
|
||||||
|
|
||||||
|
if (!data.url.startsWith(`${scope + org}/`)) {
|
||||||
|
throw new TypeError('[StreamSaver] bad `data.pathname`')
|
||||||
|
}
|
||||||
|
|
||||||
|
// This sends the message data as well as transferring
|
||||||
|
// messageChannel.port2 to the service worker. The service worker can
|
||||||
|
// then use the transferred port to reply via postMessage(), which
|
||||||
|
// will in turn trigger the onmessage handler on messageChannel.port1.
|
||||||
|
|
||||||
|
const transferable = data.readableStream
|
||||||
|
? [ ports[0], data.readableStream ]
|
||||||
|
: [ ports[0] ]
|
||||||
|
|
||||||
|
if (!(data.readableStream || data.transferringReadable)) {
|
||||||
|
keepAlive()
|
||||||
|
}
|
||||||
|
|
||||||
|
return sw.postMessage(data, transferable)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.opener) {
|
||||||
|
// The opener can't listen to onload event, so we need to help em out!
|
||||||
|
// (telling them that we are ready to accept postMessage's)
|
||||||
|
window.opener.postMessage('StreamSaver::loadedPopup', '*')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (navigator.serviceWorker) {
|
||||||
|
registerWorker().then(() => {
|
||||||
|
window.onmessage = onMessage
|
||||||
|
messages.forEach(window.onmessage)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// FF can ping sw with fetch from a secure hidden iframe
|
||||||
|
// shouldn't really be possible?
|
||||||
|
keepAlive()
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
134
thirdparty/sw.js
vendored
134
thirdparty/sw.js
vendored
@@ -1,4 +1,130 @@
|
|||||||
/* global self ReadableStream Response */
|
/*! streamsaver. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||||
// https://github.com/jimmywarting/StreamSaver.js/blob/master/sw.js
|
/* global self ReadableStream Response */
|
||||||
// MIT License
|
|
||||||
self.addEventListener("install",()=>{self.skipWaiting()}),self.addEventListener("activate",e=>{e.waitUntil(self.clients.claim())});const map=new Map;function createStream(e){return new ReadableStream({start(t){e.onmessage=(({data:e})=>{if("end"===e)return t.close();"abort"!==e?t.enqueue(e):t.error("Aborted the download")})},cancel(){console.log("user aborted")}})}self.onmessage=(e=>{if("ping"===e.data)return;const t=e.data,n=t.url||self.registration.scope+Math.random()+"/"+("string"==typeof t?t:t.filename),a=e.ports[0],s=new Array(3);s[1]=t,s[2]=a,e.data.readableStream?s[0]=e.data.readableStream:e.data.transferringReadable?a.onmessage=(e=>{a.onmessage=null,s[0]=e.data.readableStream}):s[0]=createStream(a),map.set(n,s),a.postMessage({download:n})}),self.onfetch=(e=>{const t=e.request.url;if(t.endsWith("/ping"))return e.respondWith(new Response("pong"));const n=map.get(t);if(!n)return null;const[a,s,o]=n;map.delete(t);const r=new Headers({"Content-Type":"application/octet-stream; charset=utf-8","Content-Security-Policy":"default-src 'none'","X-Content-Security-Policy":"default-src 'none'","X-WebKit-CSP":"default-src 'none'","X-XSS-Protection":"1; mode=block"});let i=new Headers(s.headers||{});i.has("Content-Length")&&r.set("Content-Length",i.get("Content-Length")),i.has("Content-Disposition")&&r.set("Content-Disposition",i.get("Content-Disposition")),s.size&&(console.warn("Depricated"),r.set("Content-Length",s.size));let l="string"==typeof s?s:s.filename;l&&(console.warn("Depricated"),l=encodeURIComponent(l).replace(/['()]/g,escape).replace(/\*/g,"%2A"),r.set("Content-Disposition","attachment; filename*=UTF-8''"+l)),e.respondWith(new Response(a,{headers:r})),o.postMessage({debug:"Download started"})});
|
self.addEventListener('install', () => {
|
||||||
|
self.skipWaiting()
|
||||||
|
})
|
||||||
|
|
||||||
|
self.addEventListener('activate', event => {
|
||||||
|
event.waitUntil(self.clients.claim())
|
||||||
|
})
|
||||||
|
|
||||||
|
const map = new Map()
|
||||||
|
|
||||||
|
// This should be called once per download
|
||||||
|
// Each event has a dataChannel that the data will be piped through
|
||||||
|
self.onmessage = event => {
|
||||||
|
// We send a heartbeat every x second to keep the
|
||||||
|
// service worker alive if a transferable stream is not sent
|
||||||
|
if (event.data === 'ping') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = event.data
|
||||||
|
const downloadUrl = data.url || self.registration.scope + Math.random() + '/' + (typeof data === 'string' ? data : data.filename)
|
||||||
|
const port = event.ports[0]
|
||||||
|
const metadata = new Array(3) // [stream, data, port]
|
||||||
|
|
||||||
|
metadata[1] = data
|
||||||
|
metadata[2] = port
|
||||||
|
|
||||||
|
// Note to self:
|
||||||
|
// old streamsaver v1.2.0 might still use `readableStream`...
|
||||||
|
// but v2.0.0 will always transfer the stream through MessageChannel #94
|
||||||
|
if (event.data.readableStream) {
|
||||||
|
metadata[0] = event.data.readableStream
|
||||||
|
} else if (event.data.transferringReadable) {
|
||||||
|
port.onmessage = evt => {
|
||||||
|
port.onmessage = null
|
||||||
|
metadata[0] = evt.data.readableStream
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
metadata[0] = createStream(port)
|
||||||
|
}
|
||||||
|
|
||||||
|
map.set(downloadUrl, metadata)
|
||||||
|
port.postMessage({ download: downloadUrl })
|
||||||
|
}
|
||||||
|
|
||||||
|
function createStream (port) {
|
||||||
|
// ReadableStream is only supported by chrome 52
|
||||||
|
return new ReadableStream({
|
||||||
|
start (controller) {
|
||||||
|
// When we receive data on the messageChannel, we write
|
||||||
|
port.onmessage = ({ data }) => {
|
||||||
|
if (data === 'end') {
|
||||||
|
return controller.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data === 'abort') {
|
||||||
|
controller.error('Aborted the download')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.enqueue(data)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cancel (reason) {
|
||||||
|
console.log('user aborted', reason)
|
||||||
|
port.postMessage({ abort: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
self.onfetch = event => {
|
||||||
|
const url = event.request.url
|
||||||
|
|
||||||
|
// this only works for Firefox
|
||||||
|
if (url.endsWith('/ping')) {
|
||||||
|
return event.respondWith(new Response('pong'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const hijacke = map.get(url)
|
||||||
|
|
||||||
|
if (!hijacke) return null
|
||||||
|
|
||||||
|
const [ stream, data, port ] = hijacke
|
||||||
|
|
||||||
|
map.delete(url)
|
||||||
|
|
||||||
|
// Not comfortable letting any user control all headers
|
||||||
|
// so we only copy over the length & disposition
|
||||||
|
const responseHeaders = new Headers({
|
||||||
|
'Content-Type': 'application/octet-stream; charset=utf-8',
|
||||||
|
|
||||||
|
// To be on the safe side, The link can be opened in a iframe.
|
||||||
|
// but octet-stream should stop it.
|
||||||
|
'Content-Security-Policy': "default-src 'none'",
|
||||||
|
'X-Content-Security-Policy': "default-src 'none'",
|
||||||
|
'X-WebKit-CSP': "default-src 'none'",
|
||||||
|
'X-XSS-Protection': '1; mode=block'
|
||||||
|
})
|
||||||
|
|
||||||
|
let headers = new Headers(data.headers || {})
|
||||||
|
|
||||||
|
if (headers.has('Content-Length')) {
|
||||||
|
responseHeaders.set('Content-Length', headers.get('Content-Length'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers.has('Content-Disposition')) {
|
||||||
|
responseHeaders.set('Content-Disposition', headers.get('Content-Disposition'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// data, data.filename and size should not be used anymore
|
||||||
|
if (data.size) {
|
||||||
|
console.warn('Depricated')
|
||||||
|
responseHeaders.set('Content-Length', data.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileName = typeof data === 'string' ? data : data.filename
|
||||||
|
if (fileName) {
|
||||||
|
console.warn('Depricated')
|
||||||
|
// Make filename RFC5987 compatible
|
||||||
|
fileName = encodeURIComponent(fileName).replace(/['()]/g, escape).replace(/\*/g, '%2A')
|
||||||
|
responseHeaders.set('Content-Disposition', "attachment; filename*=UTF-8''" + fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
event.respondWith(new Response(stream, { headers: responseHeaders }))
|
||||||
|
|
||||||
|
port.postMessage({ debug: 'Download started' })
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user