Skip to content

Commit

Permalink
test GH pages (#1)
Browse files Browse the repository at this point in the history
* WritableStream is not necessary

* Update README.md

* Compatibility overhaul (jimmywarting#92)

* Compatibility overhaul

* Use transferable TransformStream regardless of polyfill loaded after StreamSaver.js

* Allow setting WritableStream class and check for ponyfill

* Allow setting TransformStream class

* lock TransformStream

* updated the example.html with ponyfill

* Use iframes to download

* sw.js claim immediately

* now using isSecureCotext checking to decide if it should use popup or iframe to install sw.js

* Start download on popup for insecure pages not on Firefox

* Change ping interval delay on Firefox to 10secs

* Tunnel readableStream through MessageChannel

Main -> MS -> sw.js

closes jimmywarting#94

* Update README.md

* Update README.md

* Update README.md
  • Loading branch information
VadimZhiltsov authored May 15, 2019
1 parent 09ba7cb commit d7733e7
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 128 deletions.
58 changes: 21 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ writer.close()
### Read blob as a stream and pipe it (see: [Screw FileReader](https://www.npmjs.com/package/screw-filereader))

```javascript
require('screw-filereader')
const fileStream = streamSaver.createWriteStream('filename.txt')
require('screw-filereader') // optional in chrome v76, streams exist native on blobs now!
const blob = new Blob([ 'a'.repeat(1E9*5) ]) // 1*5 MB
const fileStream = streamSaver.createWriteStream('filename.txt', blob.size)

blob.stream().pipeTo(fileStream)
```
Expand All @@ -108,7 +108,6 @@ blob.stream().pipeTo(fileStream)

```javascript
get_user_media_stream_somehow().then(mediaStream => {
let fr = new FileReader
let mediaRecorder = new MediaRecorder(mediaStream)
let chunks = Promise.resolve()
let fileStream = streamSaver.createWriteStream('filename.mp4')
Expand All @@ -125,14 +124,10 @@ get_user_media_stream_somehow().then(mediaStream => {
, 1000)
}

mediaRecorder.ondataavailable = ({blob}) => {
chunks = chunks.then(() => new Promise(resolve => {
fr.onload = () => {
writer.write(new Uint8Array(fr.result))
resolve()
}
fr.readAsArrayBuffer(blob)
}))
mediaRecorder.ondataavailable = ({ blob }) => {
chunks = chunks.then(() => blob.arrayBuffer().then(buf =>
writer.write(new Uint8Array(buf))
))
}

})
Expand All @@ -142,29 +137,26 @@ get_user_media_stream_somehow().then(mediaStream => {

```javascript
fetch(url).then(res => {
const fileStream = streamSaver.createWriteStream('filename.txt')
const writer = fileStream.getWriter()
const fileStream = streamSaver.createWriteStream('filename.txt')

// more optimized
if (res.body.pipeTo) {
// like as we never did fileStream.getWriter()
writer.releaseLock()
return res.body.pipeTo(fileStream)
}

const reader = res.body.getReader()
const pump = () => reader.read()
.then(({ value, done }) => done
// close the stream so we stop writing
? writer.close()
// Write one chunk, then get the next one
: writer.write(value).then(pump)
)

// Start the reader
pump().then(() =>
console.log('Closed the stream, Done writing')
)
const writer = fileStream.getWriter()
const reader = res.body.getReader()
const pump = () => reader.read().then(({ value, done }) => done
// close the stream so we stop writing
? writer.close()
// Write one chunk, then get the next one
: writer.write(value).then(pump)
)

// Start the reader
pump().then(() =>
console.log('Closed the stream, Done writing')
)
})
```

Expand Down Expand Up @@ -231,12 +223,6 @@ python -m SimpleHTTPServer 3001
# then open localhost:3001/example.html
```

Consensus
=========
Go ahead and vote for how important this feature is

- [serviceWorker][17] MS Edge status: In Development
- [streams][18] Firefox Status: ASSIGNED

[1]: https://github.com/eligrey
[2]: https://github.com/eligrey/FileSaver.js
Expand All @@ -254,8 +240,6 @@ Go ahead and vote for how important this feature is
[14]: https://streams.spec.whatwg.org/#rs-class
[15]: https://www.npmjs.com/package/@mattiasbuelens/web-streams-polyfill
[16]: https://developer.microsoft.com/en-us/microsoft-edge/platform/status/fetchapi
[17]: https://developer.microsoft.com/en-us/microsoft-edge/platform/status/serviceworker
[18]: https://bugzilla.mozilla.org/show_bug.cgi?id=1128959
[19]: https://webtorrent.io
[npm-image]: https://img.shields.io/npm/v/streamsaver.svg?style=flat-square
[npm-url]: https://www.npmjs.com/package/streamsaver
166 changes: 111 additions & 55 deletions StreamSaver.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* global location WritableStream ReadableStream define MouseEvent MessageChannel TransformStream */
/* global chrome location ReadableStream define MessageChannel TransformStream */

;((name, definition) => {
typeof module !== 'undefined'
? module.exports = definition()
Expand All @@ -8,40 +9,96 @@
})('streamSaver', () => {
'use strict'

const secure = location.protocol === 'https:' ||
location.protocol === 'chrome-extension:' ||
location.hostname === 'localhost'
let iframe
let loaded
let transfarableSupport = false
let streamSaver = {
let iframe, background
const test = fn => { try { fn() } catch (e) {} }
const ponyfill = window.WebStreamsPolyfill || {}
const once = { once: true }
const firefox = 'MozAppearance' in document.documentElement.style
const mozExtension = location.protocol === 'moz-extension:'
const streamSaver = {
createWriteStream,
WritableStream: window.WritableStream || ponyfill.WritableStream,
supported: false,
version: {
full: '1.2.0',
major: 1,
minor: 2,
dot: 0
}
version: { full: '1.2.0', major: 1, minor: 2, dot: 0 },
mitm: 'https://jimmywarting.github.io/StreamSaver.js/mitm.html?version=1.2.0',
ping: 'https://jimmywarting.github.io/StreamSaver.js/ping.html?version=1.2.0'
}

function makeIframe (src) {
const iframe = document.createElement('iframe')
iframe.hidden = true
iframe.src = src
iframe.addEventListener('load', () => {
iframe.loaded = true
}, once)
document.body.appendChild(iframe)
return iframe
}

streamSaver.mitm = 'https://jimmywarting.github.io/StreamSaver.js/mitm.html?version=' +
streamSaver.version.full
test(() => {
background = chrome.extension.getBackgroundPage() === window
})

try {
test(() => {
// Some browser has it but ain't allowed to construct a stream yet
streamSaver.supported = 'serviceWorker' in navigator && !!new ReadableStream()
} catch (err) {}
})

try {
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()
transfarableSupport = readable.locked === true
} catch (err) {
// Was first enabled in chrome v73
// Freeze TransformStream object (can only work with native)
Object.defineProperty(streamSaver, 'TransformStream', {
configurable: false,
writable: false,
value: TransformStream
})
})

const isSecureContext = window.isSecureContext && (!firefox || !background)

function iframePostMessage (url, args) {
iframe = iframe || makeIframe(url)
if (iframe.loaded) {
iframe.contentWindow.postMessage(...args)
} else {
iframe.addEventListener('load', () => {
iframe.contentWindow.postMessage(...args)
}, once)
}
}

function load (url, noTabs, popUp) {
let popup = { close: () => (popup.closed = 1), fns: [], onLoad: fn => popup.fns.push(fn) }
if (!noTabs && window.chrome && chrome.tabs && chrome.tabs.create) {
chrome.tabs.create({ url: url, active: false }, popup2 => {
popup.close = () => chrome.tabs.remove(popup2.id)

if (popup.closed) {
popup.close()
} else {
let fn
chrome.tabs.onUpdated.addListener(fn = (tabId, _, tab) => {
if (tabId === popup2.id && tab.status === 'complete') {
chrome.tabs.onUpdated.removeListener(fn)
popup.onLoad = fn => fn()
popup.fns.forEach(popup.onLoad)
}
})
}
})
} else {
if (popUp || !firefox && !isSecureContext) {
popup = window.open(url, Math.random())
} else {
popup.close = (x => () => x.remove())(makeIframe(url))
}
}
return popup
}

function createWriteStream (filename, queuingStrategy, size) {
Expand All @@ -52,62 +109,56 @@

let channel = new MessageChannel()
let popup
let hash = ''
let setupChannel = readableStream => new Promise(resolve => {
const args = [ { filename, size }, '*', [ channel.port2 ] ]
const transferable = [ channel.port2 ]
const request = { filename, size }
const args = [ request, '*', transferable ]

// Pass along transfarable stream
if (readableStream) {
args[0].readableStream = readableStream
args[2].push(readableStream)
channel.port1.postMessage({ readableStream }, [ readableStream ])
request.transferringReadable = true
}

channel.port1.onmessage = evt => {
// Service worker sent us a link from where
// we recive the readable link (stream)
if (evt.data.download) {
resolve() // Signal that the writestream are ready to recive data
if (!secure) popup.close() // don't need the popup any longer
if (window.chrome && chrome.extension &&
chrome.extension.getBackgroundPage &&
chrome.extension.getBackgroundPage() === window) {
chrome.tabs.create({ url: evt.data.download, active: false })
} else {
window.location = evt.data.download
if (popup) {
if (!hash && !iframe && firefox) {
iframePostMessage(streamSaver.ping, [evt.data, '*'])
}
popup.close() // don't need the popup any longer
}
popup = load(evt.data.download, isSecureContext)

// Cleanup
if (readableStream) {
// We don't need postMessages now when stream are transferable
channel.port1.onmessage = null
channel.port1.close()
channel.port2.close()
}
} else {
if (popup) {
if (firefox) popup.close()
popup = null
}

channel.port1.onmessage = null
}
}

if (secure && !iframe) {
iframe = document.createElement('iframe')
iframe.src = streamSaver.mitm
iframe.hidden = true
document.body.appendChild(iframe)
if (isSecureContext) {
return iframePostMessage(streamSaver.mitm, args)
}

if (secure && !loaded) {
let fn
iframe.addEventListener('load', fn = () => {
loaded = true
iframe.removeEventListener('load', fn)
iframe.contentWindow.postMessage(...args)
})
if (!hash && mozExtension && !streamSaver.transformStream) {
hash = '#' + Math.random()
}

if (secure && loaded) {
iframe.contentWindow.postMessage(...args)
}

if (!secure) {
popup = window.open(streamSaver.mitm, Math.random())
popup = load(streamSaver.mitm + hash, !hash, true)
if (popup.postMessage) {
let onready = evt => {
if (evt.source === popup) {
popup.postMessage(...args)
Expand All @@ -119,11 +170,16 @@
// so popup.onload() don't work but postMessage still dose
// work cross origin
window.addEventListener('message', onready)
} else {
popup.onLoad(() => {
args[0].hash = hash
iframePostMessage(streamSaver.ping, args)
})
}
})

if (transfarableSupport) {
const ts = new TransformStream({
if (streamSaver.TransformStream) {
const ts = new streamSaver.TransformStream({
start () {
return new Promise(resolve =>
setTimeout(() => setupChannel(ts.readable).then(resolve))
Expand All @@ -134,7 +190,7 @@
return ts.writable
}

return new WritableStream({
return new streamSaver.WritableStream({
start () {
// is called immediately, and should perform any actions
// necessary to acquire access to the underlying sink.
Expand Down
14 changes: 7 additions & 7 deletions example.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ <h3>What would you like to save?</h3>
</div>
<button id="$close" hidden title="When you are done writing">Close</button>
<button id="$abort" hidden title="When you want to abort">Abort</button>
<script src="StreamSaver.js"></script>
<script src="https://cdn.jsdelivr.net/webtorrent/latest/webtorrent.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/web-streams-polyfill@1.3.2/dist/polyfill.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/web-streams-polyfill@2.0.2/dist/ponyfill.min.js"></script>
<script src="https://rawgit.com/jimmywarting/browser-su/master/build/permissions.js"></script>
<script src="StreamSaver.js"></script>

<script>
!streamSaver.supported && prompt(
Expand Down Expand Up @@ -73,9 +73,9 @@ <h3>What would you like to save?</h3>
permission,
writer,
url = "https://d8d913s460fub.cloudfront.net/videoserver/cat-test-video-320x240.mp4",
fileStream = streamSaver.createWriteStream(filename)
fileStream = kind !== 'torrent' && streamSaver.createWriteStream(filename)

writer = fileStream.getWriter()
writer = kind !== 'torrent' && fileStream.getWriter()
$abort.onclick = event => writer.abort()
$close.onclick = event => writer.close()

Expand Down Expand Up @@ -170,8 +170,8 @@ <h3>What would you like to save?</h3>
})

const file = torrent.files[5]
let myFile = streamSaver.createWriteStream(file.name, file.length)
let writer = myFile.getWriter()
fileStream = streamSaver.createWriteStream(file.name, file.length)
writer = fileStream.getWriter()

file.createReadStream()
.on('data', data => writer.write(data))
Expand All @@ -192,7 +192,7 @@ <h3>What would you like to save?</h3>
mediaRecorder.stop()
setTimeout(()=>{
chunks.then(evt => {
myFile.close()
fileStream.close()
})
}, 1000)
}
Expand Down
Loading

0 comments on commit d7733e7

Please sign in to comment.