├── .gitignore ├── README.md ├── client ├── app.js ├── index.js ├── lib │ ├── render.js │ └── ws-client.js ├── styles │ ├── global.js │ └── vars.js └── views │ └── main.js ├── hello.txt ├── index.js ├── lib └── kv.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | logs/* 2 | node_modules/* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hyperblog 2 | 3 | A portable, p2p blogging server and client. WIP. 4 | 5 | - [hyperkv](https://github.com/substack/hyperkv) provides conflict free, reproducable Key-Value blog index database between contributers local machines/devices, and the webserver. 6 | - [hyperlog](https://github.com/mafintosh/hyperlog) provides an append-only, p2p-syncable data store that backs the kv blog index. 7 | - [choo](https://github.com/yoshuawuyts/choo) provides universal server rendering and a client app. 8 | - [csjs](https://github.com/rtsao/csjs) provides css-in-js for a unified JS/CSS workflow. 9 | - [bankai](https://github.com/yoshuawuyts/bankai) provides buildstep free bundling of js and server rendered html 10 | - [hyperserv](https://github.com/bcomnes/hyperserv) provides a simple server and router. 11 | - [mutliplex-rpc](https://github.com/substack/multiplex-rpc) + [websocket-stream](https://github.com/maxogden/websocket-stream) + [reconnect-core](https://www.npmjs.com/package/reconnect-core) provides robust duplex rpc with streams between client and server 12 | -------------------------------------------------------------------------------- /client/app.js: -------------------------------------------------------------------------------- 1 | var choo = require('choo') 2 | var mainView = require('./views/main') 3 | var app = choo() 4 | 5 | app.model({ 6 | namespace: 'message', 7 | state: { 8 | server: 'rehydration has kicked in, server data was tossed', 9 | client: 'hello client!' 10 | } 11 | }) 12 | 13 | app.router((route) => [ 14 | route('/', mainView) 15 | ]) 16 | 17 | module.exports = app 18 | -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | var app = require('./app') 2 | // var getCss = require('csjs/get-css') 3 | var insertCSS = require('insert-css') 4 | var RPC = require('multiplex-rpc') 5 | var pump = require('pump') 6 | var url = require('url') 7 | var BrowserStdout = require('browser-stdout') 8 | var ws = require('./lib/ws-client') 9 | 10 | var globalCss = require('./styles/global') 11 | var address = 'ws://' + url.parse(window.location.href).host 12 | // insertCSS(getCss(globalCss)) 13 | insertCSS(globalCss) 14 | app.start('#app-root') 15 | 16 | var activeRPC 17 | var re = ws(function (stream) { 18 | var rpc = activeRPC = RPC() 19 | pump(rpc, stream, rpc, function (err) { 20 | if (err) console.error(err) 21 | }) 22 | 23 | var client = rpc.wrap([ 'hello:s', 'foo' ]) 24 | 25 | client.foo('bah', function (err, data) { 26 | if (err) console.error(err) 27 | if (data) console.log(data) 28 | }) 29 | 30 | pump(client.hello(), BrowserStdout(), function (err) { 31 | if (err) console.error(err) 32 | }) 33 | }).connect(address) 34 | 35 | ws.logger(re) 36 | 37 | window.addEventListener('beforeunload', function (event) { 38 | try { 39 | ws.reconnect = false 40 | activeRPC.end() 41 | } catch (e) { 42 | console.error(e) 43 | } 44 | }) 45 | -------------------------------------------------------------------------------- /client/lib/render.js: -------------------------------------------------------------------------------- 1 | 2 | app.model({ 3 | namespace: 'message', 4 | state: { 5 | server: 'rehydration has kicked in, server data was tossed', 6 | client: 'hello client!' 7 | } 8 | -------------------------------------------------------------------------------- /client/lib/ws-client.js: -------------------------------------------------------------------------------- 1 | var websocket = require('websocket-stream') 2 | var inject = require('reconnect-core') 3 | 4 | var ws = inject(function () { 5 | // arguments are what you passed to .connect 6 | // this is the reconnect instance 7 | var address = arguments[0] 8 | var protocols = arguments[1] 9 | var options = arguments[3] 10 | var ws = websocket(address, protocols, options) 11 | return ws 12 | }) 13 | 14 | module.exports = ws 15 | 16 | function logger (re) { 17 | re.on('connect', function (con) { 18 | console.log('connected') 19 | }) 20 | .on('reconnect', function (n, delay) { 21 | console.log('reconnect: n(' + n + ') ' + delay) 22 | }) 23 | .on('disconnect', function (err) { 24 | console.log('disconnected') 25 | if (err instanceof Error) console.error(err) 26 | }) 27 | .on('error', function (err) { 28 | if (err instanceof Error) { 29 | switch (err) { 30 | default: 31 | console.error(err) 32 | break 33 | } 34 | } 35 | }) 36 | } 37 | 38 | module.exports.logger = logger 39 | -------------------------------------------------------------------------------- /client/styles/global.js: -------------------------------------------------------------------------------- 1 | var vars = require('./vars') 2 | var theme = vars.theme 3 | var colors = vars.colors 4 | var fonts = vars.fonts 5 | // var csjs = require('csjs') 6 | // var css = csjs.noScope 7 | 8 | var globalCss = ` 9 | html { 10 | color: ${theme.baseColor}; 11 | background-color: ${theme.background}; 12 | -webkit-font-smoothing: antialiased; 13 | } 14 | 15 | body { 16 | margin: 0px; 17 | font-family: ${fonts.monoFont}; 18 | } 19 | 20 | ul { 21 | list-style-type: none; 22 | } 23 | 24 | ul li:before { 25 | content: "-"; 26 | position: absolute; 27 | margin-left: -1.1em; 28 | } 29 | 30 | a { transition: background-color 200ms, color 200ms; text-decoration: none; } 31 | a:link { color: ${theme.link}; background-color: ${colors.black}; } /* unvisited links */ 32 | a:visited { color: ${theme.link}; } /* visited links */ 33 | a:hover { color: ${colors.black}; background-color: ${colors.red}; } /* user hovers */ 34 | a:visited:hover { color: ${colors.white}; background-color: ${colors.black}; } 35 | a:active { color: ${colors.red}; background-color: transparent; } /* active links */ 36 | 37 | main { 38 | margin: 1.2em 1.2em; 39 | word-wrap: break-word; 40 | } 41 | 42 | @media only screen and (min-device-height: 480px) { 43 | body { max-width: 100%; } 44 | } 45 | 46 | @media only screen and (-webkit-min-device-pixel-ratio: 2) {} 47 | ` 48 | 49 | module.exports = globalCss 50 | -------------------------------------------------------------------------------- /client/styles/vars.js: -------------------------------------------------------------------------------- 1 | var colors = exports.colors = { 2 | gray: 'hsla(212, 27%, 26%, 1.0)', 3 | darkBlue: 'hsla(212, 21%, 10%, 1.0)', 4 | blue: 'hsla(202, 65%, 42%, 1.0)', 5 | lightBlue: 'hsla(216, 44%, 78%, 1.0)', 6 | white: 'hsla(0, 0%, 100%, 1.0)', 7 | black: 'hsla(0, 0%, 0%, 1.0)', 8 | red: 'hsla(352, 100%, 50%, 1.0)', 9 | neonGreen: 'hsla(72, 87%, 54%, 1.0)', 10 | orange: 'hsla(30, 95%, 60%, 1.0)' 11 | } 12 | 13 | exports.theme = { 14 | rss: colors.orange, 15 | baseColor: colors.lightBlue, 16 | link: colors.blue, 17 | background: colors.darkBlue 18 | } 19 | 20 | exports.fonts = { 21 | monoFont: [ 22 | 'Menlo', 23 | 'Monaco', 24 | 'Ubuntu Mono', 25 | 'Consolas', 26 | 'Lucida Console', 27 | 'Liberation Mono', 28 | 'DejaVu Sans Mono', 29 | 'Bitstream Vera Sans Mono', 30 | 'Courier New', 31 | 'monospace', 32 | 'serif' 33 | ], 34 | sansFont: [ 35 | 'Helvetica Neue', 36 | 'Helvetica', 37 | 'Arial', 38 | 'Lucida Grande', 39 | 'sans-serif' 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /client/views/main.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const html = require('choo/html') 3 | 4 | module.exports = function (state, prev, send) { 5 | const serverMessage = state.message.server 6 | const clientMessage = state.message.client 7 | 8 | assert.equal(typeof serverMessage, 'string', 'server should be a string') 9 | assert.equal(typeof clientMessage, 'string', 'client should be a string') 10 | 11 | return html` 12 |
13 |

