Repository: wmhilton/download-with-webtorrent-button Branch: master Commit: e1c9b0a3e4d5 Files: 13 Total size: 21.8 KB Directory structure: gitextract_nyt5xsd5/ ├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── dist/ │ ├── index.css │ └── index.js ├── generate_gif.js ├── index.css ├── index.html ├── index.js ├── package.json ├── postcss.config.js └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ node_modules screenshots ================================================ FILE: .travis.yml ================================================ language: node_js node_js: 6 deploy: provider: npm email: wmhilton@gmail.com on: skip_cleanup: true tags: true branch: master repo: wmhilton/download-with-webtorrent-button api_key: secure: hlb8qQQlurY9WGoyU7le3HSrxwZil6GJp2N8ZPlrJQS1fawbg5uwLxI2JkdydCEAIe3/SVD2JEKQTa2toq20z/w2cWYEGectKJxMUGzM4VOK9UgSsBM0CKTa7N3nXoZbxZRjOjDcdHtPV6q5HBy3VKM3RIcJsOst8DbQHFI9cfhrOLBPm+Y9Ke+dE7mij+P1ys+tnLRLzEBEC9Ea7fn85/+4IAmhU7tZp/lyu1doPlFSNa7bkscB8DCB82DWkY9Vw4fRJYpHPC2861RsYlXoVdcUhv6EdCCKFtQIJQsxoToCOVtCV5tHRRlMK3rmv9r1I/cPjYVkdcfRmgO+P/3IlT+5GDiYGaBvq4Og3vdyMcPhf7poQhIJDysCTLo+BpODP3zUeCprE7dqMrkTHrGlL46gSGBmRYIJkhxtC0oMLug5QEwep510jHSdidutQVoX5w5dGdy70jKyNBYJrTOOuaQ52QNWPBGy1LniEBGD50Dv/Qk71lhElhMqDpsj15JPIlwy4hE0aLQfrNE1bp3+YjspUkbSARcwBuyoFr90+lMZKDJJtYH9Kms8TQmcbycmPkwacAJj6e8/3rD5njgVwDfm/8c5fGy+qilz5tYGIrDJwaURE1MRRAP+NZc6srWglOFsGns05r09F0eSNJ19qVxY+s7LRA+7TYTeltiWpss= ================================================ FILE: LICENSE.md ================================================ This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to ================================================ FILE: README.md ================================================ # download-with-webtorrent-button Transform ordinary download links into super-powered WebTorrent ones! ## Example Check out the demo page: https://wmhilton.github.io/download-with-webtorrent-button [![screenshot of WebTorrent button](https://github.com/wmhilton/download-with-webtorrent-button/blob/master/dist/animated.gif?raw=true)](https://wmhilton.github.io/download-with-webtorrent-button) Sponsor ## Rationale Do you have a big, popular file that lots of people download and the Internet would grind to a halt if your website went down? It is EASY to spread the burden of hosting a file among all the people who are downloading it. Bittorrent (despite its association with piracy) is a fabulous network protocol that does exactly that. It use to be that you needed to install separate software to use Bittorrent, but with the advent of [WebTorrent](https://webtorrent.io) it is now possible to use the Bittorrent protocol seamlessly in the browser without visitors ever having to leave your site! Despite how FREAKING AWESOME WebTorrent is, not enough sites are taking advantage of it. To make taking advantage of WebTorrent as easy and accessible as possible, I decided to make a "Download with WebTorrent" button that turns your ordinary download link into a super-powered WebTorrent download link! All you have to do is paste a small code snippet in your HTML. ## Installation using a CDN Add the following stylesheet to ``: ```html ``` And the following scripts to the bottom of ``: ```html ``` This adds a single function `registerWebtorrentLinks()` to the global scope. It automatically initializes `a` tags. If you add additional `a` tags after the initial page load (such as in the case of single page apps) you can rerun registerWebtorrentLinks(). If you want to override the CSS styles, take a look at `index.css`. ## Installation using a module bundler? Somebody should fork this and make it a React component. Pull requests welcome! ## Usage ### The easiest way To add a **Download with WebTorrent** button to your page, use a regular `` link. The link's `href` attribute will be provided as a fallback on browsers that can't run WebTorrent, or if an error occurs. Then add a `data-webtorrent` attribute. You can use `data-webtorrent="auto"` and these [fabulous](https://github.com/wmhilton/webtorrentify-link) [free](https://github.com/wmhilton/webtorrentify-server) [services](https://github.com/wmhilton/cors-buster) will auto-generate a WebTorrent-compatible .torrent file for your link. ```html Link Text ``` ### Bring your own torrent If you already have a magnet URI, you can use that, ```html Link Text ``` or the location of a .torrent file, ```html Link Text ``` but know that WebTorrent is not yet compatible with the DHT and requires `ws` or `http` trackers. If your .torrent only includes `udp` trackers or is tracker-less and relies on the DHT, you are better off using `data-webtorrent="auto"`. If your torrent is a folder torrent rather than a single file, add a `data-file` attribute with the name of the individual file you intend the link for. ```html Sintel ``` ## License Copyright 2017 William Hilton. Licensed under [The Unlicense](http://unlicense.org/). ================================================ FILE: dist/index.css ================================================ a[data-webtorrent]{display:inline-block;color:#000;text-decoration:none;font:11pt sans-serif;font-weight:700;text-align:center;padding:10px;border-radius:10px;box-shadow:2px 2px 8px rgba(0,0,0,.2);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 1334 1534' stroke-linejoin='round'%3E%3Cstyle%3E.a%7Bfill:%23ef334c;%7D%3C/style%3E%3Cpath d='M15 374l637-368c8-5 19-5 28 0l637 368c8 5 14 14 14 24l0 736c0 10-5 19-14 24l-637 368c-8 5-19 5-28 0l-637-368c-8-5-14-14-14-24l0-736c0-10 5-19 14-24Z' class='a'/%3E%3Cpath d='M98 424l556-321c7-4 17-4 24 0l556 321c7 4 12 12 12 21l0 642c0 9-5 17-12 21l-556 321c-7 4-17 4-24 0l-556-321c-7-4-12-12-12-21l0-642c0-9 4-16 12-21Z' fill='%23343b45'/%3E%3Cpath d='M666 1078l242 219c0 0 269-155 327-189 7-4 11-11 11-19 0-54 0-261 0-261l-321-298 -259 548Z' fill='%23262b33'/%3E%3Cpath d='M980 636c-8-83-78-147-163-147 -68 0-126 41-151 100 -25-59-83-100-151-100 -85 0-155 64-163 147 -1 6-1 11-1 17 0 199 244 293 315 426 71-133 315-227 315-426 0-6 0-11-1-17Z' class='a'/%3E%3C/svg%3E"),-webkit-linear-gradient(right,#4caf50,#009e9e),-webkit-linear-gradient(bottom right,#00b7b7 0,#009e9e);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 1334 1534' stroke-linejoin='round'%3E%3Cstyle%3E.a%7Bfill:%23ef334c;%7D%3C/style%3E%3Cpath d='M15 374l637-368c8-5 19-5 28 0l637 368c8 5 14 14 14 24l0 736c0 10-5 19-14 24l-637 368c-8 5-19 5-28 0l-637-368c-8-5-14-14-14-24l0-736c0-10 5-19 14-24Z' class='a'/%3E%3Cpath d='M98 424l556-321c7-4 17-4 24 0l556 321c7 4 12 12 12 21l0 642c0 9-5 17-12 21l-556 321c-7 4-17 4-24 0l-556-321c-7-4-12-12-12-21l0-642c0-9 4-16 12-21Z' fill='%23343b45'/%3E%3Cpath d='M666 1078l242 219c0 0 269-155 327-189 7-4 11-11 11-19 0-54 0-261 0-261l-321-298 -259 548Z' fill='%23262b33'/%3E%3Cpath d='M980 636c-8-83-78-147-163-147 -68 0-126 41-151 100 -25-59-83-100-151-100 -85 0-155 64-163 147 -1 6-1 11-1 17 0 199 244 293 315 426 71-133 315-227 315-426 0-6 0-11-1-17Z' class='a'/%3E%3C/svg%3E"),linear-gradient(270deg,#4caf50 0,#009e9e),linear-gradient(to top left,#00b7b7 0,#009e9e);background-repeat:no-repeat,no-repeat,no-repeat;background-position:5px 50%,left 100%,100%;background-size:28px 28px,0 100%,100%;padding-left:3em}a[data-webtorrent]:after{content:attr(title)}a[data-webtorrent].no-webrtc a,a[data-webtorrent]:after{display:block;clear:left;font-weight:400;text-align:center;font:8pt sans-serif}a[data-webtorrent].no-webrtc a{color:#000;text-decoration:none;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}a[data-webtorrent].no-webrtc a:hover{text-decoration:underline} ================================================ FILE: dist/index.js ================================================ !function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=2)}([function(e,t){function n(){"use strict";function e(e){var o=e.currentTarget;return o.classList.contains("init")?t(e):o.classList.contains("downloading")?n(e):o.classList.contains("ready")?r(e):!o.classList.contains("seeding")||r(e)}function t(e){var t=e.currentTarget;t.classList.remove("init"),t.classList.add("downloading");try{var n=t.innerText,r=new WebTorrent;return r.on("error",function(e){console.error("ERROR: "+e.message)}),"auto"===t.dataset.webtorrent&&(t.dataset.webtorrent="https://webtorrentify.now.sh/?href="+t.href,t.title="Generating .torrent file..."),r.add(t.dataset.webtorrent,function(e){function r(){var r=e.numPeers;r+=1===r?" peer":" peers";var i=Math.floor(100*e.progress)+"%";t.style="background-size: 28px 28px, "+i+" 100%, 100%;",e.done?e.done&&t.classList.contains("seeding")&&(t.innerText=o.name+" - Ready",t.title="Seeding ("+r+")"):t.innerText.endsWith(" - Ready")||(t.innerText=n+" - "+i,t.title="Downloading ("+r+")")}console.log(e);var o;o=1===e.files.length||void 0===t.dataset.file?e.files[0]:e.files.find(function(e){return e.name===t.dataset.file}),r(),setInterval(r,500),o.getBlobURL(function(e,n){if(e)return void window.alert("WebTorrent error: source getBlobURL");t.classList.remove("downloading"),t.classList.add("ready"),t.innerText=o.name+" - Ready",t.title="Click to save file",t.download=o.name,t.href=n})}),e.preventDefault(),!1}catch(e){return console.log(e),!0}}function n(e){return e.preventDefault(),!1}function r(e){var t=e.currentTarget;return t.classList.remove("ready"),t.classList.add("seeding"),!0}for(var o=document.querySelectorAll("a[data-webtorrent]"),i=0;i { console.log('Generating animated gif') var fs = require('fs') var GIFEncoder = require('gifencoder') var encoder = new GIFEncoder(210, 64) var pngFileStream = require('png-file-stream') pngFileStream('screenshots/*.png') .pipe(encoder.createWriteStream({ repeat: -1, delay: 1000, quality: 10 })) .pipe(fs.createWriteStream('dist/animated.gif')) }) .catch(err => console.log) ================================================ FILE: index.css ================================================ a[data-webtorrent] { display: inline-block; color: #000; text-decoration: none; font: 11pt sans-serif; font-weight: bold; text-align: center; padding: 10px; border-radius: 10px; box-shadow: 2px 2px 8px rgba(0,0,0,0.2); background-image: url(./assets/webtorrent-v3.1.svg), linear-gradient(to left, #4CAF50 0%, #009e9e 100%), linear-gradient(to top left, #00b7b7 0%, #009e9e 100%); background-repeat: no-repeat, no-repeat, no-repeat; background-position: 5px 50%, left 100%, 100%; background-size: 28px 28px, 0px 100%, 100%; padding-left: 3em; } /* Used for displaying status messages */ a[data-webtorrent]::after { display: block; clear: left; content: attr(title); font-weight: normal; text-align: center; font: 8pt sans-serif; } /* Used for fallback Bittorrent link */ a[data-webtorrent].no-webrtc a { display: block; clear: left; font-weight: normal; text-align: center; font: 8pt sans-serif; color: black; text-decoration: none; width: fit-content; } a[data-webtorrent].no-webrtc a:hover { text-decoration: underline; } ================================================ FILE: index.html ================================================ Button Demo - Download with WebTorrent Button node-v6.10.2-linux-x64.tar.xz ================================================ FILE: index.js ================================================ /* global WebTorrent */ function registerWebtorrentLinks () { 'use strict' // Add onclick handlers to all elements var links = document.querySelectorAll('a[data-webtorrent]') for (var i = 0; i < links.length; i++) { // Wrap the link innerHTML so its easier to update var a = links[i] if (WebTorrent.WEBRTC_SUPPORT) { a.title = 'Download with WebTorrent' a.addEventListener('click', onButtonClick) } else { a.classList.add('no-webrtc') if (a.dataset.webtorrent !== 'auto') { a.title = '' var sublink = document.createElement('a') sublink.href = a.dataset.webtorrent sublink.innerText = 'alternate Bittorrent link' a.appendChild(sublink) } else { a.title = 'Download' } } a.classList.add('init') } function onButtonClick (e) { // The button has a simple state machine that progresses from // 'init' to 'downloading' to 'ready' to 'seeding' and clicks // are handled differently in each case. var a = e.currentTarget if (a.classList.contains('init')) return downloadWithWebTorrent(e) if (a.classList.contains('downloading')) return ignoreClicks(e) if (a.classList.contains('ready')) return saveFile(e) if (a.classList.contains('seeding')) return saveFile(e) return true } // This is what runs when user clicks the link function downloadWithWebTorrent (e) { var a = e.currentTarget a.classList.remove('init') a.classList.add('downloading') try { var title = a.innerText // Initialize WebTorrent var client = new WebTorrent() client.on('error', function (err) { console.error('ERROR: ' + err.message) }) // The 'auto' option will dynamically generate a .torrent file // using a free service I built if (a.dataset.webtorrent === 'auto') { a.dataset.webtorrent = 'https://webtorrentify.now.sh/?href=' + a.href a.title = 'Generating .torrent file...' } // This starts downloading the torrent client.add(a.dataset.webtorrent, function (torrent) { console.log(torrent) // Torrents can contain multiple files, so we have to deal with that. var file if (torrent.files.length === 1 || a.dataset.file === undefined) { file = torrent.files[0] } else { file = torrent.files.find(function (file) { return file.name === a.dataset.file }) } // Show progress bar function progress () { var numPeers = torrent.numPeers numPeers += (numPeers === 1 ? ' peer' : ' peers') var percent = Math.floor(torrent.progress * 100) + '%' // Nifty progress bar using CSS gradient backgrounds a.style = 'background-size: 28px 28px, ' + percent + ' 100%, 100%;' if (!torrent.done) { // Update download percentage if (!a.innerText.endsWith(' - Ready')) { a.innerText = title + ' - ' + percent a.title = 'Downloading (' + numPeers + ')' } } else if (torrent.done && a.classList.contains('seeding')) { a.innerText = file.name + ' - Ready' a.title = 'Seeding (' + numPeers + ')' } } progress() setInterval(progress, 500) // When the file is ready, change the button text to reflect that file.getBlobURL(function (err, url) { if (err) { window.alert('WebTorrent error: source getBlobURL') return } a.classList.remove('downloading') a.classList.add('ready') a.innerText = file.name + ' - Ready' a.title = 'Click to save file' a.download = file.name a.href = url }) }) // Prevent default link behavior and don't follow it. e.preventDefault() return false } catch (err) { console.log(err) // If something went wrong, bail and use the default link behavior // to download the link without WebTorrent if possible. return true } } // If we're already downloading don't start another download function ignoreClicks (e) { e.preventDefault() return false } // Once the file is downloaded, we change the href to point to a blob. // Thus we just let the link do its default behavior. function saveFile (e) { var a = e.currentTarget a.classList.remove('ready') a.classList.add('seeding') return true } } if (window) { window.registerWebtorrentLinks = registerWebtorrentLinks registerWebtorrentLinks() } ================================================ FILE: package.json ================================================ { "name": "download-with-webtorrent-button", "version": "1.0.5", "description": "Transform ordinary download links into super-powered WebTorrent ones!", "main": "./index.js", "style": "./index.css", "devDependencies": { "css-loader": "^0.28.0", "extract-text-webpack-plugin": "^2.1.0", "file-loader": "^0.11.1", "gifencoder": "^1.0.6", "http-server": "^0.9.0", "husky": "^0.13.3", "nightmare": "^2.10.0", "png-file-stream": "^1.0.0", "postcss-loader": "^1.3.3", "standard": "^10.0.1", "svg-url-loader": "^2.0.2", "webpack": "^2.3.3", "webtorrent": "^0.98.15" }, "browserlist": [ "last 2 versions" ], "scripts": { "start": "http-server --cors -o", "precommit": "npm test && npm run build", "build": "webpack -p", "gif": "node generate_gif.js", "test": "standard index.js" }, "repository": { "type": "git", "url": "git+https://github.com/wmhilton/download-with-webtorrent-button.git" }, "keywords": [], "author": "William Hilton ", "license": "Unlicense", "bugs": { "url": "https://github.com/wmhilton/download-with-webtorrent-button/issues" }, "homepage": "https://github.com/wmhilton/download-with-webtorrent-button#readme" } ================================================ FILE: postcss.config.js ================================================ module.exports = { plugins: [ require('autoprefixer') ] } ================================================ FILE: webpack.config.js ================================================ const path = require('path') const pkg = require('./package.json') const ExtractTextPlugin = require('extract-text-webpack-plugin') module.exports = { entry: [pkg.main, pkg.style], output: { filename: 'index.js', path: path.resolve(__dirname, 'dist') }, module: { rules: [ { test: /\.css$/, use: ExtractTextPlugin.extract({ use: [ { loader: 'css-loader', options: {importLoaders: 1} }, 'postcss-loader' ] }) }, { test: /\.svg/, use: 'svg-url-loader' }, ], }, plugins: [ new ExtractTextPlugin('index.css'), ] }