├── README.md
├── assets
├── 404.html
├── home.html
└── index.html
├── federalist.png
├── include
├── hresolver.js
├── preload.js
└── renderer.js
├── main.js
├── package-lock.json
└── package.json
/README.md:
--------------------------------------------------------------------------------
1 | # federalist
2 | ## Access a new kind of decentralized website on the DHT
3 |
4 | ## Learn more by joining the [Handshake Discord Community](https://discord.gg/tXJ2UdGuda)
5 |
6 | ## Extension
7 |
8 | An extension was made by the [Kyokan](https://kyokan.io) team. [Bob Extension](https://chrome.google.com/webstore/detail/bob-extension/ogcmjchbmdichlfelhmceldndgmgpcem?hl=en-US) supports the federalist protocol and a plethora of other features including Handshake wallet support! This is highly recommended to use moving forward. This repo will be updated soon to support webrtc.
9 |
10 | ### Short Description
11 |
12 | federalist uses webtorrent with BEP 46 and Handshake domain resolution to access truly decentralized, unblockable swarm websites.
13 |
14 | ### Long Description
15 |
16 | federalist is a proof of concept to show that it's possible, today, to create a decentralized, uncensorable and generally unblockable, distributed high performance
17 | viewable page. federalist achieves this by using several amazing technologies and weaves them together:
18 |
19 | - [WebTorrent](https://github.com/webtorrent) - Provides torrenting capabilities and the bulk of the system. federalist pages are torrents that are
20 | distributed via magnet links and DHT is used to find peers.
21 |
22 | - [DMT](https://github.com/lmatteis/dmt) - Reference implementation of decentralized mutable torrents, [BEP 46](https://github.com/lmatteis/bittorrent.org/blob/master/beps/bep_0046.rst).
23 |
24 | - [Handshake](https://github.com/handshake-org/) - Provides decentralized DNS
25 |
26 | - [nodeJS](https://github.com/nodejs) - Provides the Javascript engine built on V8 by Chrome
27 |
28 | - [Electron](https://github.com/electron) - Provides the GUI
29 |
30 | - [Chromium](https://github.com/chromium/chromium) - Electron uses this really cool software to deliver GUI applications that can be made with HTML, CSS and Javascript.
31 |
32 | ### Features
33 |
34 | - Update your unblockable swarm site without updating your DNS, as often as you'd like.
35 |
36 | - Use HTML, CSS and Javascript
37 |
38 | - Unblockable
39 |
40 | ### Status
41 |
42 | This software is a POC and is in version 0.1a. This is an upgrade from the previous version which worked with markdown files and only immutable destinations.
43 |
44 | ### Screenshot
45 |
46 | 
47 |
48 |
49 | ## Use Cases
50 |
51 | - Create an unblockable blog
52 |
53 | - Whistleblow (must use an anonymizer)
54 |
55 | - Share information
56 |
57 | - Share meta information about other torrents
58 |
59 | - Create an unblockable personal link site
60 |
61 | - Save bandwidth and CDN costs
62 |
63 | ## federalist
64 |
65 | The federalist app is used to browse. You can use the [publius](https://github.com/publiusfederalist/publius) app to share/seed your page and get the infohash to which an on-chain Handshake name will be resolved.
66 |
67 | ## Installation
68 |
69 | Required
70 | ```
71 | npm
72 | nodejs
73 | ```
74 |
75 | 1. Clone this repo
76 | ```
77 | git clone https://github.com/publiusfederalist/federalist
78 | cd federalist
79 | ```
80 |
81 | 2. Install npm modules
82 | ```
83 | npm install
84 | ```
85 |
86 | 3. Run federalist
87 | ```
88 | npm start
89 | ```
90 |
91 | 4. Access a decentralized, unblockable swarm site.
92 | ```
93 | federalist://federalistpapers
94 | ```
95 |
96 | which points to:
97 |
98 | ```
99 | magnet:?xs=urn:btpk:e239849106256aad20b0ddadd9f2cb013910dab3207f3b200fbe2e76899cb6c2
100 | ```
101 |
102 | ## Discussion
103 |
104 | Please make an issue on github for any bugs or feature requests. To discuss more,
105 | join #scarywater on irc.freenode.net.
106 |
107 |
108 | ## Copyright and License
109 |
110 | (c) 2021 Publius
111 |
112 | MIT LICENSED
113 |
--------------------------------------------------------------------------------
/assets/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | 404 Not Found
4 |
9 |
10 |
11 | federalist 0.1a
12 | Thanks for trying out federalist 0.1a.
13 | Check out federalist://federalistpapers .
14 | Please donate HNS to hs1qxgz3w2ex9y4v4x37v5jmgmv0m9nsultdala654
.
15 |
16 |
17 |
--------------------------------------------------------------------------------
/assets/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/federalist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/publiusfederalist/federalist/f75246c96b34d92721890d6fd8983ab77138dc12/federalist.png
--------------------------------------------------------------------------------
/include/hresolver.js:
--------------------------------------------------------------------------------
1 | const doh = require('dohjs');
2 | const resolver = new doh.DohResolver('https://query.hdns.io/dns-query');
3 |
4 | let hresolver = async function(name) {
5 | let response = await resolver.query(name,'TXT');
6 | if(response.answers.length>0) {
7 | let returned=null;
8 | response.answers.forEach(ans=>{
9 | returned = ans.data.toString();
10 | });
11 | return (returned?returned:null);
12 | }
13 | else
14 | return null;
15 | }
16 |
17 | module.exports = hresolver;
18 |
--------------------------------------------------------------------------------
/include/preload.js:
--------------------------------------------------------------------------------
1 | const { contextBridge, ipcRenderer } = require("electron");
2 | contextBridge.exposeInMainWorld(
3 | "api", {
4 | send: (channel, data) => {
5 | let validChannels = ["go","external","title"];
6 | if (validChannels.includes(channel)) {
7 | ipcRenderer.send(channel, data);
8 | }
9 | },
10 | receive: (channel, func) => {
11 | let validChannels = ["show","notfound","updateAddress","disableAddress","enableAddress"];
12 | if (validChannels.includes(channel)) {
13 | ipcRenderer.on(channel, (event, ...args) => func(...args));
14 | }
15 | }
16 | }
17 | );
18 |
--------------------------------------------------------------------------------
/include/renderer.js:
--------------------------------------------------------------------------------
1 | // webview Items
2 | const webview = document.querySelector('webview');
3 |
4 | var buttonForward = document.getElementById('forward');
5 | var buttonBack = document.getElementById('back');
6 | var buttonGo = document.getElementById('go');
7 | var addressBar = document.getElementById('address');
8 |
9 | var rapidFire='';
10 |
11 | var hUrls = [];
12 | hUrls[0]="federalist://";
13 | var curPos = 0;
14 |
15 | webview.addEventListener('dom-ready',()=>{
16 | // webview Actions
17 | buttonGo.onclick = function() {
18 | window.api.send("go", document.getElementById('address').value);
19 | buttonGo.disabled = true;
20 | addressBar.disabled = true;
21 | }
22 | addressBar.onkeyup = function(e) {
23 | if(e.key === 'Enter' || e.keyCode === 13)
24 | buttonGo.click();
25 | }
26 | buttonBack.onclick = function() {
27 | if(webview.canGoBack()) {
28 | curPos--;
29 | addressBar.value=hUrls[curPos];
30 | webview.goBack();
31 | }
32 | toggleButtons();
33 | }
34 | buttonForward.onclick = function() {
35 | if(webview.canGoForward()) {
36 | curPos++;
37 | addressBar.value=hUrls[curPos];
38 | webview.goForward();
39 | }
40 | toggleButtons();
41 | }
42 | toggleButtons();
43 | webview.addEventListener('did-finish-load',(e) => {
44 | window.api.send("title",webview.getTitle() + " - federalist");
45 | });
46 | webview.addEventListener('will-navigate',(e)=>{
47 | webview.stop();
48 | if(rapidFire!=e.url) {
49 | rapidFire=e.url;
50 | setTimeout(()=>{
51 | rapidFire="";
52 | },1000);
53 | if(e.url.substr(0,4)=="http")
54 | window.api.send("external", e.url);
55 | else if(e.url.substr(0,4)=="file" || e.url.substr(0,4)=="magn" || e.url.substr(0,4)=="fede")
56 | window.api.send("go",e.url);
57 | else
58 | doLoadURL(e.url);
59 | }
60 | });
61 | });
62 |
63 |
64 | // IPC
65 | window.api.receive("notfound", (data) => {
66 | webview.src="404.html";
67 | curPos++;
68 | hUrls[curPos]=data;
69 | });
70 | window.api.receive("show", (data) => {
71 | doLoadURL(data);
72 | toggleButtons();
73 | });
74 | window.api.receive("updateAddress", (data) => {
75 | curPos++;
76 | hUrls[curPos]=data;
77 | addressBar.value=data;
78 | });
79 | window.api.receive("enableAddress",() => {
80 | buttonGo.disabled = false;
81 | addressBar.disabled = false;
82 | });
83 | window.api.receive("disableAddress",() => {
84 | buttonGo.disabled = true;
85 | addressBar.disabled = true;
86 | });
87 |
88 |
89 | function doLoadURL(url) {
90 | webview.loadURL(url);
91 | }
92 | function toggleButtons() {
93 | if(webview.canGoBack())
94 | buttonBack.disabled=false;
95 | else
96 | buttonBack.disabled=true;
97 | if(webview.canGoForward())
98 | buttonForward.disabled=false;
99 | else
100 | buttonForward.disabled=true;
101 | }
102 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | const {app, BrowserWindow, shell} = require('electron');
2 | const path = require('path');
3 | const fs = require('fs');
4 | const fse = require('fs-extra');
5 | const ipcMain = require('electron').ipcMain;
6 | const WebTorrent = require('webtorrent');
7 | const hresolver = require("./include/hresolver.js");
8 | const crypto = require('crypto');
9 | const ed = require('supercop.js');
10 | const url = require('url');
11 |
12 | const client = new WebTorrent();
13 | var mainWindow;
14 |
15 | function createWindow () {
16 | mainWindow = new BrowserWindow({
17 | width: 800,
18 | height: 600,
19 | minWidth: 400,
20 | minHeight:600,
21 | webPreferences: {
22 | webviewTag: true,
23 | nodeIntegration: false,
24 | contextIsolation: true,
25 | enableRemoteModule: false,
26 | preload: path.join(__dirname,"include/preload.js")
27 | }
28 | })
29 | mainWindow.on('ready-to-show', function() {
30 | mainWindow.show();
31 | });
32 | mainWindow.loadFile('assets/index.html')
33 | }
34 | app.whenReady().then(() => {
35 | createWindow();
36 | app.on('activate', function () {
37 | if (BrowserWindow.getAllWindows().length === 0) createWindow();
38 | });
39 | });
40 | app.on('window-all-closed', function () {
41 | if (process.platform !== 'darwin') app.quit();
42 | })
43 |
44 |
45 | var TEMP_FOLDER = app.getPath('appData')+"/federalist/";
46 | if(!fs.existsSync(TEMP_FOLDER))
47 | fs.mkdirSync(TEMP_FOLDER);
48 | TEMP_FOLDER+="torrents/";
49 | if(!fs.existsSync(TEMP_FOLDER))
50 | fs.mkdirSync(TEMP_FOLDER);
51 |
52 |
53 | var current = {
54 | host: "",
55 | name: "",
56 | pos: -1
57 | };
58 | function go(hash,path,name) {
59 | let files = getFiles(TEMP_FOLDER+hash+'/web3root');
60 | let displayed = false;
61 | let directory = "# Directory listing";
62 |
63 | if(!path || path=="" || path=="/")
64 | path="/index.html";
65 |
66 | current.host = hash;
67 | current.name = name;
68 |
69 | files.forEach((file)=> {
70 | if(file.substr((TEMP_FOLDER+hash+'/web3root/').length)==path.substr(1)) {
71 | displayed=true;
72 | mainWindow.webContents.send('show','file:///'+TEMP_FOLDER+hash+'/web3root'+path);
73 | }
74 | directory+="- "+file;
75 | });
76 | if(!displayed) {
77 | if(path != "/") {
78 | mainWindow.webContents.send('notfound','404');
79 | mainWindow.webContents.send('updateAddress','404 Not Found');
80 | mainWindow.webContents.send('enableAddress');
81 | return;
82 | }
83 | else
84 | mainWindow.webContents.send('show','file:///'+TEMP_FOLDER+current.host+'/web3root'+path);
85 | }
86 | if(name=='')
87 | mainWindow.webContents.send('updateAddress',"magnet:?xt=urn:btih:"+hash+path);
88 | else
89 | mainWindow.webContents.send('updateAddress',"federalist://"+name+path);
90 | mainWindow.webContents.send('enableAddress');
91 | }
92 | ipcMain.on('title',(event,arg) => {
93 | mainWindow.setTitle(arg);
94 | });
95 | ipcMain.on('external',(event,arg) => { shell.openExternal(arg); });
96 | ipcMain.on('go',async (event,arg) => {
97 | mainWindow.webContents.send('disableAddress');
98 | var federalistData={};
99 | var name='';
100 | var target={};
101 |
102 | if(arg.startsWith('federalist')) {
103 | _tmp = await parseFederalist(arg);
104 | if(_tmp.err) {
105 | mainWindow.webContents.send('enableAddress');
106 | mainWindow.webContents.send('notfound','DNS Error');
107 | mainWindow.webContents.send('updateAddress','DNS Error');
108 | return;
109 | }
110 | arg = _tmp.url;
111 | name = _tmp.name;
112 | }
113 | if(decodeURI(arg).startsWith('file://'+TEMP_FOLDER)) {
114 | arg=decodeURI(arg).substr(('file://'+TEMP_FOLDER).length);
115 | target.hash=arg.substr(0,40);
116 | target.path=arg.substr(49);
117 | target.name=current.name;
118 | }
119 | else {
120 | target = await parseAddress(arg,name);
121 | mainWindow.webContents.send('enableAddress');
122 | mainWindow.webContents.send('notfound','DHT Error');
123 | mainWindow.webContents.send('updateAddress','DHT Error');
124 | }
125 | if(!fs.existsSync(TEMP_FOLDER+target.hash)) {
126 | if(!client.get(target.hash)) {
127 | client.add("magnet:?xt=urn:btih:"+target.hash,{path:TEMP_FOLDER+target.hash},(torrent) => {
128 | if(torrent.length>10000000) {
129 | client.remove(torrent.infoHash);
130 | mainWindow.webContents.send('show','Over 10MB');
131 | return;
132 | }
133 | torrent.on('done',()=> {
134 | go(target.hash,target.path,target.name);
135 | });
136 | });
137 | }
138 | }
139 | else {
140 | let seeding=false;
141 | if (client.get(target.hash))
142 | seeding=true;
143 | if(!seeding) {
144 | client.add("magnet:?xt=urn:btih:"+target.hash,{path:TEMP_FOLDER+target.hash});
145 | }
146 | go(target.hash,target.path,target.name);
147 | }
148 | });
149 |
150 | async function parseAddress(arg,name) {
151 | var target = {};
152 |
153 | if(!arg || arg.length==0)
154 | target.error = true;
155 | else if (arg.startsWith('file://')) {
156 | target.path = arg.substr(("file://"+__dirname).length);
157 | target.hash = current.host;
158 | target.name = current.name;
159 | }
160 | else if (arg.startsWith('magnet:?xt=urn:btih:')) {
161 | target.hash = arg.substr(20,40);
162 | target.path = arg.substr(60);
163 | target.name = name;
164 | }
165 | else if (arg.startsWith('magnet:?xs=urn:btpk:')) {
166 | let tmp = arg.substr(20);
167 | if (tmp.indexOf('/')>0) {
168 | target.hash = await runConsume(arg.substr(20,arg.indexOf('/')));
169 | if(target.hash==="error")
170 | target.err = true;
171 | else {
172 | target.path = tmp.substr(tmp.indexOf('/'));
173 | target.name = name;
174 | }
175 | }
176 | else {
177 | target.hash = await runConsume(arg.substr(20));
178 | if(target.hash==="error")
179 | target.err = true;
180 | else {
181 | target.path = '';
182 | target.name = name;
183 | }
184 | }
185 | }
186 | else
187 | target.err = true;
188 |
189 | return target;
190 | }
191 |
192 | async function parseFederalist(arg) {
193 | let err=false;
194 | let name,path,destination;
195 | if(arg.startsWith('federalist://')) {
196 | let tmp = arg.substr(13);
197 | if(tmp.indexOf('/')>0) {
198 | name = tmp.substr(0,tmp.indexOf('/'));
199 | path = tmp.substr(tmp.indexOf('/'));
200 | }
201 | else {
202 | name = tmp;
203 | path = '';
204 | }
205 |
206 | destination = (await hresolver(name));
207 |
208 | if(!destination || destination == "")
209 | err = true;
210 | else
211 | destination=destination.toString().trim();
212 |
213 | }
214 | else
215 | err = true;
216 |
217 | if(err)
218 | return {err:err};
219 | else
220 | return {url:destination + path, name:name};
221 | }
222 |
223 | async function runConsume(publicKey) {
224 | var buffPubKey = Buffer.from(publicKey, 'hex')
225 | var targetID = crypto.createHash('sha1').update(buffPubKey).digest('hex') // XXX missing salt
226 | var client = new WebTorrent({ dht: {verify: ed.verify }})
227 | client.on('error', () => {
228 | return "error";
229 | });
230 |
231 | var dht = client.dht
232 | return new Promise((resolve,reject) => {
233 | dht.on('ready', async function () {
234 | dht.get(targetID, async function (err, res) {
235 | if (err || !res) {
236 | client.destroy();
237 | reject("error");
238 | }
239 | else {
240 | client.destroy();
241 | resolve(res.v.ih.toString('hex'));
242 | }
243 | });
244 | });
245 | });
246 | }
247 | function getFiles(mpath, mfiles) {
248 | let files = fs.readdirSync(mpath);
249 | mfiles = mfiles || [];
250 | files.forEach((file)=> {
251 | if(fs.statSync(mpath + "/" + file).isDirectory())
252 | mfiles = getFiles(mpath + "/" + file,mfiles);
253 | else
254 | mfiles.push(path.join(mpath,"/",file));
255 | });
256 | return mfiles;
257 | }
258 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "federalist",
3 | "version": "1.0.0",
4 | "description": "Access the federalist, a form of web3",
5 | "main": "main.js",
6 | "scripts": {
7 | "start": "electron ."
8 | },
9 | "keywords": [
10 | "web3",
11 | "federalist",
12 | "handshake",
13 | "bittorrent"
14 | ],
15 | "author": "root",
16 | "license": "MIT",
17 | "dependencies": {
18 | "dohjs": "^0.3.2",
19 | "electron": "^16.0.3",
20 | "marked": "^4.0.5",
21 | "supercop.js": "^2.0.1",
22 | "webtorrent": "^1.5.8"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------