Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

data-attribute DOM API with client.render() to activate #1421

Open
wants to merge 3 commits into
base: master
from
Open
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -15,20 +15,18 @@ npm install webtorrent

## Quick Example

```js
```html
<video autoplay controls
data-torrent-src="magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10&dn=Sintel&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fsintel.torrent"
data-torrent-path="Sintel.mp4"></video>
<script src="https://cdn.jsdelivr.net/webtorrent/latest/webtorrent.min.js"></script>
<script>
var client = new WebTorrent()
var torrentId = 'magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10&dn=Sintel&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fsintel.torrent'
client.add(torrentId, function (torrent) {
// Torrents can contain many files. Let's use the .mp4 file
var file = torrent.files.find(function (file) {
return file.name.endsWith('.mp4')
})
// Display the file by adding it to the DOM. Supports video, audio, image, etc. files
file.appendTo('body')
client.render(function (err) {
throw err
})
</script>
```

# WebTorrent API
@@ -150,6 +148,33 @@ buf.name = 'Some file name'
client.seed(buf, cb)
```

## `client.render([opts], function onrendered (err) {})`

Render files into the DOM using data attributes.

```html
<img data-torrent-src="torrentId" /> <!-- The torrentId that contains the file to render (see client.add()) -->
<img data-torrent-src="torrentId" data-torrent-path="path/to/file" /> <!-- The path to the desired file in a multi file torrent -->
<img data-torrent-src="torrentId" data-torrent-fallback="http://example.com/img.png"/> <!-- A normal src URL to fall back to in case of errors -->
```

The torrentId passed to `[data-torrent-src]` will use an existing matching torrent with client.get(), or it will create a new one with client.add(). If there's an existing matching torrent, then it won't be modified by a new magnet link, so no new webseeds, trackers, or other options will be added.

If `opts` is specified, then the default options (shown below) will be overridden.

```js
{
elements: [Node], // An array of DOM nodes to render to (default=document.querySelectorAll('[data-torrent-src]'))
timeout: Number // Number of milliseconds to wait for downloads to initiate before using fallback url (default=5000ms)
}
```

If the `onrendered` callback is specified, then it will be called after all the DOM nodes are successfully rendered to.

If no path is specified via `[data-torrent-path]`, then `torrent.files[0]` will be used.

The path in `[data-torrent-path]` is relative to the torrent's root folder, unlike `file.path` which includes the root directory.

## `client.on('torrent', function (torrent) {})`

Emitted when a torrent is ready to be used (i.e. metadata is available and store is
@@ -224,18 +224,18 @@ script on your page. If you use [browserify](http://browserify.org/), you can

It's easy to download a torrent and add it to the page.

```js
```html
<video autoplay controls
data-torrent-src="magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10&dn=Sintel&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fsintel.torrent"
data-torrent-path="Sintel.mp4"></video>
<script src="https://cdn.jsdelivr.net/webtorrent/latest/webtorrent.min.js"></script>
<script>
var client = new WebTorrent()
var torrentId = 'magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10&dn=Sintel&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fsintel.torrent'
client.add(torrentId, function (torrent) {
// Torrents can contain many files. Let's use the .mp4 file
var file = torrent.files.find(function (file) {
return file.name.endsWith('.mp4')
})
file.appendTo('body') // append the file to the DOM
client.render(function (err) {
throw err
})
</script>
```

This supports video, audio, images, PDFs, Markdown, [and more][render-media], right
@@ -441,6 +441,90 @@ WebTorrent.prototype._destroy = function (err, cb) {
self.dht = null
}

/**
* Render files from a torrent into DOM nodes with custom attributes:
* [data-torrent-src] TorrentId to render
* [data-torrent-path] path to the file to render in a multi file torrent
* [data-torrent-fallback] fallback url for src
* @param {Object} opts
* @param {function} cb
*/
WebTorrent.prototype.render = function (opts, cb) {
var self = this
if (typeof opts === 'function') return self.render(null, opts)

if (!opts) opts = {}

var nodes = opts.elements || document.querySelectorAll('[data-torrent-src]')

if (nodes.length === 0) return cb()

var numRendered = 0

nodes.forEach(function (elem) {
var torrentId = elem.getAttribute('data-torrent-src')

var torrent = self.get(torrentId) || self.add(torrentId)

// don't pass error to fallback() because the cb() might have already been called before the torrent error
torrent.once('error', function () { fallback() })

// if nothing has been downloaded after timeout length, use fallback
setTimeout(function () {
if (torrent.downloaded === 0) {
fallback(new Error('Torrent with infohash: ' + torrent.infoHash + ' timed out. Using fallback url.'))
}
}, opts.timeout || 5000)

// ensure metadata is available before using files in renderFile()
if (torrent.metadata) {
renderFile()
} else {
torrent.once('metadata', function () { renderFile() })
}

function renderFile () {
var filePath = elem.getAttribute('data-torrent-path')

var fileToRender = torrent.files.find(function (file) {
if (!filePath) return true // if no path is specified, render the first file

var rootDir = /\//.test(file.path) ? (torrent.name + '/') : ''

// remove initial / if present
filePath = filePath.replace(/^\//, '')

return file.path === rootDir + filePath
})

if (!fileToRender) {
return fallback(new Error('No file found matching this path: ' + filePath + ' in torrent with infohash: ' + torrent.infoHash))
}

// some errors from render-media are thrown, and some are passed to the callback
try {
fileToRender.renderTo(elem, {
autoplay: elem.hasAttribute('autoplay'),
controls: elem.hasAttribute('controls')
}, function (err) {
if (err) return fallback(err)

if (++numRendered === nodes.length) cb()
})
} catch (err) {
fallback(err)
}
}

function fallback (error) {
var fallbackSrc = elem.getAttribute('data-torrent-fallback')
if (fallbackSrc) elem.setAttribute('src', fallbackSrc)

if (error) cb(error)
}
})
}

WebTorrent.prototype._onListening = function () {
this._debug('listening')
this.listening = true
@@ -96,6 +96,150 @@ if (!(global && global.process && global.process.versions && global.process.vers
})
})
})

test('client.render() without argument', function (t) {
t.plan(11)

var client = new WebTorrent({ dht: false, tracker: false })

client.on('error', function (err) { t.fail(err) })
client.on('warning', function (err) { t.fail(err) })

client.seed(img, function (torrent) {
var tag = document.createElement('img')
tag.setAttribute('data-torrent-src', torrent.infoHash)
document.body.appendChild(tag)

var tag2 = document.createElement('img')
tag2.setAttribute('data-torrent-src', torrent.infoHash)
document.body.appendChild(tag2)

client.render(function (err) {
verifyImage(t, err, tag)
verifyImage(t, err, tag2)
client.destroy(function (err) {
t.error(err, 'client destroyed')
})
})
})
})

test('client.render() with element argument', function (t) {
t.plan(7)

var client = new WebTorrent({ dht: false, tracker: false })

client.on('error', function (err) { t.fail(err) })
client.on('warning', function (err) { t.fail(err) })

client.seed(img, function (torrent) {
var elemToRender = document.createElement('img')
elemToRender.setAttribute('data-torrent-src', torrent.infoHash)
document.body.appendChild(elemToRender)

var elem2 = document.createElement('img')
elem2.setAttribute('data-torrent-src', torrent.infoHash)
document.body.appendChild(elem2)

client.render({elements: [elemToRender]}, function (err) {
verifyImage(t, err, elemToRender)
t.false(elem2.hasAttribute('src'), 'unspecified element should not be altered')
elem2.remove()
client.destroy(function (err) {
t.error(err, 'client destroyed')
})
})
})
})

test('client.render() with path', function (t) {
t.plan(6)

var client = new WebTorrent({ dht: false, tracker: false })

client.on('error', function (err) { t.fail(err) })
client.on('warning', function (err) { t.fail(err) })

var text = Buffer.from('text')
text.name = 'text.txt'

client.seed([text, img], function (torrent) {
var imgElem = document.createElement('img')
imgElem.setAttribute('data-torrent-src', torrent.infoHash)
imgElem.setAttribute('data-torrent-path', 'img.png')
document.body.appendChild(imgElem)

var textElem = document.createElement('iframe')
textElem.setAttribute('data-torrent-src', torrent.infoHash)
textElem.setAttribute('data-torrent-path', 'text.txt')
document.body.appendChild(textElem)

client.render(function (err) {
// error will be returned if either is rendered to the wrong element
verifyImage(t, err, imgElem)
textElem.remove()
client.destroy(function (err) {
t.error(err, 'client destroyed')
})
})
})
})

test('client.render() fallback on render error', function (t) {
t.plan(3)

var client = new WebTorrent({ dht: false, tracker: false })

client.on('error', function (err) { t.fail(err) })
client.on('warning', function (err) { t.fail(err) })

client.seed(img, function (torrent) {
// use wrong element type to cause error in renderTo()
var elem = document.createElement('video')
elem.setAttribute('data-torrent-src', torrent.infoHash)
elem.setAttribute('data-torrent-fallback', 'fake url')
document.body.appendChild(elem)

client.render(function (err) {
t.ok(err)
t.equals(elem.getAttribute('src'), 'fake url')
elem.remove()
client.destroy(function (err) {
t.error(err, 'client destroyed')
})
})
})
})

test('client.render() fallback on torrent error', function (t) {
t.plan(3)

var client = new WebTorrent({ dht: false, tracker: false })

client.on('error', function (err) { t.fail(err) })
client.on('warning', function (err) { t.fail(err) })

client.seed(img, function (torrent) {
var elem = document.createElement('img')
elem.setAttribute('data-torrent-src', torrent.infoHash)
elem.setAttribute('data-torrent-fallback', 'fake url')
document.body.appendChild(elem)

client.render(function (err) {
t.error(err)
process.nextTick(function () {
torrent.emit('error')
process.nextTick(function () {
t.equals(elem.getAttribute('src'), 'fake url')
elem.remove()
client.destroy(function (err) {
t.error(err, 'client destroyed')
})
})
})
})
})
})
}

test('WebTorrent.WEBRTC_SUPPORT', function (t) {
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.