[
  {
    "path": ".gitignore",
    "content": "node_modules\n.*.swp\nbundle.js\n\n\n"
  },
  {
    "path": "README.md",
    "content": "# hyperpad\n\n> A peer-to-peer collaborative text editor for humans and communities.\n\n## What is it?\n\nHyperpad is a free, open source, peer-to-peer text editor for people and their\ncommunities. Authors control who gets access, and data is hosted by the peers\nwho are interested in it.\n\n## Current Status\n\nI've paused work on the web client in favour of building out an Electron-based\nnative client, [hyperpad-desktop](https://github.com/noffle/hyperpad-desktop).\nThis lets me dodge some difficult problems around web tech (indexed-db\nperformance; webrtc) and focus on making the core\n[hyper-string](https://github.com/noffle/hyper-string) primitive robust and\ndurable.\n\n## Why another collaborative editor?\n\nSome of the most popular collaborative document editors today include [Google\nDocs](https://www.google.com/docs/about/) and [Etherpad](http://etherpad.org/).\n\nGoogle Docs gets the fundamental piece right: real-time text editing. However,\nall of your data is stored by and readable by Google, Inc. It is closed source\nproprietary software.\n\nEtherpad takes this a step further in multiple directions: it is [open\nsource](https://github.com/ether/etherpad-lite), and can be deployed by anyone\non any server. This lets any individual or group run etherpad and keep ownership\nand privacy to their data.\n\nEtherpad is most of the way there, but Hyperpad goes the rest of the way in two\ncrucial aspects:\n\n### 1. No servers required\n\nIn peer-to-peer networks, all users are equal.\n\nNobody needs the monetary resources and technical know-how to run a server.\n\nUnlike centralized services, you own each pad you create. Turn on encryption,\nand your data becomes unreadable to anyone but those you grant access to. There\nare no service providers to go out of business and lose your data.\n\nEverything is client-side HTML and Javascript: you can just save the Hyperpad\nwebsite and run it locally on your computer, and it will function just fine!\n\n### 2. Works great offline\n\nNot everybody in the world is online. Among those who are, many do not have\nconsistent, broadband connections. People-respecting software must work\nexcellently offline; no exceptions.\n\nForgetting this is the *The Silicon Valley Privilege* (TODO: link to article).\n\nHyperpad uses an *eventually consistent* data structure called\n[hyperlog](https://github.com/mafintosh/hyperlog), which operates happily\noffline and will sync with other users whenever a network connection is\navailable.\n\n## How does it work?\n\nHyperpad relies on the browser itself for storing documents, and powerful\npeer-to-peer primitives like\n[WebRTC](https://developer.mozilla.org/en-US/docs/Web/Guide/API/WebRTC) and\n[hyperlog](https://github.com/mafintosh/hyperlog) to organize and transfer\ndocuments to those with access.\n\nThe act of having a document open in your browser immediately lets a user act\nas a host for that document's data, sharing it in real-time with others with\nothers. In the case that a user is offline, they can still freely make edits\nlocally, which will propagate to others storing the document when they\nre-establish a network connection.\n\nHyperpad is built in a modular fashion atop a set of do-one-thing-well modules:\n\n- [hyper-textarea](https://github.com/noffle/hyper-textarea): back a textarea\n  with a hyper-string for conflict-free p2p replication\n- [hyper-string](https://github.com/noffle/hyper-string): a conflict-free p2p\n  string data structure\n- [textarea-op-stream](https://github.com/noffle/textarea-op-stream): readable\n  stream of a textarea's inserts and deletes\n- lots of great modules from [mafintosh](https://github.com/mafintosh/):\n  [hyperlog](https://github.com/mafintosh/hyperlog),\n  [signalhub](https://github.com/mafintosh/signalhub), and others!\n\n## Coming Soon(tm)\n\n- [hyperpad-desktop](https://github.com/noffle/hyperpad-desktop)\n- ~Faster operations (batching in the `hyper-string` layer)~\n- Encryption (with separate read/write privileges)\n- Secure app delivery (maybe [hyperboot](https://github.com/substack/hyperboot))\n\n## License\n\n[ISC](https://en.wikipedia.org/wiki/ISC_license)\n"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n  <title>hyperpad</title>\n  <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n<body>\n  <div id=\"wrap\">\n    <h1 id=\"title\">Initializing..</h1>\n    <input id=\"new-pad\" type=\"button\" value=\"New Pad\" onclick=\"onNewPad()\"/>\n    <div id=\"hint\">Share this URL to let others edit this pad!</div>\n    <textarea id=\"pad\" placeholder=\"A shining, brand new document.\"></textarea>\n    <div class=\"logo\">hyper<span class=\"logo2\">PAD</span></div>\n  </div>\n  <script src=\"bundle.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "index.js",
    "content": "var swarm = require('webrtc-swarm')\nvar signalhub = require('signalhub')\nvar hyperize = require('hyper-textarea')\nvar hstring = require('hyper-string')\nvar memdown = require('memdown')\nvar down = require('level-js')\nvar levelup = require('levelup')\nvar query = require('query-string')\nvar debug = console.log//require('debug')('hyperpad')\nvar eos = require('end-of-stream')\nvar hyperlog = require('hyperlog')\n\nvar ta = document.getElementById('pad')\n\nonNewPad = function () {\n  window.open(window.location.href.substring(0, window.location.href.indexOf('?')))\n}\n\nvar q = query.parse(location.search)\nvar doc = 'Untitled_' + (''+Math.random()).substring(2, 25)\nif (q.doc) {\n  doc = q.doc\n} else {\n  window.location.href += '?doc=' + doc\n}\n\n// var string = hstring(levelup('hyperpad-'+doc, { db: down }))\n// var string = hstring(levelup('hyperpad-'+doc, { db: memdown }))\nvar indexDb = levelup('hyperpad-'+doc, { db: down })\nvar string = hstring(indexDb)\nvar storageLog = hyperlog(indexDb, string.log.valueEncoding)\n\nvar r = storageLog.replicate({ live: true })\nvar s = string.log.replicate({ live: true })\nr.pipe(s).pipe(r)\n\nr.on('end', function () {\n  console.log('replication /w indexeddb log ended!')\n})\nr.on('error', function (err) {\n  console.log('replication /w indexeddb log ended: ' + err)\n})\n\nhyperize(ta, string)\n\ndocument.getElementById('title').innerHTML = doc\n\nvar hub = signalhub('hyperpad-' + doc, [\n  'http://eight45.net:9400',\n  // 'http://localhost:2500'\n  // 'https://signalhub.mafintosh.com'\n])\n\nvar rtcConfig = {\n  \"iceServers\": [\n    {\n      \"urls\": \"stun:23.21.150.121\"\n    },\n    {\n      \"username\": \"a03c3482a5ac94f85f98bf6478c7b779ed1c45756798f5246e870741ef0cda04\",\n      \"credential\": \"EIveHo0aA0i8WuCepOoWNGopB4LYxI72013GBv35ZGA=\",\n      \"urls\": \"turn:global.turn.twilio.com:3478?transport=udp\"\n    },\n    {\n      \"username\": \"a03c3482a5ac94f85f98bf6478c7b779ed1c45756798f5246e870741ef0cda04\",\n      \"credential\": \"EIveHo0aA0i8WuCepOoWNGopB4LYxI72013GBv35ZGA=\",\n      \"urls\": \"turn:global.turn.twilio.com:3478?transport=tcp\"\n    },\n    {\n      \"username\": \"a03c3482a5ac94f85f98bf6478c7b779ed1c45756798f5246e870741ef0cda04\",\n      \"credential\": \"EIveHo0aA0i8WuCepOoWNGopB4LYxI72013GBv35ZGA=\",\n      \"urls\": \"turn:global.turn.twilio.com:443?transport=tcp\"\n    }\n  ]\n}\n\nvar sw = swarm(hub, {\n  config: rtcConfig\n})\nsw.on('peer', function (peer, id) {\n  debug('replicating to a new peer:', id)\n\n  var r = string.createReplicationStream({ live: true })\n  eos(r, end)\n  eos(peer, end)\n  r.pipe(peer).pipe(r)\n\n  function end (err) {\n    debug('replication stream ended:', err)\n  }\n})\n\nsw.on('disconnect', function (peer, id) {\n  debug('disconnected from a peer:', id)\n})\n\nsw.on('close', function () {\n  debug('signalhub connection lost')\n})\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"hyperpad\",\n  \"description\": \"p2p collaborative text editor\",\n  \"version\": \"0.0.10\",\n  \"author\": \"Stephen Whitmore <stephen.whitmore@gmail.com>\",\n  \"repository\": {\n    \"url\": \"git://github.com/noffle/hyperpad.git\"\n  },\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"start\": \"browserify index.js > bundle.js && ecstatic -p 7000\"\n  },\n  \"dependencies\": {\n    \"debug\": \"^2.2.0\",\n    \"end-of-stream\": \"^1.1.0\",\n    \"hyper-string\": \"^2.0.1\",\n    \"hyper-textarea\": \"^1.0.0\",\n    \"hyperlog\": \"^4.12.1\",\n    \"level-js\": \"^2.2.4\",\n    \"levelup\": \"^1.3.2\",\n    \"memdown\": \"^1.2.4\",\n    \"query-string\": \"^4.2.2\",\n    \"signalhub\": \"^4.7.4\",\n    \"webrtc-swarm\": \"^2.6.1\"\n  },\n  \"devDependencies\": {\n    \"browserify\": \"^13.1.1\",\n    \"ecstatic\": \"^3.1.1\"\n  },\n  \"license\": \"ISC\"\n}\n"
  },
  {
    "path": "style.css",
    "content": "html {\n  background: #fafafa;\n  font-family: -apple-system, BlinkMacSystemFont, /* MacOS and iOS */\n               'avenir next', avenir, /* MacOS and iOS */\n               'Segoe UI', /* Windows */\n               'lucida grande', /* Older MacOS */\n               'helvetica neue', helvetica, /* Older MacOS */\n               'Fira Sans', /* Firefox OS */\n               roboto, noto, /* Google stuff */\n               'Droid Sans', /* Old Google stuff */\n               cantarell, oxygen, ubuntu, /* Linux stuff */\n               'franklin gothic medium', 'century gothic', /* Windows stuff */\n               'Liberation Sans', /* Linux */\n               sans-serif; /* Everything else */\n  font-size: 16px;\n}\nhtml, body {\n  margin: 0;\n  padding: 0;\n}\n#wrap {\n  box-sizing: border-box;\n  display: flex;\n  flex-direction: column;\n  height: 100vh;\n  padding: 5px 10px;\n  position: relative;\n}\n#hint {\n  font-size: 0.85em;\n  margin-bottom: 5px;\n  font-style: italic;\n  text-align: right;\n}\n.logo {\n  color: #aaa;\n  margin-top: 5px;\n  text-align: right;\n}\n.logo2 {\n  font-weight: 900;\n}\n#title {\n  font-weight: 300;\n  margin: 0;\n}\n#new-pad {\n  background: #fff;\n  border: 1px solid #aaa;\n  border-radius: 4px;\n  color: #444;\n  cursor: pointer;\n  font-size: 13px;\n  padding: .25em .75em .3em;\n  position: absolute;\n  right: 10px;\n  top: 10px;\n}\n#new-pad:hover,\n#new-pad:active,\n#new-pad:focus {\n  background: #eee;\n  border-color: #777;\n  color: #222;\n}\n#pad {\n  background: #fff;\n  border: 1px solid #ccc;\n  color: #333;\n  font-size: 16px;\n  flex-grow: 1;\n  outline: none;\n  padding: 1em;\n  resize: none;\n}\n#pad:focus {\n  border-color: #aaa;\n  color: #111;\n}\n"
  }
]