├── .gitignore
├── README.md
├── dist
└── bundle.js
├── index.html
├── lib
├── filter.js
├── greet.js
├── handshake.js
├── initiator.js
├── listen.js
├── peers.js
├── scope.js
└── view.js
├── package.json
├── server.js
├── src
├── get.js
├── index.js
├── local.js
└── put.js
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | data.json
4 |
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | gun-rtc
2 | =======
3 | WebRTC Communication layer for gunDB
4 |
5 | > **Note:** this project is under development and unfinished.
6 |
7 | ## What is it
8 | The idea is that you could replace
9 | ```html
10 |
11 | ```
12 | with
13 | ```html
14 |
15 | ```
16 | and automatically upgrade your boring websocket app to a decentralized, resiliant, offline-first, WebRTC driven app. Oh, and if WebRTC isn't supported, you can keep your websockets and still collab with the cool people on WebRTC.
17 |
18 | ## What's the holdup?
19 | We've been holding off the heavy development until [gun's 0.3](https://github.com/amark/gun/blob/master/CHANGELOG.md) release hit the masses. Now that most of the ecosystem has caught up with the release, we're ready to dive back in and build the future.
20 |
21 | > **Update (05/17):** through development, we've come to realize our plugin system could use some love. We're hoping to have a release out soon that allows us to build modules (like gun-rtc) without overloading and masking other plugins just to read and write data. After we've got that resolved, then we can push forward on gun-rtc without needing hackery.
22 |
23 | Star this repository to get occasional project updates and to aid my narcissism.
24 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | GunRTC
6 |
7 |
8 |
16 | No connection
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/lib/filter.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true*/
2 | 'use strict';
3 | var seen = {};
4 |
5 | module.exports = function filter(signal) {
6 | if (seen[signal]) {
7 | return true;
8 | }
9 | seen[signal] = true;
10 | return false;
11 | };
12 |
--------------------------------------------------------------------------------
/lib/greet.js:
--------------------------------------------------------------------------------
1 | /*jslint nomen: true, node: true */
2 |
3 | var Gun = require('gun/gun');
4 | var initiator = require('./initiator');
5 | var filter = require('./filter');
6 | var online = require('./peers').online;
7 |
8 | var younger = (function () {
9 | 'use strict';
10 | var time = new Date().getTime();
11 | return function (node) {
12 | return (Gun.is.node.state(node, 'id') > time);
13 | };
14 | }());
15 |
16 |
17 | function greet(peers, myID) {
18 | 'use strict';
19 |
20 | // each peer
21 | peers.map().val(function (obj) {
22 | // except myID
23 | if (obj.id === myID || !younger(obj)) {
24 | return;
25 | }
26 | if (online[obj.id]) {
27 | return;
28 | }
29 |
30 | var client, peer = this;
31 |
32 | function handleSignal(signal) {
33 | var obj, SDO = JSON.stringify(signal);
34 | obj = {};
35 |
36 | filter(SDO);
37 |
38 | // post the session object
39 | obj[Gun.text.random(10)] = SDO;
40 | peer.path(myID).put(obj);
41 | }
42 |
43 | // create a peer and listen for signals
44 | client = initiator(true, obj.id, handleSignal);
45 |
46 | // listen for new messages
47 | peer.path(myID).map().val(function (res, key) {
48 | if (typeof res === 'object') {
49 | return;
50 | }
51 |
52 | if (key === 'id' || filter(res)) {
53 | return;
54 | }
55 |
56 | var signal = JSON.parse(res);
57 |
58 | // connection failed. Don't join.
59 | if (!client.destroyed) {
60 | client.signal(signal);
61 | }
62 | });
63 |
64 | });
65 |
66 | }
67 |
68 |
69 | module.exports = greet;
70 |
--------------------------------------------------------------------------------
/lib/handshake.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true*/
2 | 'use strict';
3 |
4 | var greet = require('./greet');
5 | var listen = require('./listen');
6 |
7 | module.exports = function (db, id) {
8 | greet(db, id);
9 | listen(db, id);
10 |
11 | return db;
12 | };
13 |
--------------------------------------------------------------------------------
/lib/initiator.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true*/
2 |
3 | /*
4 | creates peer objects
5 | and assigns event handlers
6 | to them.
7 | */
8 |
9 | 'use strict';
10 | var peers = require('./peers');
11 | var Gun = require('gun/gun');
12 | var SimplePeer = require('simple-peer');
13 | var view = require('./view');
14 | var local = require('../src/local');
15 | var emitter = local.events;
16 |
17 |
18 |
19 | module.exports = function initiator(init, id, signal) {
20 | var peer = new SimplePeer({
21 | initiator: init,
22 | trickle: true
23 | });
24 | peer.on('connect', function () {
25 | view.connection();
26 | peers.online[id] = peer;
27 | window.peers = peers;
28 | });
29 | peer.on('signal', signal);
30 | peer.on('error', view.error);
31 | peer.on('close', function () {
32 | view.disconnect();
33 | peer.destroy();
34 | delete peers.online[id];
35 | });
36 |
37 |
38 | peer.on('data', function (data) {
39 | emitter.emit(data.event, data, peer);
40 | });
41 |
42 | return peer;
43 | };
44 |
--------------------------------------------------------------------------------
/lib/listen.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true*/
2 | var initiator = require('./initiator');
3 | var filter = require('./filter');
4 | var Gun = require('gun/gun');
5 |
6 | module.exports = function listen(peers, myself) {
7 | 'use strict';
8 |
9 | // each request object
10 | peers.path(myself).map().val(function (v, key) {
11 | var peer, invalid, requests = this;
12 |
13 | if (key === 'id') {
14 | return;
15 | }
16 |
17 | function handleSignal(signal) {
18 | var SDO = JSON.stringify(signal);
19 | filter(SDO);
20 | requests.path(Gun.text.random(10)).put(SDO);
21 | }
22 |
23 | // create a peer instance
24 | peer = initiator(false, key, handleSignal);
25 |
26 | // each session description object
27 | requests.map().val(function (SDO, key) {
28 | if (typeof SDO === 'object') {
29 | return;
30 | }
31 | if (key === 'id' || filter(SDO)) {
32 | return;
33 | }
34 |
35 | var signal = JSON.parse(SDO);
36 |
37 | // don't signal if the connection is closed
38 | if (!peer.destroyed) {
39 | peer.signal(signal);
40 | }
41 |
42 | });
43 | });
44 |
45 | };
46 |
--------------------------------------------------------------------------------
/lib/peers.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true*/
2 | 'use strict';
3 |
4 | function PeerCollection() {}
5 | PeerCollection.prototype = {
6 | each: function (cb) {
7 | var peer;
8 | for (peer in this) {
9 | if (this.hasOwnProperty(peer)) {
10 | cb(this[peer], peer, this);
11 | }
12 | }
13 | return this;
14 | },
15 |
16 | broadcast: function (msg) {
17 | return this.each(function (peer) {
18 | peer.send(msg);
19 | });
20 | }
21 | };
22 |
23 | module.exports = {
24 | online: new PeerCollection()
25 | };
26 |
--------------------------------------------------------------------------------
/lib/scope.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true*/
2 | module.exports = 'gx904egpMe7bl1ggaPVv:';
3 |
--------------------------------------------------------------------------------
/lib/view.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true*/
2 | 'use strict';
3 | var view;
4 |
5 | function $(query) {
6 | return document.querySelector(query);
7 | }
8 |
9 | module.exports = view = {
10 | h1: $('h1'),
11 |
12 | connection: function () {
13 | view.h1.style.color = '#00ba00';
14 | view.h1.innerHTML = 'Connected';
15 | },
16 |
17 | error: function (e) {
18 | $('div').innerHTML = e.message || 'No message';
19 | },
20 |
21 | disconnect: function () {
22 | view.h1.style.color = '#ba0000';
23 | view.h1.innerHTML = 'Disconnected';
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gun-rtc",
3 | "version": "0.0.1",
4 | "description": "WebRTC transport layer for gun",
5 | "main": "index.js",
6 | "directories": {
7 | "example": "examples"
8 | },
9 | "dependencies": {
10 | "gun": "^0.3.3",
11 | "iso-emitter": "^1.0.0",
12 | "simple-peer": "^6.1.0"
13 | },
14 | "devDependencies": {
15 | "webpack": "^1.12.13",
16 | "webpack-dev-server": "^1.14.1"
17 | },
18 | "scripts": {
19 | "test": "mocha",
20 | "start": "node server.js",
21 | "build": "webpack --watch"
22 | },
23 | "repository": {
24 | "type": "git",
25 | "url": "git+https://github.com/PsychoLlama/gun-rtc.git"
26 | },
27 | "keywords": [
28 | "WebRTC",
29 | "RTC",
30 | "distributed",
31 | "peer-to-peer",
32 | "P2P",
33 | "gun",
34 | "gundb",
35 | "db",
36 | "transport",
37 | "persistence",
38 | "data-sync",
39 | "synchronization",
40 | "sync-engine",
41 | "conflict-resolution"
42 | ],
43 | "author": "Jesse Gibson (http://techllama.com)",
44 | "license": "(Zlib OR MIT OR Apache-2.0)",
45 | "bugs": {
46 | "url": "https://github.com/PsychoLlama/gun-rtc/issues"
47 | },
48 | "homepage": "https://github.com/PsychoLlama/gun-rtc#readme"
49 | }
50 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true, nomen: true */
2 | 'use strict';
3 |
4 | var express = require('express');
5 | var Gun = require('gun');
6 | var app = express();
7 | var port = process.argv[2] || 8080;
8 |
9 |
10 | var gun = new Gun({
11 | file: 'data.json'
12 | }).wsp(app);
13 |
14 | function serve(route) {
15 | app.use('/', express['static'](__dirname + route));
16 | }
17 |
18 | serve('/');
19 | serve('/lib');
20 | serve('/dist');
21 |
22 | app.listen(port, function () {
23 | console.log('Listening on port', port);
24 | });
25 |
--------------------------------------------------------------------------------
/src/get.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true, nomen: true*/
2 | 'use strict';
3 | var Gun = require('gun/gun');
4 | var local = require('./local');
5 | var peers = require('../lib/peers');
6 | var emitter = local.events;
7 | var cache = {};
8 |
9 | // server
10 | emitter.on('get', function (req, peer) {
11 | if (!local.db || cache[req.RID]) {
12 | return;
13 | }
14 | cache[req.RID] = true;
15 |
16 | // this won't be necessary in the future!
17 | local.db.__.opt.wire.get(req.lex, function (err, value) {
18 |
19 | if (!peer.connected) {
20 | return;
21 | }
22 |
23 | peer.send({
24 | event: req.RID,
25 | value: value,
26 | err: err
27 | });
28 | }, req.opt);
29 | });
30 |
31 |
32 |
33 | // client
34 | module.exports = function (lex, cb, opt) {
35 | opt = {};
36 | var requestID = Gun.text.random(20);
37 |
38 | local.db.__.opt.wire.get(lex, cb, opt);
39 |
40 | // when calling `.put`, the raw `.get` driver responds
41 | // with more data causing it to infinitely recurse.
42 | // support options for server stuns.
43 | emitter.on(requestID, function (data) {
44 | cb(data.err, data.value);
45 | });
46 |
47 | // be more verbose until we have
48 | // Gun.is.lex, cuz I'm lazy :P
49 | peers.online.broadcast({
50 | RID: requestID,
51 | event: 'get',
52 | lex: lex,
53 | opt: opt
54 | });
55 | };
56 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /*jslint nomen: true, node: true */
2 | 'use strict';
3 |
4 | var handshake = require('../lib/handshake');
5 | var peers = require('../lib/peers');
6 | var SimplePeer = require('simple-peer');
7 | var Gun = require('gun/gun');
8 | var local = require('./local');
9 | Gun.time.now = function () {
10 | return new Date().getTime();
11 | };
12 |
13 | Gun.on('opt').event(function (gun, opt) {
14 | opt = opt || {};
15 | var support, browser, wire, rtc = opt.rtc;
16 | support = SimplePeer.WEBRTC_SUPPORT;
17 | browser = typeof window !== 'undefined';
18 |
19 | if (rtc === false || (!support && browser)) {
20 | return;
21 | }
22 |
23 | if (!peers.db) {
24 |
25 | peers.db = new Gun({
26 | peers: gun.__.opt.peers,
27 | rtc: false
28 | }).get({
29 | '#': 'GUN_RTC_PEERS_SETUP',
30 | '>': {
31 | '>': new Date().getTime()
32 | }
33 | });
34 |
35 | peers.db.path(local.ID).put({
36 | id: local.ID
37 | });
38 |
39 | handshake(peers.db, local.ID);
40 | }
41 |
42 | wire = opt.wire || {};
43 | gun.opt({
44 | wire: {
45 | get: wire.get || require('./get'),
46 | put: wire.put || require('./put')
47 | }
48 | }, true);
49 |
50 | });
51 |
52 | window.gun = new Gun(location + 'gun');
53 |
54 | module.exports = Gun;
55 |
--------------------------------------------------------------------------------
/src/local.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true*/
2 | var Gun = require('gun/gun');
3 | var Emitter = require('events');
4 |
5 | // local data interface
6 | window.local = module.exports = {
7 | db: new Gun({
8 | rtc: false
9 | }),
10 |
11 | ID: Gun.text.random(),
12 | events: new Emitter()
13 | };
14 |
--------------------------------------------------------------------------------
/src/put.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true, nomen: true*/
2 | 'use strict';
3 | var Gun = require('gun/gun');
4 | var local = require('./local');
5 | var peers = require('../lib/peers');
6 | var events = local.events;
7 | var cache = {};
8 |
9 | // server
10 | events.on('put', function (req, peer) {
11 | if (cache[req.RID]) {
12 | return;
13 | }
14 | cache[req.RID] = true;
15 |
16 | // this won't be necessary in the future!
17 | local.db.__.opt.wire.put(req.graph, function (err, node) {
18 | if (!peer.connected) {
19 | return;
20 | }
21 | peer.send({
22 | event: req.RID,
23 | err: err,
24 | value: node
25 | });
26 | }, req.opt);
27 | });
28 |
29 | // client
30 | module.exports = function (graph, cb, opt) {
31 | opt = {};
32 | var requestID = Gun.text.random(20);
33 |
34 | // Gun.is.graph(graph, function (node, soul) {
35 | // var graph = local.db.__.graph;
36 | // if (!graph[soul]) {
37 | // graph[soul] = node;
38 | // }
39 | // });
40 | // local.db.__.opt.wire.put(graph, cb, opt);
41 |
42 | Gun.is.graph(graph, function (node, soul) {
43 | local.db.put(node).key(soul);
44 | });
45 |
46 | events.on(requestID, function (data) {
47 | cb(data.err, data.value);
48 | });
49 |
50 | peers.online.broadcast({
51 | RID: requestID,
52 | event: 'put',
53 | graph: graph,
54 | opt: opt
55 | });
56 | };
57 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true, nomen: true*/
2 | module.exports = {
3 | context: __dirname + "/src",
4 | entry: "./index.js",
5 | output: {
6 | path: __dirname,
7 | filename: "dist/bundle.js"
8 | }
9 | };
10 |
--------------------------------------------------------------------------------