server message: ${serverMessage}

14 |

client message: ${clientMessage}

15 |

${` 16 | The first message is passed in by the server on compile time, 17 | the second message was set by the client. 18 | The more static the data you pass in, the more cachable your site 19 | becomes (and thus performant). Try and keep the amount of properties 20 | you pass in on the server to a minimum for most applications - it'll 21 | make life a lot easier in the long run, hah. 22 | `}

23 |
24 | ` 25 | } 26 | -------------------------------------------------------------------------------- /hello.txt: -------------------------------------------------------------------------------- 1 | hello from the fs system 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var JSONStream = require('JSONStream') 2 | var path = require('path') 3 | var Hyperserv = require('hyperserv') 4 | var minimist = require('minimist') 5 | var makeKV = require('./lib/kv') 6 | var websocket = require('websocket-stream') 7 | var RPC = require('multiplex-rpc') 8 | var fs = require('fs') 9 | var pump = require('pump') 10 | var argv = minimist(process.argv.slice(2), { 11 | alias: { p: 'port' }, 12 | default: { port: 8000 } 13 | }) 14 | var hyperstream = require('hyperstream') 15 | var browserify = require('browserify') 16 | var bankai = require('bankai')() 17 | var staticClient = require('./client/app') 18 | // var makeRoute = Hyperserv.makeRoute 19 | var js = bankai.js(browserify, require.resolve('./client'), {debug: true}) 20 | var html = bankai.html({ favicon: false, css: false }) 21 | 22 | // var spy = require('through2-spy') 23 | // var streamSpy = spy.ctor({wantStrings: true}, chunk => console.log(chunk)) 24 | 25 | var kv = makeKV(path.join(__dirname, 'logs')) 26 | 27 | var app = new Hyperserv() 28 | // var wss 29 | websocket.createServer({server: app.httpServer}, handle) 30 | 31 | // Set up routes 32 | app.router.set('/', function (req, res, opts, cb) { 33 | res.setHeader('Content-Type', 'text/html; charset=utf-8') 34 | const state = { message: { server: 'hello server!' } } 35 | const inner = staticClient.toString(req.url, state) 36 | const hs = hyperstream({ 'body': { _appendHtml: inner } }) 37 | pump(html(req, res), hs, res, cb) 38 | }) 39 | 40 | app.router.set('/bundle.js', function (req, res, opts, cb) { 41 | pump(js(req, res), res, cb) 42 | }) 43 | 44 | app.router.set('/all', function (req, res, opts, cb) { 45 | kv.createReadStream().pipe(JSONStream.stringify()).pipe(res) 46 | }) 47 | 48 | app.httpServer.listen(argv.port) 49 | 50 | function handle (stream) { 51 | var rpc = RPC({ 52 | foo: function (bar, cb) { 53 | var data = { foo: bar } 54 | return cb(null, data) 55 | }, 56 | hello: function () { 57 | return fs.createReadStream(path.join(__dirname, '/hello.txt')) 58 | } 59 | }) 60 | 61 | pump(stream, rpc, stream, function (err) { 62 | if (err) { 63 | switch (err.message) { 64 | case 'premature close': 65 | console.log('premature close') 66 | break 67 | default: 68 | console.log(err) 69 | } 70 | } 71 | }) 72 | } 73 | -------------------------------------------------------------------------------- /lib/kv.js: -------------------------------------------------------------------------------- 1 | var level = require('level') 2 | var hyperlog = require('hyperlog') 3 | var hyperkv = require('hyperkv') 4 | var sub = require('subleveldown') 5 | var path = require('path') 6 | var mkdirp = require('mkdirp') 7 | 8 | function createKV (logPath) { 9 | if (!logPath) throw new Error('Missing logPath') 10 | mkdirp.sync(logPath) 11 | var hdb = level(path.join(logPath, 'hyperlog')) 12 | var idb = level(path.join(logPath, 'index')) 13 | 14 | var log = hyperlog(hdb, { valueEncoding: 'json' }) 15 | var db = sub(idb, 'kv') 16 | 17 | var kv = hyperkv({ 18 | log: log, 19 | db: db 20 | }) 21 | 22 | return kv 23 | } 24 | 25 | module.exports = createKV 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hyperblog", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon index.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "JSONStream": "^1.2.1", 14 | "bankai": "^3.3.1", 15 | "browser-stdout": "^1.3.0", 16 | "browserify": "^13.3.0", 17 | "bufferutil": "^1.3.0", 18 | "choo": "^3.3.0", 19 | "csjs": "^1.0.6", 20 | "flush-write-stream": "^1.0.2", 21 | "hyperkv": "^1.7.1", 22 | "hyperlog": "^4.11.0", 23 | "hyperserv": "^4.0.1", 24 | "hyperstream": "^1.2.2", 25 | "level": "^1.5.0", 26 | "mkdirp": "^0.5.1", 27 | "multiplex-rpc": "^1.0.1", 28 | "pump": "^1.0.2", 29 | "reconnect-core": "^1.3.0", 30 | "subleveldown": "^2.1.0", 31 | "utf-8-validate": "^1.2.2", 32 | "websocket-stream": "^3.3.0" 33 | }, 34 | "devDependencies": { 35 | "@tap-format/spec": "^0.2.0", 36 | "through2-spy": "^2.0.0", 37 | "changelog-maker": "^2.2.4", 38 | "dependency-check": "^2.7.0", 39 | "nodemon": "^1.11.0", 40 | "request": "^2.79.0", 41 | "snazzy": "^5.0.0", 42 | "standard": "^8.4.0", 43 | "tape": "^4.6.2" 44 | } 45 | } 46 | --------------------------------------------------------------------------------