git-ssb-web
Advanced tools
Comparing version 1.2.0 to 1.3.0
19
about.js
@@ -12,20 +12,7 @@ /* ssb-about | ||
var cat = require('pull-cat') | ||
var asyncMemo = require('./async-memo') | ||
module.exports = function (sbot, myId) { | ||
var cache = {/* id: about */} | ||
var callbacks = {/* id: [callback] */} | ||
module.exports = function (sbot, id) { | ||
var getAbout = asyncMemo(getAboutFull, sbot, id) | ||
function getAbout(id, cb) { | ||
if (id in cache) | ||
return cb(null, cache[id]) | ||
if (id in callbacks) | ||
return callbacks[id].push(cb) | ||
var cbs = callbacks[id] = [cb] | ||
getAboutFull(sbot, myId, id, function (err, about) { | ||
var about = !err && (cache[id] = about) | ||
while (cbs.length) | ||
cbs.pop()(err, about) | ||
}) | ||
} | ||
getAbout.getName = function (id, cb) { | ||
@@ -32,0 +19,0 @@ getAbout(id, function (err, about) { |
334
index.js
@@ -0,2 +1,4 @@ | ||
var fs = require('fs') | ||
var http = require('http') | ||
var path = require('path') | ||
var url = require('url') | ||
@@ -10,3 +12,17 @@ var ref = require('ssb-ref') | ||
var ssbAbout = require('./about') | ||
var marked = require('ssb-marked') | ||
var asyncMemo = require('./async-memo') | ||
var multicb = require('multicb') | ||
marked.setOptions({ | ||
gfm: true, | ||
mentions: true, | ||
tables: true, | ||
breaks: true, | ||
pedantic: false, | ||
sanitize: true, | ||
smartLists: true, | ||
smartypants: false | ||
}) | ||
function parseAddr(str, def) { | ||
@@ -77,2 +93,30 @@ if (!str) return def | ||
function tryDecodeURIComponent(str) { | ||
if (!str || (str[0] == '%' && ref.isBlobId(str))) | ||
return str | ||
try { | ||
str = decodeURIComponent(str) | ||
} finally { | ||
return str | ||
} | ||
} | ||
function getRepoName(repoId, cb) { | ||
// TODO: use petnames | ||
cb(null, repoId.substr(0, 20) + '…') | ||
} | ||
var hasOwnProp = Object.prototype.hasOwnProperty | ||
function getContentType(filename) { | ||
var ext = filename.split('.').pop() | ||
return hasOwnProp.call(contentTypes, ext) | ||
? contentTypes[ext] | ||
: 'text/plain' | ||
} | ||
var contentTypes = { | ||
css: 'text/css' | ||
} | ||
var msgTypes = { | ||
@@ -88,3 +132,3 @@ 'git-repo': true, | ||
module.exports = function (listenAddr, cb) { | ||
var ssb, reconnect, myId | ||
var ssb, reconnect, myId, getRepo | ||
var about = function (id, cb) { cb(null, {name: id}) } | ||
@@ -103,2 +147,5 @@ | ||
}) | ||
getRepo = asyncMemo(function (id, cb) { | ||
ssbGit.getRepo(ssb, id, {live: true}, cb) | ||
}) | ||
} | ||
@@ -132,7 +179,10 @@ } | ||
var u = url.parse(req.url) | ||
var dirs = u.pathname.slice(1).split(/\/+/).map(decodeURIComponent) | ||
var dirs = u.pathname.slice(1).split(/\/+/) | ||
switch (dirs[0]) { | ||
case '': | ||
return serveIndex(req) | ||
case 'static': | ||
return serveFile(req, dirs) | ||
default: | ||
dirs = dirs.map(tryDecodeURIComponent) | ||
if (ref.isMsgId(dirs[0])) | ||
@@ -147,13 +197,37 @@ return serveRepoPage(dirs[0], dirs.slice(1)) | ||
function serve404(req) { | ||
var body = '404 Not Found' | ||
function serveFile(req, dirs) { | ||
var filename = path.join.apply(path, [__dirname].concat(dirs)) | ||
return readNext(function (cb) { | ||
fs.stat(filename, function (err, stats) { | ||
cb(null, err ? | ||
err.code == 'ENOENT' ? serve404(req) | ||
: servePlainError(500, err.message) | ||
: stats.isDirectory() ? | ||
servePlainError(403, 'Directory not listable') | ||
: cat([ | ||
pull.once([200, { | ||
'Content-Type': getContentType(filename), | ||
'Content-Length': stats.size, | ||
'Last-Modified': stats.mtime.toGMTString() | ||
}]), | ||
toPull(fs.createReadStream(filename)) | ||
])) | ||
}) | ||
}) | ||
} | ||
function servePlainError(code, msg) { | ||
return pull.values([ | ||
[404, { | ||
'Content-Length': body.length, | ||
[code, { | ||
'Content-Length': msg.length, | ||
'Content-Type': 'text/plain' | ||
}], | ||
body | ||
msg | ||
]) | ||
} | ||
function serve404(req) { | ||
return servePlainError(404, '404 Not Found') | ||
} | ||
function renderTry(read) { | ||
@@ -177,16 +251,32 @@ var ended | ||
function serveError(id, err) { | ||
function serveTemplate(title, code, read) { | ||
if (read === undefined) return serveTemplate.bind(this, title, code) | ||
return cat([ | ||
pull.values([ | ||
[code || 200, { | ||
'Content-Type': 'text/html' | ||
}], | ||
'<!doctype html><html><head><meta charset=utf-8>', | ||
'<title>' + escapeHTML(title || 'git ssb') + '</title>', | ||
'<link rel=stylesheet href="/static/styles.css?' + Math.random() + '"/>', | ||
'</head>\n', | ||
'<body>', | ||
'<header>', | ||
'<h1><a href="/">git ssb</a></h1>', | ||
'</header>', | ||
'<article>']), | ||
renderTry(read), | ||
pull.once('</article></body></html>') | ||
]) | ||
} | ||
function serveError(err) { | ||
if (err.message == 'stream is closed') | ||
reconnect() | ||
return pull.values([ | ||
[500, { | ||
'Content-Type': 'text/html' | ||
}], | ||
'<!doctype html><html><head><meta charset=utf-8>', | ||
'<title>' + err.name + '</title></head><body>', | ||
'<h1><a href="/">git ssb</a></h1>', | ||
'<h2>' + err.name + '</h3>' + | ||
'<pre>' + escapeHTML(err.stack) + '</pre>' + | ||
'</body></html>' | ||
]) | ||
return pull( | ||
pull.once( | ||
'<h2>' + err.name + '</h3>' + | ||
'<pre>' + escapeHTML(err.stack) + '</pre>'), | ||
serveTemplate(err.name, 500) | ||
) | ||
} | ||
@@ -197,31 +287,36 @@ | ||
function renderFeed(feedId) { | ||
var opts = { | ||
reverse: true, | ||
id: feedId | ||
} | ||
return pull( | ||
ssb.links({ | ||
reverse: true, | ||
rel: 'repo', | ||
source: feedId, | ||
values: true | ||
feedId ? ssb.createUserStream(opts) : ssb.createLogStream(opts), | ||
pull.filter(function (msg) { | ||
return msg.value.content.type in msgTypes | ||
}), | ||
pull.take(20), | ||
pull.asyncMap(renderUpdate) | ||
pull.asyncMap(function (msg, cb) { | ||
about.getName(msg.value.author, function (err, name) { | ||
if (err) return cb(err) | ||
switch (msg.value.content.type) { | ||
case 'git-repo': return renderRepoCreated(msg, name, cb) | ||
case 'git-update': return renderUpdate(msg, name, cb) | ||
} | ||
}) | ||
}) | ||
) | ||
} | ||
/* | ||
function renderRepoCreated(msg, cb) { | ||
function renderRepoCreated(msg, authorName, cb) { | ||
var repoLink = link([msg.key]) | ||
var authorLink = link([msg.value.author]) | ||
cb(null, '<p>' + timestamp(msg.value.timestamp) + '<br>' + | ||
authorLink + ' created repo ' + repoLink + '</p>') | ||
var authorLink = link([msg.value.author], authorName) | ||
cb(null, '<section class="collapse">' + timestamp(msg.value.timestamp) + '<br>' + | ||
authorLink + ' created repo ' + repoLink + '</section>') | ||
} | ||
*/ | ||
function renderUpdate(msg, cb) { | ||
about.getName(msg.value.author, function (err, name) { | ||
if (err) return cb(err) | ||
var repoLink = link([msg.value.content.repo]) | ||
var authorLink = link([msg.value.author], name) | ||
cb(null, '<p>' + timestamp(msg.value.timestamp) + '<br>' + | ||
authorLink + ' pushed to ' + repoLink + '</p>') | ||
}) | ||
function renderUpdate(msg, authorName, cb) { | ||
var repoLink = link([msg.value.content.repo]) | ||
var authorLink = link([msg.value.author], authorName) | ||
cb(null, '<section class="collapse">' + timestamp(msg.value.timestamp) + '<br>' + | ||
authorLink + ' pushed to ' + repoLink + '</section>') | ||
} | ||
@@ -232,35 +327,15 @@ | ||
function serveIndex() { | ||
return cat([ | ||
pull.values([ | ||
[200, { | ||
'Content-Type': 'text/html' | ||
}], | ||
'<!doctype html><html><head><meta charset=utf-8>', | ||
'<title>git ssb</title></head><body>', | ||
'<h1><a href="/">git ssb</a></h1>' | ||
]), | ||
renderTry(renderFeed()), | ||
pull.once('</body></html>') | ||
]) | ||
return serveTemplate('git ssb')(renderFeed()) | ||
} | ||
function serveUserPage(feedId) { | ||
return cat([ | ||
pull.values([ | ||
[200, { | ||
'Content-Type': 'text/html' | ||
}], | ||
'<!doctype html><html><head><meta charset=utf-8>', | ||
'<title>git ssb</title></head><body>', | ||
'<h1><a href="/">git ssb</a></h1>', | ||
]), | ||
return serveTemplate(feedId)(cat([ | ||
readOnce(function (cb) { | ||
about.getName(feedId, function (err, name) { | ||
cb(null, '<h2>' + link([feedId], name) + '</h2>' + | ||
'<p><small><code>' + feedId + '</code></small></p>') | ||
cb(null, '<h2>' + link([feedId], name) + | ||
'<code class="user-id">' + feedId + '</code></h2>') | ||
}) | ||
}), | ||
renderFeed(feedId), | ||
pull.once('</body></html>') | ||
]) | ||
])) | ||
} | ||
@@ -273,3 +348,3 @@ | ||
return readNext(function (cb) { | ||
ssbGit.getRepo(ssb, id, function (err, repo) { | ||
getRepo(id, function (err, repo) { | ||
if (err) { | ||
@@ -279,3 +354,3 @@ if (0) | ||
else | ||
cb(null, serveError(id, err)) | ||
cb(null, serveError(err)) | ||
return | ||
@@ -309,14 +384,7 @@ } | ||
function serveRepoNotFound(id, err) { | ||
return pull.values([ | ||
[404, { | ||
'Content-Type': 'text/html' | ||
}], | ||
'<!doctype html><html><head><meta charset=utf-8>', | ||
'<title>Repo not found</title></head><body>', | ||
'<h1><a href="/">git ssb</a></h1>', | ||
return serveTemplate('Repo not found', 404, pull.values([ | ||
'<h2>Repo not found</h2>', | ||
'<p>Repo ' + id + ' was not found</p>', | ||
'<pre>' + escapeHTML(err.stack) + '</pre>', | ||
'</body></html>' | ||
]) | ||
])) | ||
} | ||
@@ -326,28 +394,26 @@ | ||
var gitUrl = 'ssb://' + repo.id | ||
var gitLink = '<code>' + gitUrl + '</code>' | ||
var gitLink = '<input class="clone-url" readonly="readonly" ' + | ||
'value="' + gitUrl + '" size="' + gitUrl.length + '" ' + | ||
'onclick="this.select()"/>' | ||
return cat([ | ||
pull.values([ | ||
[200, { | ||
'Content-Type': 'text/html' | ||
}], | ||
'<!doctype html><html><head><meta charset=utf-8>' + | ||
'<title>git ssb</title></head><body>' + | ||
'<h1><a href="/">git ssb</a></h1>' + | ||
'<h2>' + link([repo.id]) + '</h2>' + | ||
'<p>git URL: ' + gitLink + '</p>']), | ||
readOnce(function (cb) { | ||
about.getName(repo.feed, function (err, name) { | ||
cb(null, '<p>Author: ' + link([repo.feed], name) + '</p>') | ||
}) | ||
}), | ||
pull.once( | ||
'<p>' + link([repo.id], 'Code') + ' ' + | ||
link([repo.id, 'activity'], 'Activity') + ' ' + | ||
link([repo.id, 'commits', branch], 'Commits') + '</p>' + | ||
'<hr/>' | ||
), | ||
renderTry(body), | ||
pull.once('<hr/></body></html>') | ||
]) | ||
var done = multicb({ pluck: 1, spread: true }) | ||
getRepoName(repo.id, done()) | ||
about.getName(repo.feed, done()) | ||
return readNext(function (cb) { | ||
done(function (err, repoName, authorName) { | ||
if (err) return cb(null, serveError(err)) | ||
cb(null, serveTemplate(repo.id)(cat([ | ||
pull.once( | ||
'<h2 class="repo-name">' + link([repo.feed], authorName) + ' / ' + | ||
link([repo.id], repoName) + '</h2>' + | ||
'<div class="repo-nav">' + link([repo.id], 'Code') + | ||
link([repo.id, 'activity'], 'Activity') + | ||
link([repo.id, 'commits', branch], 'Commits') + | ||
gitLink + | ||
'</div>'), | ||
body | ||
]))) | ||
}) | ||
}) | ||
} | ||
@@ -358,8 +424,10 @@ | ||
return renderRepoPage(repo, rev, cat([ | ||
pull.once('<h3>' + type + ': ' + rev + ' '), | ||
pull.once('<section><h3>' + type + ': ' + rev + ' '), | ||
revMenu(repo, rev), | ||
pull.once('</h3>'), | ||
type == 'Branch' && renderRepoLatest(repo, rev), | ||
pull.once('</section><section>'), | ||
renderRepoTree(repo, rev, path), | ||
renderRepoReadme(repo, rev, path), | ||
pull.once('</section>'), | ||
renderRepoReadme(repo, rev, path) | ||
])) | ||
@@ -395,3 +463,3 @@ } | ||
return '<p>' + timestamp(msg.value.timestamp) + '<br>' + | ||
return '<section class="collapse">' + timestamp(msg.value.timestamp) + '<br>' + | ||
(numObjects ? 'Pushed ' + numObjects + ' objects<br>' : '') + | ||
@@ -407,3 +475,3 @@ refs.map(function (update) { | ||
}).join('<br>') + | ||
'</p>' | ||
'</section>' | ||
} | ||
@@ -416,3 +484,3 @@ } | ||
return renderRepoPage(repo, branch, cat([ | ||
pull.once('<h3>Commits</h3><ul>'), | ||
pull.once('<h3>Commits</h3>'), | ||
pull( | ||
@@ -425,3 +493,3 @@ repo.readLog(branch), | ||
var treePath = [repo.id, 'tree', commit.id] | ||
cb(null, '<li>' + | ||
cb(null, '<section class="collapse">' + | ||
'<strong>' + link(commitPath, escapeHTML(commit.title)) + '</strong><br>' + | ||
@@ -432,7 +500,6 @@ '<code>' + commit.id + '</code> ' + | ||
escapeHTML(commit.committer.name) + ' committed on ' + commit.committer.date.toLocaleString() + | ||
'</li>') | ||
'</section>') | ||
}) | ||
}) | ||
), | ||
pull.once('</ul>') | ||
) | ||
])) | ||
@@ -479,3 +546,3 @@ } | ||
var commitPath = [repo.id, 'commit', commit.id] | ||
cb(null, '<p>' + | ||
cb(null, | ||
'Latest: <strong>' + link(commitPath, escapeHTML(commit.title)) + | ||
@@ -488,4 +555,3 @@ '</strong><br>' + | ||
escapeHTML(commit.author.name) + ' authored on ' + | ||
commit.author.date.toLocaleString() : '') + | ||
'</p>') | ||
commit.author.date.toLocaleString() : '')) | ||
}) | ||
@@ -505,5 +571,7 @@ }) | ||
function renderRepoTree(repo, rev, path) { | ||
var pathLinks = linkPath([repo.id, 'tree'], [rev].concat(path)) | ||
var pathLinks = path.length === 0 ? '' : | ||
': ' + linkPath([repo.id, 'tree'], [rev].concat(path)) | ||
return cat([ | ||
pull.once('<h3>Files: ' + pathLinks + '</h3><ul>'), | ||
pull.once('<h3>Files' + pathLinks + '</h3>' + | ||
'<ul class="files">'), | ||
pull( | ||
@@ -514,3 +582,4 @@ repo.readDir(rev, path), | ||
var filePath = [repo.id, type, rev].concat(path, file.name) | ||
return '<li>' + link(filePath) + '</li>' | ||
var fileName = (type == 'tree') ? file.name + '/' : file.name | ||
return '<li>' + link(filePath, fileName) + '</li>' | ||
}) | ||
@@ -540,9 +609,17 @@ ), | ||
cb(null, cat([ | ||
pull.once('<h4>' + escapeHTML(file.name) + '</h4>' + | ||
'<blockquote><pre>'), | ||
pull( | ||
obj.read, | ||
escapeHTMLStream() | ||
), | ||
pull.once('</pre></blockquote>') | ||
pull.once('<section><h4>' + escapeHTML(file.name) + '</h4><hr/>'), | ||
/\.md|\/.markdown/i.test(file.name) ? | ||
readOnce(function (cb) { | ||
pull(obj.read, pull.collect(function (err, bufs) { | ||
if (err) return cb(err) | ||
var buf = Buffer.concat(bufs, obj.length) | ||
cb(null, marked(buf.toString())) | ||
})) | ||
}) | ||
: cat([ | ||
pull.once('<pre>'), | ||
pull(obj.read, escapeHTMLStream()), | ||
pull.once('</pre>') | ||
]), | ||
pull.once('</section>') | ||
])) | ||
@@ -598,14 +675,7 @@ }) | ||
function serveBlobNotFound(repoId, err) { | ||
return pull.values([ | ||
[404, { | ||
'Content-Type': 'text/html' | ||
}], | ||
'<!doctype html><html><head><meta charset=utf-8>', | ||
'<title>Blob not found</title></head><body>', | ||
'<h1><a href="/">git ssb</a></h1>', | ||
return serveTemplate(400, 'Blob not found', pull.values([ | ||
'<h2>Blob not found</h2>', | ||
'<p>Blob in repo ' + link([repoId]) + ' was not found</p>', | ||
'<pre>' + escapeHTML(err.stack) + '</pre>', | ||
'</body></html>' | ||
]) | ||
'<pre>' + escapeHTML(err.stack) + '</pre>' | ||
])) | ||
} | ||
@@ -612,0 +682,0 @@ |
{ | ||
"name": "git-ssb-web", | ||
"version": "1.2.0", | ||
"version": "1.3.0", | ||
"description": "web server for browsing git repos on ssb", | ||
"bin": "server.js", | ||
"dependencies": { | ||
"multicb": "^1.2.1", | ||
"pull-cat": "^1.1.8", | ||
@@ -14,2 +15,3 @@ "pull-git-repo": "^0.2.0", | ||
"ssb-keys": "^4.0.10", | ||
"ssb-marked": "^0.5.4", | ||
"ssb-ref": "^2.2.2", | ||
@@ -16,0 +18,0 @@ "ssb-reconnect": "^0.1.0", |
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
25364
7
799
12
2
+ Addedmulticb@^1.2.1
+ Addedssb-marked@^0.5.4