git ssb

0+

ev / mvp



Commit 46ef9e90772ab5e16ae211a9cfab1ea45fcaa806

initial commit

Ev Bogue committed on 7/22/2018, 2:50:13 PM

Files changed

.gitignoreadded
bin.jsadded
config.jsadded
config/index.jsadded
config/inject.jsadded
index.jsadded
keys.jsadded
manifest.jsonadded
package.jsonadded
readme.mdadded
render.jsadded
scuttlebot.jsadded
style.cssadded
style.css.jsonadded
style.jsadded
tools.jsadded
views.jsadded
.gitignoreView
@@ -1,0 +1,2 @@
1 +build
2 +node_modules
bin.jsView
@@ -1,0 +1,114 @@
1 +var fs = require('fs')
2 +var path = require('path')
3 +var ssbKeys = require('ssb-keys')
4 +var stringify = require('pull-stringify')
5 +var open = require('opn')
6 +var home = require('os-homedir')()
7 +var nonPrivate = require('non-private-ip')
8 +var muxrpcli = require('muxrpcli')
9 +
10 +var SEC = 1e3
11 +var MIN = 60*SEC
12 +
13 +var network = 'ssb'
14 +//var network = 'decent'
15 +//var network = 'testnet'
16 +
17 +var config = require('./config/inject')(network)
18 +
19 +config.keys = ssbKeys.loadOrCreateSync(path.join(config.path, 'secret'))
20 +
21 +var mvdClient = fs.readFileSync(path.join('./build/index.html'))
22 +
23 +var manifestFile = path.join(config.path, 'manifest.json')
24 +
25 +var argv = process.argv.slice(2)
26 +var i = argv.indexOf('--')
27 +var conf = argv.slice(i+1)
28 +argv = ~i ? argv.slice(0, i) : argv
29 +
30 +if (argv[0] == 'server') {
31 +
32 + var createSbot = require('scuttlebot')
33 + .use(require('scuttlebot/plugins/master'))
34 + .use(require('scuttlebot/plugins/gossip'))
35 + .use(require('scuttlebot/plugins/replicate'))
36 + .use(require('ssb-friends'))
37 + .use(require('ssb-blobs'))
38 + .use(require('ssb-links'))
39 + .use(require('ssb-ebt'))
40 + .use(require('scuttlebot/plugins/invite'))
41 + .use(require('scuttlebot/plugins/local'))
42 + .use(require('decent-ws'))
43 + .use({
44 + name: 'serve',
45 + version: '1.0.0',
46 + init: function (sbot) {
47 + sbot.ws.use(function (req, res, next) {
48 + var send = config
49 + delete send.keys // very important to keep this, as it removes the server keys from the config before broadcast
50 + send.address = sbot.ws.getAddress()
51 + sbot.invite.create({modern: true}, function (err, cb) {
52 + send.invite = cb
53 + })
54 + if(req.url == '/')
55 + res.end(mvdClient)
56 + if(req.url == '/get-config')
57 + res.end(JSON.stringify(send))
58 + else next()
59 + })
60 + }
61 + })
62 +
63 + open('http://localhost:' + config.ws.port, {wait: false})
64 +
65 + var server = createSbot(config)
66 +
67 + fs.writeFileSync(manifestFile, JSON.stringify(server.getManifest(), null, 2))
68 +} else {
69 +
70 + var manifest
71 + try {
72 + manifest = JSON.parse(fs.readFileSync(manifestFile))
73 + } catch (err) {
74 + throw explain(err,
75 + 'no manifest file'
76 + + '- should be generated first time server is run'
77 + )
78 + }
79 +
80 + // connect
81 + require('ssb-client')(config.keys, {
82 + manifest: manifest,
83 + port: config.port,
84 + host: config.host||'localhost',
85 + caps: config.caps,
86 + key: config.key || config.keys.id
87 + }, function (err, rpc) {
88 + if(err) {
89 + if (/could not connect/.test(err.message)) {
90 + console.log('Error: Could not connect to the scuttlebot server.')
91 + console.log('Use the "server" command to start it.')
92 + if(config.verbose) throw err
93 + process.exit(1)
94 + }
95 + throw err
96 + }
97 +
98 + // add some extra commands
99 + manifest.version = 'async'
100 + manifest.config = 'sync'
101 + rpc.version = function (cb) {
102 + console.log(require('./package.json').version)
103 + cb()
104 + }
105 + rpc.config = function (cb) {
106 + console.log(JSON.stringify(config, null, 2))
107 + cb()
108 + }
109 +
110 + // run commandline flow
111 + muxrpcli(argv, manifest, rpc, config.verbose)
112 + })
113 +}
114 +
config.jsView
@@ -1,0 +1,34 @@
1 +var http = require('http')
2 +
3 +module.exports = function () {
4 + var host = window.location.origin
5 +
6 + function getConfig () {
7 + http.get(host + '/get-config', function (res) {
8 + res.on('data', function (data, remote) {
9 + var config = data
10 + localStorage[host] = config
11 + })
12 + })
13 + }
14 +
15 + if (localStorage[host]) {
16 + var config = JSON.parse(localStorage[host])
17 + getConfig()
18 + } else {
19 + getConfig()
20 + setTimeout(function () {
21 + location.reload()
22 + }, 1000)
23 + }
24 +
25 + config.blobsUrl = host + '/blobs/get/'
26 + config.emojiUrl = host + '/img/emoji/'
27 +
28 + if (config.ws.remote)
29 + config.remote = config.ws.remote
30 + else
31 + config.remote = config.address
32 +
33 + return config
34 +}
config/index.jsView
@@ -1,0 +1,1 @@
1 +module.exports = require('./inject')()
config/inject.jsView
@@ -1,0 +1,68 @@
1 +var path = require('path')
2 +var home = require('os-homedir')
3 +var nonPrivate = require('non-private-ip')
4 +var merge = require('deep-extend')
5 +var id = require('ssb-keys')
6 +var RC = require('rc')
7 +var SEC = 1e3
8 +var MIN = 60*SEC
9 +
10 +module.exports = function (name, override) {
11 + console.log('Using the ' + name + ' config')
12 +
13 + var network
14 +
15 + if (name === 'ssb') {
16 + network = {
17 + port: 8008,
18 + ws: {
19 + port: 8080
20 + },
21 + caps: {
22 + shs: '1KHLiKZvAvjbY1ziZEHMXawbCEIM6qwjCDm3VYRan/s=',
23 + sign: null
24 + }
25 + }
26 + }
27 +
28 + if (name === 'testnet') {
29 + network = {
30 + port: 9999,
31 + ws: {
32 + port: 1337
33 + },
34 + caps: {
35 + shs: 'sR74I0+OW6LBYraQQ2YtFtqV5Ns77Tv5DyMfyWbrlpI=',
36 + sign: null
37 + }
38 + }
39 + }
40 +
41 + var HOME = home() || 'browser' //most probably browser
42 +
43 + return RC(name, merge(network, {
44 + name: name,
45 + //standard stuff that probably doesn't need to change below
46 + host: nonPrivate.v4 || '',
47 + timeout: 0,
48 + allowPrivate: true,
49 + pub: true,
50 + local: true,
51 + friends: {
52 + dunbar: 150,
53 + hops: 3
54 + },
55 + gossip: {
56 + connections: 3
57 + },
58 + path: path.join(HOME, '.' + name),
59 + timers: {
60 + connection: 0,
61 + reconnect: 5*SEC,
62 + ping: 5*MIN,
63 + handshake: 5*SEC
64 + },
65 + master: [],
66 + party: true //disable quotas
67 + }, override || {}))
68 +}
index.jsView
@@ -1,0 +1,29 @@
1 +var h = require('hyperscript')
2 +var route = require('./views')
3 +
4 +var id = require('./keys').id
5 +
6 +document.head.appendChild(h('style', require('./style.css.json')))
7 +
8 +var screen = h('div#screen')
9 +
10 +var search = h('input.search', {placeholder: 'Search'})
11 +
12 +var nav = h('div.navbar',
13 + h('div.internal',
14 + h('li', h('a', {href: '#' + id}, id)),
15 + h('li', h('a', {href: '#' }, 'Stream')),
16 + )
17 +)
18 +
19 +document.body.appendChild(nav)
20 +document.body.appendChild(screen)
21 +route()
22 +
23 +window.onhashchange = function () {
24 + var oldscreen = document.getElementById('screen')
25 + var newscreen = h('div#screen')
26 + oldscreen.parentNode.replaceChild(newscreen, oldscreen)
27 + route()
28 +}
29 +
keys.jsView
@@ -1,0 +1,6 @@
1 +
2 +var config = require('./config')()
3 +var ssbKeys = require('ssb-keys')
4 +var path = require('path')
5 +
6 +module.exports = ssbKeys.loadOrCreateSync(path.join(config.caps.shs + '/secret'))
manifest.jsonView
@@ -1,0 +1,63 @@
1 +{
2 + "auth": "async",
3 + "address": "sync",
4 + "manifest": "sync",
5 + "get": "async",
6 + "createFeedStream": "source",
7 + "createLogStream": "source",
8 + "messagesByType": "source",
9 + "createHistoryStream": "source",
10 + "createUserStream": "source",
11 + "links": "source",
12 + "relatedMessages": "async",
13 + "add": "async",
14 + "publish": "async",
15 + "getAddress": "sync",
16 + "getLatest": "async",
17 + "latest": "source",
18 + "latestSequence": "async",
19 + "whoami": "sync",
20 + "usage": "sync",
21 + "gossip": {
22 + "peers": "sync",
23 + "add": "sync",
24 + "ping": "duplex",
25 + "connect": "async",
26 + "changes": "source",
27 + "reconnect": "sync"
28 + },
29 + "friends": {
30 + "all": "async",
31 + "hops": "async",
32 + "createFriendStream": "source",
33 + "get": "sync"
34 + },
35 + "replicate": {
36 + "changes": "source"
37 + },
38 + "invite": {
39 + "create": "async",
40 + "accept": "async",
41 + "use": "async"
42 + },
43 + "block": {
44 + "isBlocked": "sync"
45 + },
46 + "private": {
47 + "publish": "async",
48 + "unbox": "sync"
49 + },
50 + "blobs": {
51 + "get": "source",
52 + "add": "sink",
53 + "ls": "source",
54 + "has": "async",
55 + "size": "async",
56 + "meta": "async",
57 + "want": "async",
58 + "push": "async",
59 + "changes": "source",
60 + "createWants": "source"
61 + },
62 + "ws": {},
63 +}
package.jsonView
@@ -1,0 +1,46 @@
1 +{
2 + "name": "mvd",
3 + "version": "1.10.1",
4 + "description": "minimum viable decent",
5 + "main": "index.js",
6 + "scripts": {
7 + "start": "node bin server",
8 + "decent": "node bin server --appname=decent",
9 + "ssb": "node bin server --appname=ssb",
10 + "testnet": "node bin server --appname=testnet",
11 + "build": "node style.js && mkdir -p build && browserify index.js | indexhtmlify > build/index.html"
12 + },
13 + "devDependencies": {
14 + "browserify": "^16.2.2",
15 + "indexhtmlify": "^1.3.1"
16 + },
17 + "author": "Ev Bogue <ev@evbogue.com>",
18 + "license": "MIT",
19 + "dependencies": {
20 + "decent-ws": "1.0.4",
21 + "deep-extend": "^0.6.0",
22 + "human-time": "0.0.1",
23 + "hyperloadmore": "^1.1.0",
24 + "hyperscript": "^2.0.2",
25 + "hyperscroll": "^1.0.0",
26 + "muxrpcli": "^1.1.0",
27 + "non-private-ip": "^1.4.3",
28 + "opn": "^5.3.0",
29 + "os-homedir": "^1.0.2",
30 + "pull-more": "^1.1.0",
31 + "pull-reconnect": "0.0.3",
32 + "pull-stream": "^3.6.8",
33 + "pull-stringify": "^2.0.0",
34 + "rc": "^1.2.7",
35 + "scuttlebot": "^11.3.0",
36 + "ssb-blobs": "^1.1.5",
37 + "ssb-client": "^4.5.7",
38 + "ssb-ebt": "^5.1.5",
39 + "ssb-feed": "^2.3.0",
40 + "ssb-friends": "^2.4.0",
41 + "ssb-keys": "^7.0.16",
42 + "ssb-links": "^3.0.3",
43 + "ssb-markdown": "^3.6.0",
44 + "ssb-ref": "^2.11.1"
45 + }
46 +}
readme.mdView
@@ -1,0 +1,12 @@
1 +# mvp
2 +
3 +### Minimum Viable Phoenix
4 +
5 +This is for my up-coming series on how to code clients using ssb.
6 +
7 +### history
8 +
9 +`mvp` is a simplified fork of [`mvd`](%NPNNvcnTMZUFZSWl/2Z4XX+YSdqsqOhyPacp+lgpQUw=.sha256)
10 +
11 +---
12 +MIT
render.jsView
@@ -1,0 +1,16 @@
1 +var h = require('hyperscript')
2 +var tools = require('./tools')
3 +
4 +function hash () {
5 + return window.location.hash.substring(1)
6 +}
7 +
8 +module.exports = function (msg) {
9 + var message = h('div.message#' + msg.key.substring(0, 44))
10 +
11 + //FULL FALLBACK
12 + message.appendChild(tools.header(msg))
13 + message.appendChild(h('pre', tools.rawJSON(msg.value.content)))
14 +
15 + return message
16 +}
scuttlebot.jsView
@@ -1,0 +1,75 @@
1 +var pull = require('pull-stream')
2 +var ssbKeys = require('ssb-keys')
3 +var ref = require('ssb-ref')
4 +var reconnect = require('pull-reconnect')
5 +
6 +var config = require('./config')()
7 +var createClient = require('ssb-client')
8 +var createFeed = require('ssb-feed')
9 +
10 +var keys = require('./keys')
11 +
12 +var CACHE = {}
13 +
14 +var rec = reconnect(function (isConn) {
15 + function notify (value) {
16 + isConn(value)
17 + }
18 +
19 + createClient(keys, {
20 + manifest: require('./manifest.json'),
21 + remote: config.remote,
22 + caps: config.caps
23 + }, function (err, _sbot) {
24 + if(err)
25 + return notify(err)
26 +
27 + sbot = _sbot
28 + sbot.on('closed', function () {
29 + sbot = null
30 + notify(new Error('closed'))
31 + })
32 +
33 + notify()
34 + })
35 +})
36 +
37 +var internal = {
38 + getLatest: rec.async(function (id, cb) {
39 + sbot.getLatest(id, cb)
40 + }),
41 + add: rec.async(function (msg, cb) {
42 + sbot.add(msg, cb)
43 + })
44 +}
45 +
46 +var feed = createFeed(internal, keys, {remote: true})
47 +
48 +module.exports = {
49 + createLogStream: rec.source(function (opts) {
50 + return pull(
51 + sbot.createLogStream(opts),
52 + pull.through(function (e) {
53 + CACHE[e.key] = CACHE[e.key] || e.value
54 + })
55 + )
56 + }),
57 + userStream: rec.source(function (config) {
58 + return pull(
59 + sbot.createUserStream(config),
60 + pull.through(function (e) {
61 + CACHE[e.key] = CACHE[e.key] || e.value
62 + })
63 + )
64 + }),
65 + get: rec.async(function (key, cb) {
66 + if('function' !== typeof cb)
67 + throw new Error('cb must be function')
68 + if(CACHE[key]) cb(null, CACHE[key])
69 + else sbot.get(key, function (err, value) {
70 + if(err) return cb(err)
71 + cb(null, CACHE[key] = value)
72 + })
73 + })
74 +}
75 +
style.cssView
@@ -1,0 +1,87 @@
1 +body {
2 + margin: 0;
3 +}
4 +
5 +#screen {
6 + position: absolute;
7 + top: 35px;
8 + bottom: 0px;
9 + left: 0px;
10 + right: 0px;
11 +}
12 +
13 +.hyperscroll {
14 + width: 100%;
15 +}
16 +
17 +.header {
18 + padding-bottom: .7em;
19 + border-bottom: 1px solid #ccc;
20 +}
21 +
22 +.navbar {
23 + border-bottom: 1px solid #ccc;
24 + width: 100%;
25 + position: fixed;
26 + z-index: 1000;
27 + margin: 0;
28 + padding-top: .3em;
29 + padding-bottom: .3em;
30 + left: 0; right: 0;
31 + top: 0;
32 +}
33 +
34 +.navbar .internal {
35 + max-width: 97%;
36 + margin-left: auto;
37 + margin-right: auto;
38 +}
39 +
40 +.navbar li {
41 + margin-top: .3em;
42 + float: left;
43 + margin-right: .6em;
44 + margin-left: .3em;
45 + list-style-type: none;
46 +}
47 +
48 +.content {
49 + max-width: 680px;
50 + margin-left: auto;
51 + margin-right: auto;
52 +}
53 +
54 +.hyperscroll > .content {
55 + max-width: 680px;
56 + margin-left: auto;
57 + margin-right: auto;
58 +}
59 +
60 +.message img, .message video {
61 + max-width: 100%;
62 +}
63 +
64 +.timestamp, .votes {
65 + float: right;
66 +}
67 +
68 +pre {
69 + width: 100%;
70 + display: block;
71 +}
72 +
73 +code {
74 + display: inline-block;
75 + vertical-align: bottom;
76 +}
77 +
78 +code, pre {
79 +overflow: auto;
80 +word-break: break-all;
81 +word-wrap: break-word;
82 +white-space: pre;
83 +white-space: -moz-pre-wrap;
84 +white-space: pre-wrap;
85 +white-space: pre\9;
86 +}
87 +
style.css.jsonView
@@ -1,0 +1,1 @@
1 +"body {\n margin: 0;\n}\n\n#screen {\n position: absolute;\n top: 35px;\n bottom: 0px;\n left: 0px;\n right: 0px;\n}\n\n.hyperscroll {\n width: 100%;\n}\n\n.header {\n padding-bottom: .7em;\n border-bottom: 1px solid #ccc;\n}\n\n.navbar {\n border-bottom: 1px solid #ccc;\n width: 100%;\n position: fixed;\n z-index: 1000;\n margin: 0;\n padding-top: .3em;\n padding-bottom: .3em;\n left: 0; right: 0;\n top: 0;\n}\n\n.navbar .internal {\n max-width: 97%;\n margin-left: auto;\n margin-right: auto;\n}\n\n.navbar li {\n margin-top: .3em;\n float: left;\n margin-right: .6em;\n margin-left: .3em;\n list-style-type: none;\n}\n\n.content {\n max-width: 680px;\n margin-left: auto;\n margin-right: auto;\n}\n\n.hyperscroll > .content {\n max-width: 680px;\n margin-left: auto;\n margin-right: auto;\n}\n\n.message img, .message video {\n max-width: 100%;\n}\n\n.timestamp, .votes {\n float: right;\n}\n\npre {\n width: 100%;\n display: block;\n}\n\ncode {\n display: inline-block;\n vertical-align: bottom;\n}\n\ncode, pre {\noverflow: auto;\nword-break: break-all;\nword-wrap: break-word;\nwhite-space: pre;\nwhite-space: -moz-pre-wrap;\nwhite-space: pre-wrap;\nwhite-space: pre\\9;\n}\n\n"
style.jsView
@@ -1,0 +1,8 @@
1 +var fs = require('fs')
2 +var path = require('path')
3 +
4 +fs.writeFileSync(
5 + path.join(__dirname, 'style.css.json'),
6 + JSON.stringify(fs.readFileSync(path.join(__dirname, 'style.css'), 'utf8'))
7 +)
8 +
tools.jsView
@@ -1,0 +1,46 @@
1 +var h = require('hyperscript')
2 +var human = require('human-time')
3 +var ref = require('ssb-ref')
4 +
5 +var markdown = require('ssb-markdown')
6 +var config = require('./config')()
7 +
8 +module.exports.timestamp = function (msg, edited) {
9 + var timestamp = h('span.timestamp', h('a', {href: '#' + msg.key}, human(new Date(msg.value.timestamp))))
10 + return timestamp
11 +}
12 +
13 +module.exports.header = function (msg) {
14 + var header = h('div.header')
15 +
16 + header.appendChild(h('span.avatar',
17 + h('a', {href: '#' + msg.value.author},
18 + msg.value.author
19 + )
20 + )
21 + )
22 +
23 + header.appendChild(exports.timestamp(msg))
24 + return header
25 +}
26 +
27 +module.exports.rawJSON = function (obj) {
28 + return JSON.stringify(obj, null, 2)
29 + .split(/([%@&][a-zA-Z0-9\/\+]{43}=*\.[\w]+)/)
30 + .map(function (e) {
31 + if(ref.isMsg(e) || ref.isFeed(e) || ref.isBlob(e)) {
32 + return h('a', {href: '#' + e}, e)
33 + }
34 + return e
35 + })
36 +}
37 +
38 +
39 +module.exports.markdown = function (msg, md) {
40 + return {innerHTML: markdown.block(msg, {toUrl: function (url, image) {
41 + if(url[0] == '%' || url[0] == '@') return '#' + url
42 + if(!image) return url
43 + if(url[0] !== '&') return url
44 + return config.blobsUrl + url
45 + }})}
46 +}
views.jsView
@@ -1,0 +1,71 @@
1 +var pull = require('pull-stream')
2 +var sbot = require('./scuttlebot')
3 +var hyperscroll = require('hyperscroll')
4 +var More = require('pull-more')
5 +var stream = require('hyperloadmore/stream')
6 +var h = require('hyperscript')
7 +var render = require('./render')
8 +var ref = require('ssb-ref')
9 +var keys = require('./keys')
10 +
11 +var logStream = function () {
12 + var content = h('div.content')
13 + var screen = document.getElementById('screen')
14 + screen.appendChild(hyperscroll(content))
15 +
16 + function createStream (opts) {
17 + return pull(
18 + More(sbot.createLogStream, opts),
19 + pull.map(function (msg) {
20 + return render(msg)
21 + })
22 + )
23 + }
24 +
25 + pull(
26 + createStream({old: false, limit: 100}),
27 + stream.top(content)
28 + )
29 +
30 + pull(
31 + createStream({reverse: true, live: false, limit: 100}),
32 + stream.bottom(content)
33 + )
34 +}
35 +
36 +var userStream = function (src) {
37 + var content = h('div.content')
38 + var screen = document.getElementById('screen')
39 + screen.appendChild(hyperscroll(content))
40 + function createStream (opts) {
41 + return pull(
42 + More(sbot.userStream, opts, ['value', 'sequence']),
43 + pull.map(function (msg) {
44 + return render(msg)
45 + })
46 + )
47 + }
48 +
49 + pull(
50 + createStream({old: false, limit: 10, id: src}),
51 + stream.top(content)
52 + )
53 +
54 + pull(
55 + createStream({reverse: true, live: false, limit: 10, id: src}),
56 + stream.bottom(content)
57 + )
58 +}
59 +
60 +function hash () {
61 + return window.location.hash.substring(1)
62 +}
63 +
64 +module.exports = function () {
65 + var src = hash()
66 + if (ref.isFeed(src)) {
67 + userStream(src)
68 + } else {
69 + logStream()
70 + }
71 +}

Built with git-ssb-web