├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── package.json ├── swarm.js └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.12' 4 | - 'iojs' 5 | sudo: false 6 | cache: 7 | directories: 8 | - node_modules 9 | script: 10 | - npm test 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # friends-swarm change log 2 | 3 | All notable changes to this project will be documented in this file. 4 | This project adheres to [Semantic Versioning](http://semver.org/). 5 | 6 | ## Unreleased 7 | * engage 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Contributions welcome! 4 | 5 | **Before spending lots of time on something, ask for feedback on your idea first!** 6 | 7 | Please search issues and pull requests before adding something new to avoid duplicating efforts and conversations. 8 | 9 | In addition to improving the project by refactoring code and and implementing relevant features, this project welcomes the following types of contributions: 10 | 11 | - **Ideas**: participate in an issue thread or start your own to have your voice heard. 12 | - **Writing**: contribute your expertise in an area by helping expand the included content. 13 | - **Copy editing**: fix typos, clarify language, and generally improve the quality of the content. 14 | - **Formatting**: help keep content easy to read with consistent formatting. 15 | 16 | ## Installing 17 | 18 | Fork and clone the repo, then `npm install` to install all dependencies. 19 | 20 | The `app` folder has the actual app, and it has its own `package.json`. If you are adding a dependency to the app, add it to `app/package.json`. The top level `package.json` has the build `devDependencies` (like electron). 21 | 22 | ## Testing 23 | 24 | Tests are run with `npm test`. Please ensure all tests are passing before submitting a pull request (unless you're creating a failing test to increase test coverage or show a problem). 25 | 26 | ## Code Style 27 | 28 | [![standard][standard-image]][standard-url] 29 | 30 | This repository uses [`standard`][standard-url] to maintain code style and consistency, and to avoid style arguments. `npm test` runs `standard` so you don't have to! 31 | 32 | [standard-image]: https://cdn.rawgit.com/feross/standard/master/badge.svg 33 | [standard-url]: https://github.com/feross/standard 34 | 35 | --- 36 | 37 | # Collaborating Guidelines 38 | 39 | **This is an OPEN Open Source Project.** 40 | 41 | ## What? 42 | 43 | Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project. 44 | 45 | ## Rules 46 | 47 | There are a few basic ground-rules for contributors: 48 | 49 | 1. **No `--force` pushes** or modifying the Git history in any way. 50 | 1. **Non-master branches** ought to be used for ongoing work. 51 | 1. **External API changes and significant modifications** ought to be subject to an **internal pull-request** to solicit feedback from other contributors. 52 | 1. Internal pull-requests to solicit feedback are *encouraged* for any other non-trivial contribution but left to the discretion of the contributor. 53 | 1. Contributors should attempt to adhere to the prevailing code style. 54 | 55 | ## Releases 56 | 57 | Declaring formal releases remains the prerogative of the project maintainer. 58 | 59 | ## Changes to this arrangement 60 | 61 | This is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change. 62 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) MOOSE Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # friends-swarm 2 | 3 | [![npm][npm-image]][npm-url] 4 | [![travis][travis-image]][travis-url] 5 | 6 | [npm-image]: https://img.shields.io/npm/v/friends-swarm.svg?style=flat-square 7 | [npm-url]: https://www.npmjs.com/package/friends-swarm 8 | [travis-image]: https://img.shields.io/travis/moose-team/friends-swarm.svg?style=flat-square 9 | [travis-url]: https://travis-ci.org/moose-team/friends-swarm 10 | 11 | :bee: webrtc-swarm with friends 12 | 13 | ## Install 14 | 15 | ``` 16 | npm install friends-swarm 17 | ``` 18 | 19 | ## Usage 20 | 21 | ```js 22 | var friendsSwarm = require('friends-swarm') 23 | ``` 24 | 25 | ## Contributing 26 | 27 | Contributions welcome! Please read the [contributing guidelines](CONTRIBUTING.md) first. 28 | 29 | ## License 30 | 31 | [ISC](LICENSE.md) 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "friends-swarm", 3 | "description": ":bee: webrtc-swarm with friends", 4 | "version": "2.1.1", 5 | "author": "moose-team", 6 | "bugs": { 7 | "url": "https://github.com/moose-team/friends-swarm/issues" 8 | }, 9 | "devDependencies": { 10 | "standard": "*", 11 | "tap-spec": "^3.0.0", 12 | "tape": "^4.0.0" 13 | }, 14 | "homepage": "https://github.com/moose-team/friends-swarm", 15 | "keywords": [ 16 | "friends" 17 | ], 18 | "license": "MIT", 19 | "main": "swarm.js", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/moose-team/friends-swarm.git" 23 | }, 24 | "scripts": { 25 | "test": "standard && tape test/*.js | tap-spec" 26 | }, 27 | "dependencies": { 28 | "hyperlog": "^4.6.0", 29 | "multiline": "^1.0.2", 30 | "nets": "^3.2.0", 31 | "protocol-buffers": "^3.1.1", 32 | "signalhub": "^4.0.3", 33 | "subleveldown": "^2.0.0", 34 | "through2": "^2.0.0", 35 | "webrtc-swarm": "^2.0.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /swarm.js: -------------------------------------------------------------------------------- 1 | var events = require('events') 2 | var subleveldown = require('subleveldown') 3 | var webrtcSwarm = require('webrtc-swarm') 4 | var signalhub = require('signalhub') 5 | var hyperlog = require('hyperlog') 6 | var through = require('through2') 7 | var protobuf = require('protocol-buffers') 8 | var multiline = require('multiline') 9 | var nets = require('nets') 10 | 11 | var messages = protobuf(multiline(function () { /* 12 | message SignedMessage { 13 | optional bytes signature = 1; 14 | required bytes message = 2; 15 | } 16 | 17 | message Message { 18 | optional string username = 1; 19 | optional string channel = 2; 20 | optional uint64 timestamp = 3; 21 | optional string text = 4; 22 | } 23 | 24 | */ })) 25 | 26 | module.exports = createSwarm 27 | 28 | function createSwarm (db, defaultOpts) { 29 | var swarm = new events.EventEmitter() 30 | var logs = {} 31 | 32 | swarm.logs = logs 33 | 34 | var processor 35 | var sign 36 | var verify 37 | 38 | if (!defaultOpts) defaultOpts = {} 39 | 40 | if (!defaultOpts.hubs) { 41 | defaultOpts.hubs = [ 42 | 'https://signalhub.mafintosh.com' // mafintosh 43 | // 'https://instant.io:8080' // feross 44 | ] 45 | } 46 | 47 | var remoteConfigUrl = defaultOpts.remoteConfigUrl || 'https://instant.io/rtcConfig' // thanks feross 48 | 49 | swarm.changes = function (name) { 50 | return logs[name] ? logs[name].changes : 0 51 | } 52 | 53 | /** 54 | * fn should be a function that takes the args `data, callback`. 55 | * The `callback` takes the args `err, signature`. 56 | */ 57 | swarm.sign = function (fn) { 58 | sign = fn 59 | } 60 | 61 | /** 62 | * fn should be a function that takes the args `username, signature, callback`. 63 | * The `callback` is called with `err, valid` with valid being truthy if the signature was verified. 64 | */ 65 | swarm.verify = function (fn) { 66 | verify = fn 67 | } 68 | 69 | swarm.process = function (fn) { 70 | processor = fn 71 | Object.keys(logs).forEach(function (name) { 72 | startProcessor(logs[name]) 73 | }) 74 | } 75 | 76 | swarm.removeChannel = function (name) { 77 | var log = logs[name] 78 | if (!log) return 79 | delete logs[name] 80 | 81 | if (log.processing) log.processing.destroy() 82 | log.peers.forEach(function (p) { 83 | p.destroy() 84 | }) 85 | } 86 | 87 | swarm.addChannel = function (name) { 88 | if (logs[name]) return 89 | 90 | getRemoteConfig(remoteConfigUrl, function (err, config) { 91 | if (err) console.error('skipping remote config', err) 92 | if (config) defaultOpts.config = config 93 | var log = logs[name] = hyperlog(subleveldown(db, name)) 94 | var id = 'friends-' + name 95 | var hub = signalhub(id, defaultOpts.hubs) 96 | var sw = webrtcSwarm(hub, defaultOpts) 97 | 98 | log.peers = [] 99 | 100 | sw.on('peer', function (p, id) { 101 | var stream = log.replicate({ live: true }) 102 | 103 | log.peers.push(p) 104 | p.on('close', function () { 105 | var i = log.peers.indexOf(p) 106 | if (i > -1) log.peers.splice(i, 1) 107 | }) 108 | 109 | swarm.emit('peer', p, name, id, stream) 110 | 111 | stream.on('push', function () { 112 | swarm.emit('push', name) 113 | }) 114 | 115 | stream.on('pull', function () { 116 | swarm.emit('pull', name) 117 | }) 118 | 119 | p.pipe(stream).pipe(p) 120 | }) 121 | 122 | if (processor) startProcessor(log) 123 | }) 124 | } 125 | 126 | swarm.send = function (message, cb) { 127 | var addMessage = function (m, sig) { 128 | var ch = message.channel || 'friends' 129 | swarm.addChannel(ch) 130 | var log = logs[ch] 131 | log.heads(function (err, heads) { 132 | if (err) return cb(err) 133 | log.add(heads, messages.SignedMessage.encode({ signature: sig, message: m }), cb) 134 | }) 135 | } 136 | 137 | var m = messages.Message.encode(message) 138 | 139 | if (sign) { 140 | sign(m, function (err, sig) { 141 | if (err) return cb(err) 142 | addMessage(m, sig) 143 | }) 144 | return 145 | } 146 | 147 | addMessage(m, null) 148 | } 149 | 150 | function startProcessor (log) { 151 | log.ready(function () { 152 | if (log.processing) return 153 | 154 | var rs = log.processing = log.createReadStream({ 155 | live: true, 156 | since: Math.max(log.changes - 500, 0) 157 | }) 158 | 159 | rs.pipe(through.obj(function (data, enc, cb) { 160 | if (data.value.toString()[0] === '{') return cb() // old stuff 161 | 162 | var val = messages.SignedMessage.decode(data.value) 163 | var m = messages.Message.decode(val.message) 164 | var u = m.username 165 | 166 | m.change = data.change 167 | 168 | if (verify) { 169 | verify(u, val.message, val.signature, function (_, valid) { 170 | m.valid = !!valid 171 | processor(m, cb) 172 | }) 173 | return 174 | } 175 | 176 | m.valid = false 177 | processor(m, cb) 178 | })) 179 | }) 180 | } 181 | 182 | return swarm 183 | } 184 | 185 | // get remote webrtc config (ice/stun/turn) 186 | function getRemoteConfig (remoteConfigUrl, cb) { 187 | nets({ url: remoteConfigUrl, json: true }, function gotConfig (err, resp, config) { 188 | if (err || resp.statusCode > 299) config = undefined // ignore errors 189 | cb(null, config) 190 | }) 191 | } 192 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 2 | --------------------------------------------------------------------------------