├── .gitignore
├── README.md
├── chatty.png
├── config
├── env.js
├── paths.js
├── polyfills.js
├── webpack.config.dev.js
├── webpack.config.prod.js
└── webpackDevServer.config.js
├── geth
├── package-lock.json
├── package.json
├── public
└── index.html
├── scripts
├── build.js
└── start.js
└── src
├── App.js
├── actions
└── index.js
├── global.styles.css
├── index.js
├── reducers
└── index.js
├── utils
└── index.js
└── whisper.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 | /dist
12 |
13 | # misc
14 | .DS_Store
15 | .env.local
16 | .env.development.local
17 | .env.test.local
18 | .env.production.local
19 | .key
20 | .crt
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Decentralized React Chat
2 |
3 | 
4 |
5 | The motivation for this project is to explore creating a decentralized app (DApp) using the Ethereum Blockchain Platform. It's my first decentralized application!
6 |
7 | ## Whisper Protocol
8 | The project uses the [Whisper Protocol](https://github.com/ethereum/wiki/wiki/Whisper) on Ethereum. It does not require Smart Contracts. Whisper uses a blockchain along with a distributed hash table (DHT) to provide secure, non-location based routing for communications. It supports unicast, multicast, and broadcast. Whisper's focus is security and is not meant to be performant-- although it is probably sufficient in most use-cases for light communication.
9 |
10 | ## Communication Flow Summary
11 | Two or more users would share a topic, a symmetrical key, and respective public keys with each other. All three are hashed together and sent into a DHT. The protocol listens to messages matching those hashes. When a match is found, the messages are routed to the parties listening.
12 |
13 | ## Project Details
14 | The project is an unpacked `create-react-app` and uses the [Web3.js](https://github.com/ethereum/web3.js/) library to communicate with a local `geth` process. [geth](https://github.com/ethereum/go-ethereum/wiki/geth) is a golang Ethereum runtime.
15 |
16 | #### geth
17 | I've included the `geth` binary in the repo for convenience. A popular library [ganache](https://github.com/trufflesuite/ganache-cli) does not yet have a Whisper implementation so `geth` seemed like my only option.
18 |
19 | The project runs `geth` in rpc development mode with websockets enabled. Websockets are required for the Whisper's subscribe functionality. Otherwise, the protocol would be limited to unicast only.
20 |
21 | All the chat-related code is in `/src`.
22 |
23 | #### Symmetrical Keys, Public Keys, Topics
24 | The project uses a static topic `0xffaadd11` as a common topic. However, the symmetrical key and the public keys are generated on mount. Users will need to join rooms via the symmetrical key -- called `Room Hash` in the application. Symmetrical keys, public keys, and topics are required to be in hexadecimal format when being sent into the protocol.
25 |
26 | To display identities, I've "encapsulated" each message with a `name` required from each user. The client just splits it from the converted readable string coming back from Whisper.
27 |
28 | #### Settings - TTL, Proof of Work, etc
29 | There are several settings for the protocol. For the most part, I kept them at default because they were ideal for a chat project. TTL, for example, is at 10 seconds.
30 |
31 | I plan to start another project to go in depth with topics, hashes, pow, and much longer ttl. Possibly a Twitter style app.
32 |
33 | #### Deploy Issues
34 | - Since `geth` does not support SSL, you need something like nginx to be able to deploy this app over https or write your own server certificates which is beyond the scope of this project.
35 |
36 | - There is an issue w/ Dockerizing the project due to a Web3 websocket dependency pointing to a repo without a commit hash. I'm still investigating the issue.
37 |
38 | - Note that Infura, a popular dapp deployment platform uses ganache under the hood and thus, does not support Whisper.
39 |
40 | ## Running the Project
41 | Yarn seems to have issues with the Web3 library. Use `npm`.
42 |
43 | ```
44 | npm install
45 | npm start
46 | ```
47 |
48 | ## Todo
49 | - Write a wrapper for web3js.shh
--------------------------------------------------------------------------------
/chatty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rodocite/decentralized-react-chat/7cc0abcf493adfd476c99abb20716c88f4840fb3/chatty.png
--------------------------------------------------------------------------------
/config/env.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 | const paths = require('./paths');
6 |
7 | // Make sure that including paths.js after env.js will read .env variables.
8 | delete require.cache[require.resolve('./paths')];
9 |
10 | const NODE_ENV = process.env.NODE_ENV;
11 | if (!NODE_ENV) {
12 | throw new Error(
13 | 'The NODE_ENV environment variable is required but was not specified.'
14 | );
15 | }
16 |
17 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
18 | var dotenvFiles = [
19 | `${paths.dotenv}.${NODE_ENV}.local`,
20 | `${paths.dotenv}.${NODE_ENV}`,
21 | // Don't include `.env.local` for `test` environment
22 | // since normally you expect tests to produce the same
23 | // results for everyone
24 | NODE_ENV !== 'test' && `${paths.dotenv}.local`,
25 | paths.dotenv,
26 | ].filter(Boolean);
27 |
28 | // Load environment variables from .env* files. Suppress warnings using silent
29 | // if this file is missing. dotenv will never modify any environment variables
30 | // that have already been set. Variable expansion is supported in .env files.
31 | // https://github.com/motdotla/dotenv
32 | // https://github.com/motdotla/dotenv-expand
33 | dotenvFiles.forEach(dotenvFile => {
34 | if (fs.existsSync(dotenvFile)) {
35 | require('dotenv-expand')(
36 | require('dotenv').config({
37 | path: dotenvFile,
38 | })
39 | );
40 | }
41 | });
42 |
43 | // We support resolving modules according to `NODE_PATH`.
44 | // This lets you use absolute paths in imports inside large monorepos:
45 | // https://github.com/facebookincubator/create-react-app/issues/253.
46 | // It works similar to `NODE_PATH` in Node itself:
47 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
48 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored.
49 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims.
50 | // https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421
51 | // We also resolve them to make sure all tools using them work consistently.
52 | const appDirectory = fs.realpathSync(process.cwd());
53 | process.env.NODE_PATH = (process.env.NODE_PATH || '')
54 | .split(path.delimiter)
55 | .filter(folder => folder && !path.isAbsolute(folder))
56 | .map(folder => path.resolve(appDirectory, folder))
57 | .join(path.delimiter);
58 |
59 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
60 | // injected into the application via DefinePlugin in Webpack configuration.
61 | const REACT_APP = /^REACT_APP_/i;
62 |
63 | function getClientEnvironment(publicUrl) {
64 | const raw = Object.keys(process.env)
65 | .filter(key => REACT_APP.test(key))
66 | .reduce(
67 | (env, key) => {
68 | env[key] = process.env[key];
69 | return env;
70 | },
71 | {
72 | // Useful for determining whether we’re running in production mode.
73 | // Most importantly, it switches React into the correct mode.
74 | NODE_ENV: process.env.NODE_ENV || 'development',
75 | // Useful for resolving the correct path to static assets in `public`.
76 | // For example,
.
77 | // This should only be used as an escape hatch. Normally you would put
78 | // images into the `src` and `import` them in code to get their paths.
79 | PUBLIC_URL: publicUrl,
80 | }
81 | );
82 | // Stringify all values so we can feed into Webpack DefinePlugin
83 | const stringified = {
84 | 'process.env': Object.keys(raw).reduce((env, key) => {
85 | env[key] = JSON.stringify(raw[key]);
86 | return env;
87 | }, {}),
88 | };
89 |
90 | return { raw, stringified };
91 | }
92 |
93 | module.exports = getClientEnvironment;
94 |
--------------------------------------------------------------------------------
/config/paths.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const fs = require('fs');
5 | const url = require('url');
6 |
7 | // Make sure any symlinks in the project folder are resolved:
8 | // https://github.com/facebookincubator/create-react-app/issues/637
9 | const appDirectory = fs.realpathSync(process.cwd());
10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
11 |
12 | const envPublicUrl = process.env.PUBLIC_URL;
13 |
14 | function ensureSlash(path, needsSlash) {
15 | const hasSlash = path.endsWith('/');
16 | if (hasSlash && !needsSlash) {
17 | return path.substr(path, path.length - 1);
18 | } else if (!hasSlash && needsSlash) {
19 | return `${path}/`;
20 | } else {
21 | return path;
22 | }
23 | }
24 |
25 | const getPublicUrl = appPackageJson =>
26 | envPublicUrl || require(appPackageJson).homepage;
27 |
28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer
29 | // "public path" at which the app is served.
30 | // Webpack needs to know it to put the right