diff --git a/.gitignore b/.gitignore index 462a33cc..457dc640 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,5 @@ node_modules -secret/* -!secret/index-sample.js - static/main.css static/bundle.js +static/__rtcConfig__.json diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 00000000..f6df16a5 --- /dev/null +++ b/Caddyfile @@ -0,0 +1,6 @@ +localhost +gzip +rewrite /__rtcConfig__ /__rtcConfig__.json # Backwards compatability +file_server { + root static +} diff --git a/README.md b/README.md index a2a29bdc..75c7c864 100644 --- a/README.md +++ b/README.md @@ -21,15 +21,13 @@ console and refresh to get detailed log output. ## Install -If you just want to do file transfer on your site, or fetch/seed files over WebTorrent, then there's no need to run a copy of instant.io on your own server. Just use the WebTorrent script directly. You can learn more at https://webtorrent.io. +instant.io is a completely static site, and is a demonstration of how little code is required to use WebTorrent. -The client-side code that instant.io uses is [here](https://github.com/webtorrent/instant.io/blob/master/client/index.js). +Almost all the client-side code that instant.io uses is [here](./client/index.js). -### Run a copy of this site on your own server +### Build -To get a clone of https://instant.io running on your own server, follow these instructions. - -Get the code: +To build a copy of the https://instant.io front-end, follow these instructions. ``` git clone https://github.com/webtorrent/instant.io @@ -37,11 +35,17 @@ cd instant.io npm install ``` -Modify the configuration options in [`config.js`](https://github.com/webtorrent/instant.io/blob/master/config.js) to set the IP/port you want the server to listen on. +### Serve + +All instant.io requires to run is a static file server, and some [NAT Traversal](https://www.html5rocks.com/en/tutorials/webrtc/infrastructure/#after-signaling-using-ice-to-cope-with-nats-and-firewalls) services. + +There are example nginx and Caddy configs in this repo, but any static file server can be used to serve instant.io -Copy [`secret/index-sample.js`](https://github.com/webtorrent/instant.io/blob/master/secret/index-sample.js) to `secret/index.js` and set the Twilio API key if you want a [NAT traversal service](https://www.twilio.com/stun-turn) (to help peers connect when behind a firewall). +1. Build the client (as explained above) +2. Serve the `static` directory using a static file server +3. Redirect `/__rtConfig__` to a JSON file with a single `iceServers` key conforming to the [WebRTC Spec](https://w3c.github.io/webrtc-pc/#dom-rtcconfiguration) -To start the server, run `npm start`. That should be it! +That's it! ## Mirrors @@ -51,7 +55,7 @@ To start the server, run `npm start`. That should be it! ## Tips 1. Create a shareable link by adding a torrent infohash or magnet link to the end -of the URL. For example: `https://instant.io#INFO_HASH` or `https://instant.io/#MAGNET_LINK`. +of the URL. For example: `https://instant.io/#INFO_HASH` or `https://instant.io/#MAGNET_LINK`. 2. You can add multiple torrents in the same browser window. diff --git a/config.js b/config.js deleted file mode 100644 index c6981119..00000000 --- a/config.js +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Is site running in production? - */ -exports.isProd = process.env.NODE_ENV === 'production' diff --git a/nginx.conf b/nginx.conf index a85a4318..2b271a70 100644 --- a/nginx.conf +++ b/nginx.conf @@ -1,5 +1,4 @@ server { - listen 23.239.22.146:80; listen 23.239.22.146:443 http2 ssl; server_name instant.io www.instant.io; @@ -7,10 +6,20 @@ server { ssl_certificate_key /etc/letsencrypt/live/instant.io/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/instant.io/chain.pem; - location / { - proxy_pass http://127.0.0.1:7010; - include /home/feross/config/nginx/_proxy.include; + gzip on; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + + root /home/feross/www/instant.io/static; + + location __rtcConfig__ { + try_files __rtcConfig__.json =404; } include /home/feross/config/nginx/_server.include; } + +server { + listen 23.239.22.146:80; + server_name instant.io www.instant.io; + return 301 https://$server_name$request_uri; +} diff --git a/package.json b/package.json index fd7f5967..37f5acbc 100644 --- a/package.json +++ b/package.json @@ -11,29 +11,22 @@ "url": "https://github.com/webtorrent/instant.io/issues" }, "dependencies": { - "compression": "^1.7.4", - "cors": "^2.8.5", "create-torrent": "^4.4.1", "date-fns": "^2.6.0", "debug": "^4.1.1", "drag-drop": "^6.0.0", "escape-html": "^1.0.3", - "express": "^4.17.1", "jszip": "^3.2.2", "prettier-bytes": "^1.0.4", - "pug": "^2.0.4", - "rollbar": "^2.13.0", "simple-get": "^3.1.0", "throttleit": "^1.0.0", "thunky": "^1.1.0", - "twilio": "^3.36.0", "upload-element": "^1.0.1", "webtorrent": "^0.107.6" }, "devDependencies": { "browserify": "^16.5.0", "husky": "^3.0.9", - "nodemon": "^1.19.4", "standard": "*", "stylus": "^0.54.7", "tinyify": "^2.5.2", @@ -63,15 +56,14 @@ "build-css": "stylus css/main.styl -o static/ -c", "build-js": "browserify --plugin tinyify client > static/bundle.js", "deploy": "./tools/trigger-deploy.sh", - "secret-download": "rsync -a -O -v --delete -e \"ssh -p 44444\" feross@webtorrent:\"/home/feross/www/instant.io/secret/\" secret/", - "secret-upload": "rsync -a -O -v --delete -e \"ssh -p 44444\" secret/ feross@webtorrent:/home/feross/www/instant.io/secret/", + "secret-download": "rsync -a -O -v --delete -e \"ssh -p 44444\" feross@webtorrent:\"/home/feross/www/instant.io/secret/rtcConfig.json\" static/rtcConfig.json", + "secret-upload": "rsync -a -O -v --delete -e \"ssh -p 44444\" static/rtcConfig.json feross@webtorrent:/home/feross/www/instant.io/secret/rtcConfig.json", "size": "npm run size-js && npm run size-css", "size-css": "npm run build-css && cat static/main.css | gzip | wc -c", "size-js": "npm run build-js && cat static/bundle.js | gzip | wc -c", - "start": "npm run build && node server", "test": "standard", "update-authors": "./tools/update-authors.sh", - "watch": "npm run watch-css & npm run watch-js & DEBUG=instant* nodemon server", + "watch": "npm run watch-css & npm run watch-js", "watch-css": "stylus css/main.styl -o static/ -w", "watch-js": "watchify client -o static/bundle.js -dv" } diff --git a/secret/index-sample.js b/secret/index-sample.js deleted file mode 100644 index d8b7e00d..00000000 --- a/secret/index-sample.js +++ /dev/null @@ -1,29 +0,0 @@ -exports.twilio = { - accountSid: 'TODO', - authToken: 'TODO' -} - -exports.rollbar = { - accessToken: 'TODO' -} - -exports.iceServers = [ - { - urls: 'stun:TODO' - }, - { - urls: 'turn:TODO:3478?transport=udp', - username: 'TODO', - credential: 'TODO' - }, - { - urls: 'turn:TODO:3478?transport=tcp', - username: 'TODO', - credential: 'TODO' - }, - { - urls: 'turn:TODO:443?transport=tcp', - username: 'TODO', - credential: 'TODO' - } -] diff --git a/server/index.js b/server/index.js deleted file mode 100644 index ac32dae1..00000000 --- a/server/index.js +++ /dev/null @@ -1,173 +0,0 @@ -require('./rollbar') - -var compress = require('compression') -var cors = require('cors') -var express = require('express') -var http = require('http') -var pug = require('pug') -var path = require('path') -// var twilio = require('twilio') -// var util = require('util') - -var config = require('../config') - -var PORT = Number(process.argv[2]) || 4000 - -var CORS_WHITELIST = [ - // Favor to friends :) - 'http://rollcall.audio', - 'https://rollcall.audio' -] - -var secret -try { - secret = require('../secret') -} catch (err) {} - -var app = express() -var server = http.createServer(app) - -// Trust "X-Forwarded-For" and "X-Forwarded-Proto" nginx headers -app.enable('trust proxy') - -// Disable "powered by express" header -app.set('x-powered-by', false) - -// Use pug for templates -app.set('views', path.join(__dirname, 'views')) -app.set('view engine', 'pug') -app.engine('pug', pug.renderFile) - -// Pretty print JSON -app.set('json spaces', 2) - -// Use GZIP -app.use(compress()) - -app.use(function (req, res, next) { - // Force SSL - if (config.isProd && req.protocol !== 'https') { - return res.redirect('https://' + (req.hostname || 'instant.io') + req.url) - } - - // Redirect www to non-www - if (config.isProd && req.hostname === 'www.instant.io') { - return res.redirect('https://instant.io' + req.url) - } - - // Use HTTP Strict Transport Security - // Lasts 1 year, incl. subdomains, allow browser preload list - if (config.isProd) { - res.header( - 'Strict-Transport-Security', - 'max-age=31536000; includeSubDomains; preload' - ) - } - - // Add cross-domain header for fonts, required by spec, Firefox, and IE. - var extname = path.extname(req.url) - if (['.eot', '.ttf', '.otf', '.woff', '.woff2'].indexOf(extname) >= 0) { - res.header('Access-Control-Allow-Origin', '*') - } - - // Prevents IE and Chrome from MIME-sniffing a response. Reduces exposure to - // drive-by download attacks on sites serving user uploaded content. - res.header('X-Content-Type-Options', 'nosniff') - - // Prevent rendering of site within a frame. - res.header('X-Frame-Options', 'DENY') - - // Enable the XSS filter built into most recent web browsers. It's usually - // enabled by default anyway, so role of this headers is to re-enable for this - // particular website if it was disabled by the user. - res.header('X-XSS-Protection', '1; mode=block') - - // Force IE to use latest rendering engine or Chrome Frame - res.header('X-UA-Compatible', 'IE=Edge,chrome=1') - - next() -}) - -app.use(express.static(path.join(__dirname, '../static'))) - -app.get('/', function (req, res) { - res.render('index', { - title: 'Instant.io - Streaming file transfer over WebTorrent' - }) -}) - -// Fetch new iceServers from twilio token regularly -// var iceServers -// var twilioClient -// try { -// twilioClient = twilio(secret.twilio.accountSid, secret.twilio.authToken) -// } catch (err) {} - -// function updateIceServers () { -// twilioClient.tokens.create({}, function (err, token) { -// if (err) return console.error(err.message || err) -// if (!token.iceServers) { -// return console.error('twilio response ' + util.inspect(token) + ' missing iceServers') -// } - -// // Support new spec (`RTCIceServer.url` was renamed to `RTCIceServer.urls`) -// iceServers = token.iceServers.map(function (server) { -// if (server.url != null) { -// server.urls = server.url -// delete server.url -// } -// return server -// }) -// }) -// } - -// if (twilioClient) { -// setInterval(updateIceServers, 60 * 60 * 4 * 1000).unref() -// updateIceServers() -// } - -// WARNING: This is *NOT* a public endpoint. Do not depend on it in your app. -app.get('/__rtcConfig__', cors({ - origin: function (origin, cb) { - var allowed = CORS_WHITELIST.indexOf(origin) >= 0 || - /https?:\/\/localhost(:|$)/.test(origin) || - /https?:\/\/airtap\.local(:|$)/.test(origin) - cb(null, allowed) - } -}), function (req, res) { - // console.log('referer:', req.headers.referer, 'user-agent:', req.headers['user-agent']) - var iceServers = secret.iceServers - - if (!iceServers) return res.status(404).send({ iceServers: [] }) - res.send({ - comment: 'WARNING: This is *NOT* a public endpoint. Do not depend on it in your app', - iceServers: iceServers - }) -}) - -app.get('/500', (req, res, next) => { - next(new Error('Manually visited /500')) -}) - -app.get('*', function (req, res) { - res.status(404).render('error', { - title: '404 Page Not Found - Instant.io', - message: '404 Not Found' - }) -}) - -if (global.rollbar) app.use(global.rollbar.errorHandler()) - -// error handling middleware -app.use(function (err, req, res, next) { - console.error(err.stack) - const code = typeof err.code === 'number' ? err.code : 500 - res.status(code).render('error', { - title: '500 Internal Server Error - Instant.io', - message: err.message || err - }) -}) - -server.listen(PORT, '127.0.0.1', function () { - console.log('listening on port %s', server.address().port) -}) diff --git a/server/rollbar.js b/server/rollbar.js deleted file mode 100644 index 2ae2c359..00000000 --- a/server/rollbar.js +++ /dev/null @@ -1,34 +0,0 @@ -const Rollbar = require('rollbar') -const { isProd } = require('../config') -const { rollbar: rollbarSecret } = require('../secret') - -if (isProd) { - global.rollbar = new Rollbar({ - accessToken: rollbarSecret.accessToken, - captureUncaught: true, - captureUnhandledRejections: true, - checkIgnore: (isUncaught, args) => { - const err = args[0] - - // Never ignore uncaught errors - if (isUncaught) return false - - // Ignore 'Bad Request' errors - if (err.status === 400) return true - - // Ignore 'Forbidden' errors - if (err.status === 403) return true - - // Ignore 'Not Found' errors - if (err.status === 404) return true - - // Ignore 'Precondition Failed' errors - if (err.status === 412) return true - - // Ignore 'Range Not Satisfiable' errors - if (err.status === 416) return true - - return false - } - }) -} diff --git a/server/views/error.pug b/server/views/error.pug deleted file mode 100644 index 2f8df34c..00000000 --- a/server/views/error.pug +++ /dev/null @@ -1,4 +0,0 @@ -extends layout -block body - h1 Error - h2= message diff --git a/server/views/index.pug b/server/views/index.pug deleted file mode 100644 index 925bbceb..00000000 --- a/server/views/index.pug +++ /dev/null @@ -1,29 +0,0 @@ -extends layout - -block body - main - picture.logo - source(srcset='/logo.svg') - img(src='/logo.png' alt='Instant.io') - p.subtitle Streaming file transfer over WebTorrent (torrents on the web) - - h1#logHeading(style='display: none') Log - .speed - .log - - section - h1 Start sharing - p - | Drag-and-drop files to begin seeding. Or choose a file: - | #{ ' ' } - input(type='file', name='upload', multiple) - - section - h1 Start downloading - form - label(for='torrentId') Download from a magnet link or info hash - input(name='torrentId', placeholder='magnet:' required) - button(type='submit') Download - - footer - p: small Powered by WebTorrent. 100% open source & free software. Works in Chrome, Firefox, and Opera. Source code available on GitHub. © 2019 WebTorrent, LLC. diff --git a/server/views/layout.pug b/server/views/layout.pug deleted file mode 100644 index 7a815d2b..00000000 --- a/server/views/layout.pug +++ /dev/null @@ -1,23 +0,0 @@ -doctype html -html(lang='en') - head - title= title - meta(charset='utf-8') - meta(http-equiv='Content-Security-Policy', content='upgrade-insecure-requests') - meta(name='viewport', content='width=device-width, initial-scale=1') - link(rel='search', href='/opensearch.xml', title='Instant.io', type='application/opensearchdescription+xml') - link(rel='stylesheet', href='/main.css', charset='utf-8') - - body(class=locals.cls || '') - block body - script(src='/bundle.js') - - //- Google Analytics - script. - window.ga = window.ga || function () { - (ga.q = ga.q || []).push(arguments) - } - ga.l = +new Date - ga('create', 'UA-3898076-18', 'auto') - ga('send', 'pageview') - script(async, src='//www.google-analytics.com/analytics.js') diff --git a/static/index.html b/static/index.html new file mode 100644 index 00000000..bb004f4b --- /dev/null +++ b/static/index.html @@ -0,0 +1,49 @@ + + + + Instant.io - Streaming file transfer over WebTorrent + + + + + + + + +
+ +

Streaming file transfer over WebTorrent (torrents on the web)

+

Log

+
+
+
+

Start sharing

+

+ Drag-and-drop files to begin seeding. Or choose a file: + +

+
+
+

Start downloading

+
+ + + +
+
+ +
+ + + + + \ No newline at end of file diff --git a/supervisor.conf b/supervisor.conf deleted file mode 100644 index 07f62d35..00000000 --- a/supervisor.conf +++ /dev/null @@ -1,9 +0,0 @@ -[program:instant] -user=www-data -directory=/home/feross/www/instant.io/ -command=/usr/bin/node server 7010 -environment=NODE_ENV="production",DEBUG="instant*" -startsecs=5 -startretries=1000000 -redirect_stderr=true -stdout_logfile=/home/feross/www/log/instant.io/site.log