import * as fileSaver from "file-saver" ; const scope = "/download-stream-service-worker/" ; let serviceWorker = null ; let serviceWorkerPort = null ; export let serviceWorkerPinger = null ; const callbacks = Object . create ( null ) ; let callbackNo = 0 ; const stoppers = Object . create ( null ) ; async function swPostMessage ( msg ) { const no = callbackNo ++ ; msg . i = no ; serviceWorkerPort . postMessage ( msg ) ; return await new Promise ( ( res , rej ) => { let timeout = setTimeout ( ( ) => rej ( new Error ( "Timeout" ) ) , 5000 ) ; callbacks [ no ] = x => { clearTimeout ( timeout ) ; res ( x ) ; } ; } ) ; } param opts export async function load ( opts = { } ) { const prefix = opts . prefix || "" ; try { if ( navigator . serviceWorker && ( navigator . userAgent . indexOf ( "Safari" ) < 0 || navigator . userAgent . indexOf ( "Chrome" ) >= 0 ) ) { let swr = await navigator . serviceWorker . getRegistration ( scope ) ; if ( ! swr || ! swr . active ) { swr = await navigator . serviceWorker . register ( prefix + "sw.js?v=5" , { scope } ) ; if ( ! swr . installing && ! swr . waiting && ! swr . active ) { await new Promise < void > ( ( res , rej ) => { let timeout = setTimeout ( ( ) => rej ( new Error ( "Timeout" ) ) , 5000 ) ; swr . onupdatefound = ( ) => { clearTimeout ( timeout ) ; res ( ) ; } ; } ) ; } serviceWorker = swr . installing || swr . waiting || swr . active ; console . log ( "Waiting for service worker to activate..." ) ; await new Promise < void > ( async ( res , rej ) => { let timeout = setTimeout ( ( ) => rej ( new Error ( "Timeout" ) ) , 5000 ) ; while ( serviceWorker . state !== "activated" ) { await new Promise ( res => { serviceWorker . addEventListener ( "statechange" , res , { once : true } ) ; } ) ; } clearTimeout ( timeout ) ; res ( ) ; } ) ; console . log ( "Service worker active, reloading" ) ; location . reload ( ) ; await new Promise ( ( ) => { } ) ; } serviceWorker = swr . active ; const mc = new MessageChannel ( ) ; serviceWorker . postMessage ( { c : "port" , p : mc . port2 } , [ mc . port2 ] ) ; serviceWorkerPort = mc . port1 ; serviceWorkerPort . onmessage = ev => { const msg = ev . data ; const callback = callbacks [ msg . i ] ; if ( callback ) { delete callbacks [ msg . i ] ; callback ( msg ) ; } } ; const ack = await swPostMessage ( { c : "setup" } ) ; if ( ack . v !== 5 ) { console . log ( "Service worker out of date, unregistering..." ) ; await swr . unregister ( ) ; console . log ( "Reloading" ) ; location . reload ( ) ; await new Promise ( ( ) => { } ) ; } const pinger = serviceWorkerPinger = document . createElement ( "iframe" ) ; pinger . style . display = "none" ; pinger . src = scope + "download-stream-service-worker-pinger.html" ; document . body . appendChild ( pinger ) ; } } catch ( ex ) { console . log ( "WARNING: Not using service workers! " + ex ) ; serviceWorker = serviceWorkerPort = null ; } } /**
 * Attempt to stream this.
 */ export async function stream ( name , body , headers ) { const utf8Name = encodeURIComponent ( name ) ; const safeName = utf8Name . replace ( /%/g , "_" ) ; headers = Object . assign ( { "content-disposition" : `attachment; filename="${ safeName }"; filename*=UTF-8''${ utf8Name }` , "cross-origin-embedder-policy" : "require-corp" } , headers ) ; if ( serviceWorker ) { const url = scope + Math . random ( ) + Math . random ( ) + Math . random ( ) + "/" + safeName ; let worked = await Promise . race ( [ swPostMessage ( { c : "stream" , u : url , h : headers } ) . then ( ( ) => true ) , ( new Promise ( res => setTimeout ( res , 5000 ) ) ) . then ( ( ) => false ) ] ) ; if ( ! worked ) console . log ( "WARNING: Failed to communicate with service worker!" ) ; if ( worked ) { const iframe = document . createElement ( "iframe" ) ; iframe . src = url ; iframe . style . display = "none" ; document . body . appendChild ( iframe ) ; worked = await Promise . race ( [ swPostMessage ( { c : "wait-start" , u : url } ) . then ( ( ) => true ) , ( new Promise ( res => setTimeout ( res , 5000 ) ) ) . then ( ( ) => false ) ] ) ; if ( ! worked ) console . log ( "WARNING: Failed to start service worker download!" ) ; } if ( worked ) { return await streamViaWorker ( url , body ) ; } } return await streamViaBlob ( name , body ) ; } /**
 * Stream this data via the service worker.
 */ async function streamViaWorker ( url , body ) { const rdr = body . getReader ( ) ; stoppers [ url ] = ( ) => swPostMessage ( { c : "end" , u : url } ) ; const keepalive = setInterval ( ( ) => { swPostMessage ( { c : "keepalive" , u : url } ) ; } , 15000 ) ; while ( true ) { const d = await rdr . read ( ) ; if ( d . done ) { delete stoppers [ url ] ; await swPostMessage ( { c : "end" , u : url } ) ; break ; } await swPostMessage ( { c : "data" , u : url , b : d . value } ) ; } clearInterval ( keepalive ) ; } /**
 * Stream this data via a blob.
 */ async function streamViaBlob ( name , body ) { console . log ( "WARNING: Saving data to a blob to download!" ) ; const data = [ ] ; const rdr = body . getReader ( ) ; while ( true ) { const part = await rdr . read ( ) ; if ( part . done ) break ; data . push ( part . value ) ; } const blob = new Blob ( data ) ; fileSaver . saveAs ( blob , name ) ; } window . addEventListener ( "beforeunload" , ( ) => { for ( const url in stoppers ) stoppers [ url ] ( ) ; } ) ;