├── .gitignore ├── Makefile ├── README.md ├── package.json ├── shadow-cljs.edn └── src └── mesh ├── main.cljs └── web.cljs /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | .shadow-cljs 3 | /*.js 4 | node_modules 5 | webrtc-signaling-mesh 6 | workspace 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | webrtc-signaling-mesh: build.js 2 | echo "#!/usr/bin/env node" > $@ 3 | npx browserify --node build.js | npx uglifyjs >> $@ 4 | chmod 755 $@ 5 | 6 | build.js: src/**/*.cljs shadow-cljs.edn 7 | npx shadow-cljs release artifact 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 🚧 Under construction. Please check back later. 2 | 3 | * Problem: WebRTC signaling is centralized. 4 | * Solution: a decentralized mesh of WebRTC signaling servers. 5 | 6 | # Quickstart: [run a node](#run-a-node) 7 | 8 | Web browsers can send messages directly peer-to-peer using WebRTC. This gives us a way to build decentralized web applications where browsers (peers) can communicate directly without a trusted third-party server in between. Unfortunately the WebRTC connection phase requires centralized services for establishing the route between peers, NAT hole-punching, and negotiating the connection. These centralized must provide a signalling protocol, STUN, and sometimes TURN connectivity, which makes them a single point of failure in peer-to-peer web applications. 9 | 10 | This server provides a solution to this problem. It uses the bittorrent DHT to find other server nodes and then forms a mesh that client browsers can use to coordinate WebRTC connectivity in a decentralized, trust-minimised way. It additionally provides a STUN and TURN service that clients can also use. 11 | 12 | Server nodes are protected from becoming overloaded by a proof-of-work requirement on clients. As load increases on a server the proof-of-work they require from clients increases, and clients can use this signal to select for nodes which are under less load, thereby ensuring better service and distribution of traffic over the network. 13 | 14 | # Run a node 15 | 16 | ... 17 | 18 | 44 | 45 | # API 46 | 47 | * `/` - returns server's hash token and current PoW height requirement. 48 | * `/nodes` - returns a random selection of recently seen nodes `["https://...", "https://..." ...]`. 49 | * `/ip` - returns the requesting client's IP address as seen by this node. 50 | * `/dht-get` - make a BitTorrent DHT mutable get request. 51 | * `/dht-put` - make a BitTorrent DHT mutable put request. 52 | * `/token` - obtain a TURN server authentication token by sending sufficient PoW. 53 | * `/room/HASH` - server-sent-event signaling channel where all posts to a particular hash are broadcast to all nodes connected to that hash. 54 | 55 | If `content-type` is set to `json` then JSON data will be returned, otherwise an HTML summary will be returned. 56 | 57 | Post size is limited to 1k, and every post requires PoW. 58 | 59 | PoW is proportional to `max(cpu, mem)`. Additional PoW is required for every message sent in the past minute to the `/room/HASH` endpoint. 60 | 61 | A STUN & TURN server are also run. Authentication tokens for the STUN/TURN server can be obtained from the `/token` endpoint. 62 | 63 | PoW tokens are generated against a time-stamped HMAC of the server secret which the server can use to establish recency. 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "bittorrent-dht": "^9.0.3", 4 | "bittorrent-protocol": "^3.1.1", 5 | "browserify": "^16.5.0", 6 | "cookie-parser": "^1.4.5", 7 | "express": "^4.17.1", 8 | "express-ws": "^4.0.0", 9 | "shadow-cljs": "^2.11.12", 10 | "uglify-es": "^3.3.9" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /shadow-cljs.edn: -------------------------------------------------------------------------------- 1 | {:source-paths ["src"] 2 | :dependencies [] 3 | :builds {:app {:target :node-script 4 | :output-to "server.js" 5 | :main mesh.main/main! 6 | :devtools {:after-load mesh.main/reload!}} 7 | :artifact {:target :node-script 8 | :output-to "build.js" 9 | :main mesh.main/main!}}} 10 | 11 | -------------------------------------------------------------------------------- /src/mesh/main.cljs: -------------------------------------------------------------------------------- 1 | (ns mesh.main 2 | (:require 3 | [mesh.web :as web] 4 | ;[mesh.bittorrent :as bt] 5 | ;[mesh.util :as util] 6 | [cljs.core.async :refer (go app 42 | (add-default-middleware) 43 | (add-default-routes)))) 44 | 45 | (defn create [] 46 | (-> (express) 47 | (add-default-middleware) 48 | (add-default-routes))) 49 | 50 | (defn serve [app] 51 | (let [host (or (aget js/process.env "BIND_ADDRESS") "127.0.0.1") 52 | port (or (aget js/process.env "PORT") "8000") 53 | srv (.bind (aget app "listen") app port host)] 54 | (srv (fn [] (js/console.log "Web server started: " (str "http://" host ":" port)))))) 55 | --------------------------------------------------------------------------------