├── .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 | ![](https://github.com/rodocite/decentralized-react-chat/blob/master/chatty.png) 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