secure-scuttlebot classic
1var fs = require('fs')
2var path = require('path')
3var pull = require('pull-stream')
4var toPull = require('stream-to-pull-stream')
5
6exports.name = 'frontend'
7exports.version = '1.0.0'
8exports.manifest = {}
9
10exports.init = function (sbot, config) {
11 if (!sbot.ws || typeof sbot.ws.use !== 'function')
12 return {}
13
14 var patchbayDir = path.join(__dirname, '..', 'patchbay')
15 var patchbayIndex = path.join(patchbayDir, 'build', 'index.html')
16 var hasPatchbay = fs.existsSync(patchbayIndex)
17 var wsCfg = config && config.ws ? config.ws : {}
18 var wsHost = typeof wsCfg.host === 'string' ? wsCfg.host : '127.0.0.1'
19 var wsPort = typeof wsCfg.port === 'number' ? wsCfg.port : 8989
20
21 if (hasPatchbay) {
22 console.log('Patchbay launched at http://' + wsHost + ':' + wsPort + '/')
23 }
24
25 function serveFile (res, filePath) {
26 var ext = path.extname(filePath)
27 var contentType = 'text/plain'
28 if (ext === '.html') contentType = 'text/html; charset=utf-8'
29 else if (ext === '.js') contentType = 'application/javascript; charset=utf-8'
30 else if (ext === '.css') contentType = 'text/css; charset=utf-8'
31 else if (ext === '.json') contentType = 'application/json; charset=utf-8'
32
33 res.writeHead(200, {'Content-Type': contentType})
34 fs.createReadStream(filePath).pipe(res)
35 }
36
37 sbot.ws.use(function (req, res, next) {
38 var url = req.url.split('?')[0]
39
40 if (req.method === 'OPTIONS' && url === '/blobs/add') {
41 res.writeHead(204, {
42 'Access-Control-Allow-Origin': '*',
43 'Access-Control-Allow-Methods': 'POST, OPTIONS',
44 'Access-Control-Allow-Headers': 'Content-Type'
45 })
46 return res.end()
47 }
48
49 if (req.method === 'POST' && url === '/blobs/add' && sbot.blobs && typeof sbot.blobs.add === 'function') {
50 return pull(
51 toPull.source(req),
52 sbot.blobs.add(function (err, hash) {
53 if (err) {
54 res.writeHead(500, {
55 'Content-Type': 'text/plain; charset=utf-8',
56 'Access-Control-Allow-Origin': '*'
57 })
58 return res.end(err.message)
59 }
60 res.writeHead(200, {
61 'Content-Type': 'text/plain; charset=utf-8',
62 'Access-Control-Allow-Origin': '*'
63 })
64 res.end(hash)
65 })
66 )
67 }
68
69 if (req.method === 'GET' && url.indexOf('/blobs/get/') === 0 && sbot.blobs && typeof sbot.blobs.get === 'function') {
70 var hash = decodeURIComponent(url.substring('/blobs/get/'.length))
71
72 return sbot.blobs.has(hash, function (err, has) {
73 if (err || !has) {
74 res.writeHead(404, {'Content-Type': 'application/json'})
75 return res.end(JSON.stringify({error: 'blob not found', id: hash}))
76 }
77
78 res.writeHead(200, {'Content-Type': 'application/octet-stream'})
79 pull(
80 sbot.blobs.get({key: hash}),
81 toPull.sink(res)
82 )
83 })
84 }
85
86 if (req.method !== 'GET' && req.method !== 'HEAD') return next()
87
88 if ((url === '/' || url === '/index.html') && hasPatchbay) {
89 var host = null
90 if (req && req.headers) {
91 host = req.headers['x-forwarded-host'] || req.headers.host
92 }
93 var remote = null
94
95 if (host && sbot.id && typeof sbot.id === 'string') {
96 var i = sbot.id.indexOf('.')
97 var key = i === -1 ? sbot.id.substring(1) : sbot.id.substring(1, i)
98
99 var proto = 'http'
100 if (req.connection && req.connection.encrypted)
101 proto = 'https'
102 else if (req.headers && typeof req.headers['x-forwarded-proto'] === 'string')
103 proto = req.headers['x-forwarded-proto'].split(',')[0].trim()
104
105 var wsProto = proto === 'https' ? 'wss' : 'ws'
106
107 remote = wsProto + '://' + host + '~shs:' + key
108 }
109
110 return fs.readFile(patchbayIndex, 'utf8', function (err, html) {
111 if (err) return next(err)
112
113 if (remote) {
114 var script = '<script>window.PATCHBAY_REMOTE = ' +
115 JSON.stringify(remote) +
116 ';</script>'
117 html = html.replace('</head>', script + '</head>')
118 }
119
120 res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'})
121 res.end(html)
122 })
123 }
124
125 if (url === '/') url = '/index.html'
126
127 var relPath = url.replace(/^\/+/, '')
128 var patchbayPath = path.join(patchbayDir, relPath)
129
130 if (patchbayPath.indexOf(patchbayDir) !== 0 || relPath.indexOf('..') !== -1)
131 patchbayPath = null
132
133 if (!patchbayPath) return next()
134
135 fs.stat(patchbayPath, function (err2, stat2) {
136 if (err2 || !stat2 || !stat2.isFile()) return next()
137 serveFile(res, patchbayPath)
138 })
139 })
140
141 return {}
142}