2018-03-05 21:49:44 -08:00
|
|
|
import fs from 'fs'
|
2018-03-05 21:02:16 -08:00
|
|
|
import path from 'path'
|
2017-12-17 03:15:55 -08:00
|
|
|
import { pwrap, pick, fcurrency, fmsat, pngPixel } from './lib/util'
|
|
|
|
|
|
|
|
// Setup
|
|
|
|
const app = require('express')()
|
|
|
|
, conf = require('./lib/config')(process.argv[2] || process.cwd())
|
2017-12-29 11:34:57 -08:00
|
|
|
, charge = require('lightning-charge-client')(conf.charge_url, conf.charge_token)
|
2017-12-17 09:22:42 -08:00
|
|
|
, files = require('./lib/files')(conf.directory, conf.default_price, conf.invoice_ttl, conf.files)
|
2017-12-17 03:15:55 -08:00
|
|
|
, tokenr = require('./lib/token')(conf.token_secret)
|
|
|
|
, preview = require('./lib/preview')(files, conf.cache_path)
|
|
|
|
|
|
|
|
// Express settings
|
|
|
|
app.set('trust proxy', conf.proxied)
|
|
|
|
app.set('env', conf.env)
|
|
|
|
|
|
|
|
app.set('view engine', 'pug')
|
|
|
|
app.set('views', conf.views_dir)
|
|
|
|
|
|
|
|
app.enable('json escape')
|
|
|
|
app.enable('strict routing')
|
|
|
|
app.enable('case sensitive routing')
|
|
|
|
|
|
|
|
// View locals
|
|
|
|
Object.assign(app.locals, {
|
|
|
|
conf, fmsat, fcurrency
|
|
|
|
, prettybytes: require('pretty-bytes')
|
|
|
|
, markdown: require('markdown-it')()
|
|
|
|
, qruri: require('qruri')
|
2018-03-05 20:48:48 -08:00
|
|
|
, version: require('../package').version
|
2017-12-17 03:15:55 -08:00
|
|
|
, pretty: (conf.env === 'development')
|
|
|
|
})
|
|
|
|
|
|
|
|
// Middlewares
|
|
|
|
app.get('/favicon.ico', (req, res) => res.sendStatus(204)) // to prevent logging
|
|
|
|
app.use(require('morgan')('dev'))
|
2017-12-17 17:44:03 -08:00
|
|
|
app.use(require('cookie-parser')())
|
2017-12-17 03:15:55 -08:00
|
|
|
app.use(require('body-parser').json())
|
|
|
|
app.use(require('body-parser').urlencoded({ extended: false }))
|
2017-12-17 17:44:03 -08:00
|
|
|
app.use(require('csurf')({ cookie: true }))
|
2017-12-17 03:15:55 -08:00
|
|
|
|
|
|
|
// Static assets
|
2018-03-05 21:49:44 -08:00
|
|
|
if (fs.existsSync(path.join(conf.static_dir, 'style.styl')))
|
2018-03-05 21:40:26 -08:00
|
|
|
app.use('/_assets', require('stylus').middleware({ src: conf.static_dir, serve: true }))
|
|
|
|
|
2017-12-17 03:15:55 -08:00
|
|
|
app.use('/_assets', require('express').static(conf.static_dir))
|
2018-03-05 21:02:16 -08:00
|
|
|
app.use('/_themes', require('express').static(path.resolve(require.resolve('bootswatch/package'), '..', 'dist')))
|
2017-12-17 03:15:55 -08:00
|
|
|
|
2018-03-05 21:40:26 -08:00
|
|
|
|
2017-12-17 03:15:55 -08:00
|
|
|
// Create invoice
|
|
|
|
app.post('/_invoice', pwrap(async (req, res) => {
|
|
|
|
const file = await files.load(req.body.file)
|
|
|
|
if (file.type !== 'file') return res.sendStatus(405)
|
|
|
|
|
2017-12-29 11:34:57 -08:00
|
|
|
const invoice = await charge.invoice(files.invoice(file))
|
2017-12-17 03:15:55 -08:00
|
|
|
res.status(201).format({
|
|
|
|
html: _ => res.redirect(file.urlpath + '?invoice=' + invoice.id)
|
|
|
|
, json: _ => res.send(pick(invoice, 'id', 'msatoshi', 'quoted_currency', 'quoted_amount', 'payreq'))
|
|
|
|
})
|
|
|
|
}))
|
|
|
|
|
|
|
|
// Payment updates long-polling via <img> hack
|
|
|
|
app.get('/_invoice/:invoice/longpoll.png', pwrap(async (req, res) => {
|
2017-12-29 11:34:57 -08:00
|
|
|
const paid = await charge.wait(req.params.invoice, 100)
|
2018-03-05 10:05:43 -08:00
|
|
|
if (paid !== null) res.type('png').send(pngPixel)
|
2017-12-17 03:15:55 -08:00
|
|
|
else res.sendStatus(402)
|
2017-12-29 11:34:57 -08:00
|
|
|
// @TODO close charge request on client disconnect
|
2017-12-17 03:15:55 -08:00
|
|
|
}))
|
|
|
|
|
|
|
|
// File browser
|
|
|
|
app.get('/:rpath(*)', pwrap(async (req, res) => {
|
|
|
|
const file = await files.load(req.params.rpath)
|
|
|
|
if (file.type == 'dir') return res.render('dir', file)
|
|
|
|
|
2017-12-29 11:34:57 -08:00
|
|
|
const invoice = req.query.invoice && await charge.fetch(req.query.invoice)
|
2017-12-17 03:15:55 -08:00
|
|
|
, access = req.query.token && tokenr.parse(file.path, req.query.token)
|
|
|
|
|
|
|
|
if (access) {
|
2018-03-05 10:05:43 -08:00
|
|
|
if ('download' in req.query) res.type(file.mime).download(file.fullpath)
|
|
|
|
else if ('view' in req.query) res.type(file.mime).sendFile(file.fullpath)
|
2017-12-17 03:15:55 -08:00
|
|
|
else res.render('file', { ...file, access })
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (invoice) {
|
2018-01-16 12:55:40 -08:00
|
|
|
if (invoice.status == 'paid') res.redirect(escape(file.name) + '?token=' + tokenr.make(invoice, conf.download_ttl))
|
2017-12-17 03:15:55 -08:00
|
|
|
else res.render('file', { ...file, invoice })
|
|
|
|
}
|
|
|
|
|
|
|
|
else if ('preview' in req.query) await preview.handler(file, res)
|
|
|
|
|
2017-12-17 17:44:03 -08:00
|
|
|
else res.render('file', { ...file, csrf: req.csrfToken(), preview: await preview.metadata(file) })
|
2017-12-17 03:15:55 -08:00
|
|
|
}))
|
|
|
|
|
|
|
|
// Normalize errors to HTTP status codes
|
|
|
|
app.use((err, req, res, next) =>
|
|
|
|
err.syscall === 'stat' && err.code == 'ENOENT' ? res.sendStatus(404)
|
2018-03-05 10:05:43 -08:00
|
|
|
: err.message === 'not found' ? res.sendStatus(404)
|
2017-12-17 03:15:55 -08:00
|
|
|
: err.message === 'forbidden' ? res.sendStatus(403)
|
|
|
|
: next(err))
|
|
|
|
|
|
|
|
// Go!
|
|
|
|
app.listen(conf.port, conf.host, _ => console.log('FileBazaar serving %s on port %d, browse at %s', conf.directory, conf.port, conf.url))
|
|
|
|
|
|
|
|
// strict handling for uncaught promise rejections
|
|
|
|
process.on('unhandledRejection', err => { throw err })
|