Webtorrent App allows you to launch web apps(SPAs) from webtorrents.
- It allows you to build insanely huge apps. Think gigabytes.
- It decreases the server load, since your users will be downloading the files from other users, not from your server.
- It's decentralized and, in fact, webtorrent apps can work with no server at all.
Webtorrentapp will attempt to download and launch the torrent you provided, if it fails, it will fetch your application's files via XHR and start seeding. Also, since connecting to the torrent and peers can last for a couple of seconds, it provides cache in order to launch your app instantly. It also provides the necessary level of abstraction so that you can ignore whether your app was loaded from a torrent, XHR or cache.
Get webtorrentapp
npm install webtorrentapp
Create two files:
- dev.html
- client.html
Initialize both with the following code:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="../webtorrentapp.js"></script>
<script>
WebtorrentApp({
})
</script>
</head>
<body>
</body>
</html>
If you wish, you can add a fancy preloader to client.html:
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css"/>
and
<div class="container">
<br/>
<div class="jumbotron">
<h1>My first webtorrent app</h1>
<div class="progress">
<div class="progress-bar progress-bar-striped active" role="progressbar"
aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style="width:100%">
Loading...
</div>
</div>
</div>
</div>
Now modify client.html so that WebtorrentApp call looks like this:
WebtorrentApp({
path: 'app/',
restoreFromCache: false //you really don't want cache getting in your way while developing
})
Create a subfolder called app, and inside it create a file called index.js. That'll be the entry point of your app. index.js must export a function, that'll take one argument, and let our app just say "Hello, world!" for now, so let's add this to index.js:
module.exports = function(wtapi){
alert("Hello, world!")
}
Now open dev.html in a browser, keep in mind that you have to access it via localhost, otherwise it won't be able to fetch the files in order to start seeding. Webtorrentapp uses debug for logging, so open up the console and run this:
localStorage.debug = "webtorrentapp"
because the log will output the torrent data that'll you'll need on the client. Refresh the page and you should see an output like this:
webtorrentapp Successfully started seeding. Infohash: c56794f957feff43fee1a24c274dc856a06d6fa7 Magnet: magnet:?xt=urn:btih:c56794f957feff43fee1a24c274dc856a06d6fa7&dn=Just+another+WebTorrent+app&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=udp%3A%2F%2Fopen.demonii.com%3A1337&tr=udp%3A%2F%2Ftracker.webtorrent.io%3A80&tr=wss%3A%2F%2Ftracker.webtorrent.io +33ms
The infohash is the unique identifier of your app, it will be different everytime the checksum of your app changes, so basically, after any modification, no matter how small.
Copy the infohash, we'll need it in a few seconds. Just to verify that your app is seeding, go to Instant.io, preferably on another computer or device, and try downloading your app by the infohash. If everything went well, open up client.html and modify it like this:
WebtorrentApp({
torrent: YOUR_TORRENTS_INFOHASH_OR_MAGNET
cache: ['index.js']
})
Now if you open client.html in a browser and check the console you should see that the launcher successfully connected to the torrent and launched your app from it. You should be able to copy just client.html and webtorrentapp.js to another computer or device and still see the "Hello, world!" message, if not, something went wrong.
Now that we're done with helloworlds, let's move on to cooler stuff. Create a new fle app/template.html and add some markup to it, for example
<h1>Why hello there, world!</h1>
In dev.html, add this line
files: ['template.html']
to the Webtorrentapp call, so it looks something like this:
WebtorrentApp({
path: 'app/',
restoreFromCache: false,
files: ['template.html']
})
Optionally, if you'd like, you can add this setting to client.html:
cache: ['template.html']
Now we need to modify the index.js file, we'll have it read the template.html from the torrent and give us its contents, like this:
module.exports = function(wtapi){
wtapi.requestFile("template.html").then(function(template){
document.body.innerHTML = template;
})
}
Refresh dev.html, copy the new infohash and the replace the old infohash in client.html, now, refresh the client.html tab in the browser, you should see the "Hello, world!" alert, and that's because it's been cached, so refresh the tab one more time, and this time you should see your template rendered.
requestFile is a function that you'll be using a lot, if you console.log its result you'll see that it actually returns to you a Node Buffer, to be more specific, a browser version of it, but it can be casted into a string, which is exactly what happenned here.
Besides requestFile there are functions that fetch from the torrent styles, scripts, and node modules, but they are quite similar to the requestFile functions, so I'd rather not bore with them, you can find them in the API section, instead, let's display an image from the torrent!
To do that, add some image to the app/ subfolder, suppose it's called image.jpg. Now in client.html add it to the file list:
files: ['template.html', 'image.jpg']
in template.html add this:
<img id="the-image"/>
and in index.js this:
wtapi.requestBlobUrl("image.jpg").then(function(url){
document.getElementById("the-image").src = url;
});
Refresh dev.html in the browser, copy the infohash, replace the old infohash in client.html and, after two refreshes, you should see your image. This uses the blob URL functionality, inspect the element to see what I'm taking about, these are url that "point" to "files" that exist within browser's memory. You can use them almost anywhere, for example, all of this will work:
<link rel="stylesheet" type="text/css" href="blob:http%3A//localhost%3A63342/1c6467aa-f183-4f09-a146-ff5e52b7267a"/>
<script src="blob:http%3A//localhost%3A63342/1c6467aa-f183-4f09-a146-ff5e52b7267a"></script>
<img src="blob:http%3A//localhost%3A63342/1c6467aa-f183-4f09-a146-ff5e52b7267a"/>
<video src="blob:http%3A//localhost%3A63342/1c6467aa-f183-4f09-a146-ff5e52b7267a"/>
<audio src="blob:http%3A//localhost%3A63342/1c6467aa-f183-4f09-a146-ff5e52b7267a"/>
Speaking of <audio>, let's make our app play some music! As you've probably figured out, in order to do this, we'd add song.mp3 to the app/ folder, and to the files list in dev.html, and in index.js we could write something like this:
wtapi.requestBlobUrl("song.url").then(function(url){
var audio = document.createElement("audio");
audio.controls = true;
audio.src = url;
document.body.appendChild(audio);
})
However, once you've updated the infohash and refreshed client.html, you might've noticed that there's a considerable delay between the loading of the app and the time when the audio actually starts playing, that's because webtorrent needs to download the entire file in order to generate its blob, but we can avoid it with streaming! Add this to index.js:
wtapi.requestStream("song.url").then(function(stream){
var audio = document.createElement("audio");
audio.controls = true;
audio.autoplay = true;
document.body.appendChild(audio);
stream.pipe(audio);
});
the requestStream function returns an instance of a subclass of Node's stream.Readable and you can use it to stream any types of files. This subclass also implements a pipe functions that allows you to stream not only to other streams, but to <audio> and <video> tags, using the MediaSource API. Keep in mind that this API is not yet fully supported, for example, it won't work in Firefox, and I had a really hard time looking for a webm file that Chrome would actually accept to stream, so I'd advise you foresee a blob url fallback for piping into <audio> and <video>, because blobs work everytime everywhere.
Check the /example/ folder of this project. You should open the index.html files via a local server, not file:///, otherwise it won't be able to fetch the files for seeding in case it finds no peers.
WebtorrentApp({
torrent: 'c56794f957feff43fee1a24c274dc856a06d6fa7',//the infohash, magnet or a URL of a .torrent file. If missing, will start seeding without timeout
files: ['something.js', 'something.mp3'], //a list of your app's files, needed when seeding
cache: ['something.js'],//the subset of files to cache in the browser storage. Default: none
path: 'app/',//a URL, relative of absolute path to the location of the app files, needed for seeding
seedTimeout: 5000,//how long to wait for the torrent to start downloading before giving up and starting seeding. Default: 5000 ms
name: 'Just another WebTorrent app',//the name of your app. Default: "Just another WebTorrent app"
restoreFromCache: true//whether to restore the app from inbrowser cache. Default: true
})
Returns a promise that will resolve with a Buffer containing the binary data of the file. The buffer can be casted to string, if needed.
Returns a promise that will resolve with the file's blob URL
Returns a Node stream.Readable with a special pipe() method that allows piping not only to another streams but also to <audio> and <video> tags via MediaSource
Utility method. Injects a <link rel="stylesheet"/> tag with the provided URL
Utility method. Injects a <script> tag with the provided URL
Reads the file from the torrent and injects a <link rel="stylesheet"/> tag with its blob URL. Returns a promise that resolves when the style is injected.
Reads the file from the torrent and injects a <script> tag with its blob URL. Returns a promise that resolves when the script is injected.
Reads the file from the torrent and evals it in global context. Returns a promise which resolves with the values of module's module.exports
If you think this software is worth a buck or two, I'd be gald to have it.
Please support the caffeine addiction of your humble servant by donating to my PayPal [email protected]