├── .gitignore ├── LICENSE ├── README.md ├── app ├── index.js ├── package.json ├── swarm.js └── yarn.lock ├── assets ├── icon.icns └── icon.ico ├── config ├── env.js ├── jest │ ├── cssTransform.js │ └── fileTransform.js ├── paths.js ├── polyfills.js ├── webpack.config.dev.js ├── webpack.config.prod.js └── webpackDevServer.config.js ├── docs └── architecture.png ├── electron-builder.json ├── package.json ├── prettier.config.js ├── public ├── favicon.ico ├── index.html └── manifest.json ├── scripts ├── build.js ├── build_binaries.sh ├── start.js └── test.js ├── src ├── Onyx.js ├── colors.js ├── components │ ├── AddContactModal.js │ ├── App.js │ ├── Avatar.js │ ├── CertSelectionModal.js │ ├── ColoredLine.js │ ├── Contact.js │ ├── Conversation.js │ ├── CreateChannelModal.js │ ├── EditProfile.js │ ├── FileSelector.js │ ├── Form │ │ ├── Button.js │ │ └── TextInput.js │ ├── Icon.js │ ├── LeftNav │ │ ├── ContactListLabel.js │ │ ├── ConversationTitle.js │ │ ├── Profile.js │ │ └── SectionTitle.js │ ├── Loader.js │ ├── MainframeBar.js │ ├── Modal.js │ ├── NoStakeIcon.js │ ├── NodeConnectionView.js │ ├── Text.js │ ├── UserProfile.js │ ├── UserProfileModal.js │ └── icons │ │ ├── arrow-right.svg │ │ ├── camera_icon.svg │ │ ├── checkmark.svg │ │ ├── circled-cross.svg │ │ ├── download.svg │ │ ├── file-dark.svg │ │ ├── file-red.svg │ │ ├── file.svg │ │ ├── flash-blue.svg │ │ ├── flash-gray.svg │ │ ├── flash-red.svg │ │ ├── generic-file.svg │ │ ├── mainframe-icon.svg │ │ ├── mainframe-logo.svg │ │ ├── mask-blue.svg │ │ ├── mask-gray.svg │ │ ├── mask-red.svg │ │ ├── pdf.svg │ │ ├── plus.svg │ │ ├── red-close.svg │ │ ├── rolling.svg │ │ └── see-qr.svg ├── constants.js ├── data │ ├── Apollo.js │ ├── Electron.js │ ├── Navigation.js │ └── Store.js ├── fonts │ ├── Muli-Bold.woff2 │ ├── Muli-Regular.woff2 │ ├── Muli-SemiBold.woff2 │ ├── Poppins-Medium.woff2 │ ├── Poppins-Regular.woff2 │ └── Poppins-SemiBold.woff2 ├── graphql │ ├── fragments.js │ └── mutations.js ├── index.css ├── index.js └── styles.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /dist 3 | /tmp 4 | /bin 5 | /app/build 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 ThusFresh Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # onyx 2 | 3 | Decentralized messaging application using PSS. 4 | 5 | ## Introduction 6 | 7 | Onyx is a taster for our next phase of development on a fully-decentralized & incentivized network. It relies on a secure messaging protocol in the Ethereum core called [PSS](https://github.com/ethersphere/go-ethereum/tree/swarm-network-rewrite/swarm/pss). 8 | 9 | ## Installation 10 | 11 | To install Onyx, download and install the latest release binaries for your platform from our [releases page](https://github.com/MainframeHQ/onyx/releases). 12 | 13 | When you first install Onyx, you will be required to stake a Mainframe token (MFT) in order to connect and communicate with other nodes. Onyx guides you through this process. You will first need to create a wallet and make sure it has at least 1 MFT and a little ETH to submit the transaction with. This can be done on [MyCrypto](https://mycrypto.com). You will be required to perform two transactions on the ethereum network. 14 | 15 | ### Step 1 - Approve deposit of 1 MFT 16 | 17 | Once you have the wallet ready, open the Onyx application and click the button entitled "Step 1 - Approve deposit of 1 MFT." This will open MyCrypto with the required transaction data pre-loaded. Before you submit the approval transaction, make sure you set the gas price high enough for the transaction to go through in a timely manner. This depends on network congestion. [ETH Gas Station](https://ethgasstation.info) is a good place to go for current average gas prices. 18 | 19 | 20 | ### Step 2 - Deposit 1 MFT and whitelist node 21 | 22 | After submitting the transaction, wait for it to be processed. Then click "Step 2 - Deposit 1 MFT and whitelist node." NOTE: do not alter the auto-generated address in the grey box. This will submit another transaction to MyCrypto, this time to perform the actual deposit so that your node can be allowed to use the Mainframe network. 23 | 24 | ### Step 3 - Restart local node 25 | 26 | After the transaction is complete, click on "Restart local node." You should now be able to use Onyx and connect with other Onyx users. 27 | 28 | ## Known issues 29 | 30 | This application is an alpha product and is currently suitable for testing purposes only. As such, there is no guarantee of: 31 | 32 | * **Security**: Our codebase is not fully tested. We authenticate both the client app and the mailboxing service and use TLS between the two, but any intruder who succeeded in accessing a remotely-installed service could read your messages, as they are stored in plaintext. Messages are transmitted via [PSS](https://github.com/ethersphere/go-ethereum/tree/pss/swarm/pss), which is intended to be highly secure but is still beta software. 33 | * **Reliability**: PSS does not provide deliverability guarantees. When remotely installed, however, the [onyx-server](https://github.com/MainframeHQ/onyx-server) is designed to store messages sent to you while you are offline. As long as PSS delivers them successfully to your mailboxing service, they should be waiting for you when you open your desktop or mobile app again. If you are running in the default mode, which runs the mailbox service only locally, any messages sent to you while your app is not running will be lost. 34 | * **Performance**: We have not sufficiently tested this version for large-scale use. All messages are stored in a global state file that gets updated with each new message that is received. We anticipate that this will not scale well. The message store was created quickly for the alpha, and will require a more robust implementation in our next phase of development. 35 | 36 | PSS & Swarm are currently in rapid development and as such, the build of swarm that we're currently using (to have a working PSS implementation) has the following known limitations from what is expected in the next release of swarm: 37 | 38 | * **Network Formation**: The network discovery protocol is not currently usable which means that the nodes in the network cannot optimise their routing tables to ensure good routing of messages. In order to ensure routing despite this, all "client" nodes are configured to connect to the network through the same permanent bootnode. 39 | * **File Storage**: The file storage functionality is not currently usable which means that files uploaded to swarm would not be fetchable later. In order to retain the file upload feature, we are running a separate swarm gateway on https://onyx-storage.mainframe.com using a different build of swarm with usable file storage. 40 | 41 | ## Get in touch! 42 | 43 | Although this release is not officially supported, we really want to hear your feedback. If you become aware of a bug or have a great idea about a feature that would make Onyx more awesome, please submit an issue on our [issues page](https://github.com/MainframeHQ/onyx/issues). 44 | 45 | ## Architecture 46 | 47 | Onyx is a privacy-focused messaging application that combines the desired features of today's best messaging tools while also maintaining the highest level of security and user sovereignty. The product consists of front-end client apps (mobile and desktop) that connect to a p2p networking node. This node can be run on the user's desktop (inside the Onyx electron wrapper) or deployed to the cloud. 48 | 49 | ![Onyx Architecture](docs/architecture.png) 50 | 51 | An Onyx node consists of a p2p node with a messaging layer and various services necessary for storing messages and managing contacts. It makes use of the [Swarm](https://github.com/ethersphere/swarm) distributed storage platform and [PSS](https://github.com/ethersphere/go-ethereum/tree/pss/swarm/pss) secure messaging protocol for message delivery and file storage. Later development milestones will include more rich messaging features, and incentivization for reliable message delivery. 52 | 53 | PSS is a connectionless communication protocol that provides dark routing capabilities in addition to conventional cryptography. A configurable level of per-message routing information allows senders to choose how specific they wish to be about whom their message is addressed to. By omitting or partially omitting the recipient’s address, messages are delivered to all matching addresses, thus increasing the difficulty of identifying both sender and receiver amidst numerous duplicate messages, or of targeting specific nodes for attack or disruption. These features enable extremely secure communications. Given networks of sufficient size, dark routing makes it virtually impossible to detect messaging activity between specific nodes. The only reliable means of disrupting this communication is to disable general Internet access. 54 | 55 | ## Development 56 | 57 | After you pull this repository, to install all the dependencies run 58 | 59 | ``` 60 | yarn 61 | ``` 62 | 63 | The app requires an [onyx-server](https://github.com/MainframeHQ/onyx-server) instance to connect to - you will be prompted when you state the app. This can be run separately, following the instructions in that repository, or can be run locally inside the electron process. To run locally, binaries for geth and swarm are required, and can be built from a known good version for the local architecture using the yarn task: 64 | 65 | ``` 66 | yarn build:binaries 67 | ``` 68 | 69 | Run 70 | 71 | ``` 72 | yarn start 73 | ``` 74 | 75 | to start the development server for the frontend. It will serve it on `localhost:3000`. 76 | 77 | Once ready, you can start the electron app with 78 | 79 | ``` 80 | yarn electron 81 | ``` 82 | 83 | ### Project structure 84 | 85 | * `app`: electron app 86 | * `assets`: build assets (app icons) 87 | * `dist`: app builds 88 | * `public`: assets that will be added to the `build` folder 89 | * `src`: source code 90 | 91 | ### Releases 92 | 93 | To build a release for the local architecture, run 94 | 95 | ``` 96 | yarn build:binaries 97 | yarn dist 98 | ``` 99 | 100 | To cross-compile for MacOS, Windows, Linux, run 101 | 102 | ``` 103 | yarn build:binaries:mwl 104 | yarn dist -mwl 105 | ``` 106 | 107 | ## Contributing 108 | 109 | Thanks for your interest in our project! Feel free to examine our list of potential enhancements on our [issues page](https://github.com/MainframeHQ/onyx/issues) and help us implement them. Feel free to submit an feature request or bug report yourself as well. 110 | 111 | ## Acknowledgments 112 | 113 | Mainframe wishes to thank the Swarm core team, including Viktor Trón, Louis Holbrook, and Lewis Marshall, as well as the JAAK team, who helped us test the group messaging features for our presentation at Devcon3 on November 4, 2017 in Cancun, Mexico. 114 | 115 | ## License 116 | 117 | [MIT](LICENSE) 118 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow, dialog, ipcMain, Menu, shell } = require('electron') 2 | const fetch = require('node-fetch') 3 | const { appReady, is } = require('electron-util') 4 | const Store = require('electron-store') 5 | const getPort = require('get-port') 6 | const createOnyxServer = require('onyx-server').default 7 | const StaticServer = require('static-server') 8 | const path = require('path') 9 | const querystring = require('querystring') 10 | 11 | const { config, version } = require(path.join(__dirname, 'package.json')) 12 | const swarm = require('./swarm') 13 | 14 | const VERSION_CHECK_URL = 'https://mainframe.com/onyx-version.json' 15 | 16 | const USE_TESTNET = process.env.USE_TESTNET || false 17 | 18 | const SWARM_WS_URL = 19 | process.env.SWARM_WS_URL || 20 | (config && config.swarmWsUrl) || 21 | 'ws://localhost:8546' 22 | const SWARM_HTTP_URL = 23 | process.env.SWARM_HTTP_URL || 24 | (config && config.swarmHttpUrl) || 25 | 'https://onyx-staking-storage.mainframe.com' 26 | 27 | const checkUpdates = () => { 28 | fetch(VERSION_CHECK_URL) 29 | .then(res => { 30 | if (res.ok) { 31 | return res.json() 32 | } else { 33 | throw new Error('Error loading version') 34 | } 35 | }) 36 | .then(data => { 37 | if (data.versions.includes(version)) { 38 | dialog.showMessageBox( 39 | mainWindow, 40 | { 41 | type: 'warning', 42 | title: data.title || 'Update Available', 43 | message: 44 | data.description || 'A new version of Onyx has been released!', 45 | cancelId: 0, 46 | buttons: ['Cancel', data.button || 'Download'], 47 | }, 48 | async index => { 49 | if (index === 1) { 50 | shell.openExternal( 51 | data.url || 'https://github.com/MainframeHQ/onyx/releases', 52 | ) 53 | } 54 | }, 55 | ) 56 | } 57 | }) 58 | .catch(() => { 59 | console.warn('Error fetching updates data') 60 | }) 61 | } 62 | 63 | let appServer, mainWindow 64 | 65 | const shouldQuit = app.makeSingleInstance(() => { 66 | if (mainWindow) { 67 | if (mainWindow.isMinimized()) mainWindow.restore() 68 | mainWindow.focus() 69 | } 70 | }) 71 | 72 | if (shouldQuit) { 73 | app.quit() 74 | } 75 | 76 | const store = new Store({ name: is.development ? 'onyx-dev' : 'onyx' }) 77 | 78 | const menu = Menu.buildFromTemplate([ 79 | { 80 | label: is.macos ? app.getName() : 'File', 81 | submenu: [ 82 | { 83 | label: 'v' + app.getVersion(), 84 | enabled: false, 85 | }, 86 | { 87 | type: 'separator', 88 | }, 89 | { 90 | label: 'Reset', 91 | click: (item, focusedWindow) => { 92 | dialog.showMessageBox( 93 | focusedWindow, 94 | { 95 | type: 'warning', 96 | message: 97 | 'Resetting the application will clear ALL data and your local Swarm identity. Are you sure you want to continue?', 98 | cancelId: 0, 99 | buttons: ['Cancel', 'Confirm'], 100 | }, 101 | async index => { 102 | if (index === 1) { 103 | store.delete('wsUrl') 104 | store.delete('certs') 105 | await swarm.reset() 106 | start() 107 | } 108 | }, 109 | ) 110 | }, 111 | }, 112 | { 113 | label: 'Quit', 114 | accelerator: 'CmdOrCtrl+Q', 115 | click: () => { 116 | app.quit() 117 | }, 118 | }, 119 | ], 120 | }, 121 | { 122 | label: 'Edit', 123 | submenu: [ 124 | { label: 'Undo', accelerator: 'CmdOrCtrl+Z', selector: 'undo:' }, 125 | { label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', selector: 'redo:' }, 126 | { type: 'separator' }, 127 | { label: 'Cut', accelerator: 'CmdOrCtrl+X', selector: 'cut:' }, 128 | { label: 'Copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:' }, 129 | { label: 'Paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:' }, 130 | { 131 | label: 'Select All', 132 | accelerator: 'CmdOrCtrl+A', 133 | selector: 'selectAll:', 134 | }, 135 | ], 136 | }, 137 | { 138 | label: 'View', 139 | submenu: [ 140 | { 141 | label: 'Reload', 142 | accelerator: 'CmdOrCtrl+R', 143 | click: (item, focusedWindow) => { 144 | if (focusedWindow) focusedWindow.reload() 145 | }, 146 | }, 147 | { 148 | label: 'Toggle Developer Tools', 149 | accelerator: is.macos ? 'Alt+Command+I' : 'Ctrl+Shift+I', 150 | click: (item, focusedWindow) => { 151 | if (focusedWindow) focusedWindow.toggleDevTools() 152 | }, 153 | }, 154 | { type: 'separator' }, 155 | { role: 'togglefullscreen' }, 156 | { role: 'resetzoom' }, 157 | { role: 'zoomin' }, 158 | { role: 'zoomout' }, 159 | ], 160 | }, 161 | ]) 162 | 163 | const createMainWindow = async url => { 164 | await appReady 165 | 166 | Menu.setApplicationMenu(menu) 167 | 168 | mainWindow = new BrowserWindow({ width: 800, height: 600, show: false }) 169 | mainWindow.loadURL(url) 170 | 171 | mainWindow.on('ready-to-show', () => { 172 | mainWindow.show() 173 | checkUpdates() 174 | }) 175 | mainWindow.on('closed', () => { 176 | swarm.stop() 177 | app.quit() 178 | mainWindow = null 179 | }) 180 | } 181 | 182 | const startAppServer = async () => { 183 | const appPort = await getPort() 184 | const appServer = new StaticServer({ 185 | rootPath: path.join(__dirname, 'build'), 186 | port: appPort, 187 | }) 188 | 189 | return new Promise(resolve => { 190 | appServer.start(() => { 191 | app.on('quit', () => { 192 | appServer.stop() 193 | }) 194 | resolve(appServer) 195 | }) 196 | }) 197 | } 198 | 199 | const startLocalOnyxServer = async () => { 200 | const port = await getPort() 201 | await createOnyxServer({ 202 | wsUrl: SWARM_WS_URL, 203 | httpUrl: SWARM_HTTP_URL, 204 | port, 205 | store, 206 | unsecure: true, 207 | testNet: USE_TESTNET, 208 | }) 209 | return port 210 | } 211 | 212 | const start = async () => { 213 | let appPort 214 | if (is.development) { 215 | appPort = 3000 216 | } else { 217 | if (appServer == null) { 218 | appServer = await startAppServer() 219 | } 220 | appPort = appServer.port 221 | } 222 | let appUrl = `http://localhost:${appPort}` 223 | let urlParams = { testNet: USE_TESTNET } 224 | let nodeAddress 225 | 226 | const stakeRequiredError = 'You need to stake Mainframe tokens for your node' 227 | 228 | const storedWsUrl = store.get('wsUrl') 229 | if (storedWsUrl) { 230 | if (storedWsUrl === 'local') { 231 | let errorMsg 232 | 233 | try { 234 | await swarm.setup() 235 | } catch (e) { 236 | console.log(e.stack) 237 | errorMsg = 238 | 'There was an error setting up the Geth account\nDebug: ' + 239 | e.toString() 240 | } 241 | 242 | if (!errorMsg) { 243 | try { 244 | await swarm.start() 245 | } catch (e) { 246 | console.log(e.stack) 247 | if (e.message.includes('No stake found')) { 248 | nodeAddress = e.message 249 | .split(' ') 250 | .splice(-1)[0] 251 | .trim() 252 | errorMsg = stakeRequiredError 253 | } else { 254 | errorMsg = 255 | 'There was an error starting the local Swarm node, you may want to check if you have a Swarm node running already, or if the port 30399 is in use\nDebug: ' + 256 | e.toString() 257 | } 258 | } 259 | } 260 | 261 | if (!errorMsg) { 262 | try { 263 | const serverPort = await startLocalOnyxServer(appPort) 264 | urlParams.wsUrl = `ws://localhost:${serverPort}/graphql` 265 | } catch (e) { 266 | console.log(e.stack) 267 | swarm.stop() 268 | if (e.message.startsWith('Missing stake')) { 269 | nodeAddress = e.address 270 | errorMsg = stakeRequiredError 271 | } else { 272 | errorMsg = 273 | 'There was an error starting the local GraphQL server, you may want to check you have a Swarm node WebSocket interface listening on ' + 274 | SWARM_WS_URL + 275 | '\nDebug: ' + 276 | e.toString() 277 | } 278 | } 279 | } 280 | 281 | if (errorMsg) { 282 | urlParams = { 283 | address: nodeAddress, 284 | wsUrl: storedWsUrl, 285 | connectionError: errorMsg, 286 | testNet: USE_TESTNET, 287 | } 288 | } 289 | } else { 290 | // Use stored remote server url 291 | let domain 292 | if (storedWsUrl.indexOf('://') !== -1) { 293 | domain = storedWsUrl.split('/')[2] 294 | } else if (storedWsUrl.indexOf('/') !== -1) { 295 | domain = storedWsUrl.split('/')[0] 296 | } 297 | urlParams.wsUrl = storedWsUrl 298 | if (!domain) { 299 | urlParams.connectionError = 'Invalid ws url' 300 | } 301 | } 302 | } 303 | 304 | if (urlParams != null) { 305 | appUrl += '?' + querystring.stringify(urlParams) 306 | } 307 | 308 | if (mainWindow == null) { 309 | createMainWindow(appUrl) 310 | } else { 311 | mainWindow.loadURL(appUrl) 312 | } 313 | 314 | app.on('activate', () => { 315 | if (mainWindow == null) { 316 | createMainWindow(appUrl) 317 | } 318 | }) 319 | 320 | app.on( 321 | 'certificate-error', 322 | (event, webContents, url, error, certificate, callback) => { 323 | console.warn('cert error: ', error) 324 | }, 325 | ) 326 | } 327 | 328 | ipcMain.on('onSetWsUrl', (e, url) => { 329 | store.set('wsUrl', url) 330 | start() 331 | }) 332 | 333 | start() 334 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Onyx", 3 | "version": "0.5.2", 4 | "description": "Decentralized messaging application based on PSS", 5 | "main": "index.js", 6 | "author": "Mainframe", 7 | "dependencies": { 8 | "electron-store": "^1.3.0", 9 | "electron-util": "^0.8.0", 10 | "execa": "^0.10.0", 11 | "fs-extra": "^5.0.0", 12 | "get-port": "^3.2.0", 13 | "mkdirp": "^0.5.1", 14 | "node-fetch": "^2.1.2", 15 | "onyx-server": "^0.5.0", 16 | "static-server": "^3.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/swarm.js: -------------------------------------------------------------------------------- 1 | const { app } = require('electron') 2 | const { is } = require('electron-util') 3 | const execa = require('execa') 4 | const { 5 | createWriteStream, 6 | ensureDir, 7 | pathExists, 8 | readdir, 9 | readJson, 10 | remove, 11 | writeFile, 12 | } = require('fs-extra') 13 | const path = require('path') 14 | const os = require('os') 15 | 16 | const platform = { 17 | darwin: 'mac', 18 | linux: 'linux', 19 | win32: 'win', 20 | }[os.platform()] 21 | 22 | const getBinPath = is.development 23 | ? name => path.join(app.getAppPath(), '..', 'bin', `${name}-${platform}`) 24 | : name => path.join(process.resourcesPath, 'bin', name) 25 | 26 | const dataDir = path.join(app.getPath('userData'), 'data') 27 | const pwdPath = path.join(dataDir, 'pwd') 28 | const keystorePath = path.join(dataDir, 'keystore') 29 | const logPath = path.join(dataDir, 'node.log') 30 | const gethPath = getBinPath('geth') 31 | const swarmPath = getBinPath('swarm') 32 | 33 | let proc 34 | 35 | const setup = async () => { 36 | if (await pathExists(keystorePath)) { 37 | console.log('keystore exists, skip setup') 38 | return 39 | } 40 | 41 | await ensureDir(dataDir) 42 | await writeFile(pwdPath, 'secret') 43 | await execa(gethPath, [ 44 | '--datadir', 45 | dataDir, 46 | '--password', 47 | pwdPath, 48 | 'account', 49 | 'new', 50 | ]) 51 | } 52 | 53 | const start = async () => { 54 | const keystoreFiles = await readdir(keystorePath) 55 | const keyFilePath = path.join(keystorePath, keystoreFiles[0]) 56 | const keystore = await readJson(keyFilePath) 57 | 58 | return new Promise((resolve, reject) => { 59 | proc = execa(swarmPath, [ 60 | '--datadir', 61 | dataDir, 62 | '--password', 63 | pwdPath, 64 | '--bzzaccount', 65 | keystore.address, 66 | '--verbosity', 67 | '4', 68 | '--bzznetworkid', 69 | '1000', 70 | '--bootnodes', 71 | 'enode://b3417a20b07104ce2f948d7458fc03c73ad5918ae6be24eaf840322c70ffa8d0f59473139bef8ef0b4caffe7bc99018ab3686b1a757d73c1a7bfd880d2b7e7ef@52.31.117.198:30399,enode://762d482f6a400c89210be4d180b192dd5f921ca9f1a42a1651293f242613874f3e4e22589be582e0837c816c0c5366c00c32b7760ca345d65eb9ed75897db8c0@54.153.70.43:30399,enode://fc8d3eb2d5cfe4ed05c9e722518c895e006456448a49c2915e30beffd1ddb1ee17cd12abd550f1745d8b17060bf6c46e822e051ae4deca0e21d7fa7a15bb4c43@13.113.67.30:30399', 72 | '--ws', 73 | '--wsorigins', 74 | '*', 75 | '--ens-api', 76 | 'https://mainnet.infura.io/55HkPWVAJQjGH4ucvfW9', 77 | '--nosync', 78 | ]) 79 | 80 | proc.catch(error => { 81 | console.error('Failed to start Swarm node: ', error.stderr) 82 | reject(error.stderr) 83 | }) 84 | 85 | proc.stderr.pipe(createWriteStream(logPath)) 86 | 87 | proc.stdout.on('data', data => { 88 | const dataStr = data.toString() 89 | if (dataStr.toLowerCase().indexOf('fatal:') !== -1) { 90 | const error = new Error(`Swarm error: ${dataStr}`) 91 | console.log(error) 92 | reject(error) 93 | } 94 | }) 95 | 96 | proc.stderr.on('data', data => { 97 | if ( 98 | data 99 | .toString() 100 | .toLowerCase() 101 | .indexOf('websocket endpoint opened') !== -1 102 | ) { 103 | console.log('Swarm node started') 104 | resolve() 105 | } 106 | }) 107 | }) 108 | } 109 | 110 | const stop = () => { 111 | return proc 112 | ? new Promise(resolve => { 113 | proc.once('exit', resolve) 114 | proc.kill() 115 | proc = undefined 116 | }) 117 | : Promise.resolve() 118 | } 119 | 120 | const reset = async () => { 121 | await stop() 122 | await remove(dataDir) 123 | await setup() 124 | } 125 | 126 | module.exports = { reset, setup, start, stop } 127 | -------------------------------------------------------------------------------- /assets/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mainframe-Archive/onyx/9c9caeab89fb3f5b8454023a5465f428590126f3/assets/icon.icns -------------------------------------------------------------------------------- /assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mainframe-Archive/onyx/9c9caeab89fb3f5b8454023a5465f428590126f3/assets/icon.ico -------------------------------------------------------------------------------- /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. 31 | // https://github.com/motdotla/dotenv 32 | dotenvFiles.forEach(dotenvFile => { 33 | if (fs.existsSync(dotenvFile)) { 34 | require('dotenv').config({ 35 | path: dotenvFile, 36 | }); 37 | } 38 | }); 39 | 40 | // We support resolving modules according to `NODE_PATH`. 41 | // This lets you use absolute paths in imports inside large monorepos: 42 | // https://github.com/facebookincubator/create-react-app/issues/253. 43 | // It works similar to `NODE_PATH` in Node itself: 44 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 45 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 46 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 47 | // https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 48 | // We also resolve them to make sure all tools using them work consistently. 49 | const appDirectory = fs.realpathSync(process.cwd()); 50 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 51 | .split(path.delimiter) 52 | .filter(folder => folder && !path.isAbsolute(folder)) 53 | .map(folder => path.resolve(appDirectory, folder)) 54 | .join(path.delimiter); 55 | 56 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 57 | // injected into the application via DefinePlugin in Webpack configuration. 58 | const REACT_APP = /^REACT_APP_/i; 59 | 60 | function getClientEnvironment(publicUrl) { 61 | const raw = Object.keys(process.env) 62 | .filter(key => REACT_APP.test(key)) 63 | .reduce( 64 | (env, key) => { 65 | env[key] = process.env[key]; 66 | return env; 67 | }, 68 | { 69 | // Useful for determining whether we’re running in production mode. 70 | // Most importantly, it switches React into the correct mode. 71 | NODE_ENV: process.env.NODE_ENV || 'development', 72 | // Useful for resolving the correct path to static assets in `public`. 73 | // For example, . 74 | // This should only be used as an escape hatch. Normally you would put 75 | // images into the `src` and `import` them in code to get their paths. 76 | PUBLIC_URL: publicUrl, 77 | } 78 | ); 79 | // Stringify all values so we can feed into Webpack DefinePlugin 80 | const stringified = { 81 | 'process.env': Object.keys(raw).reduce((env, key) => { 82 | env[key] = JSON.stringify(raw[key]); 83 | return env; 84 | }, {}), 85 | }; 86 | 87 | return { raw, stringified }; 88 | } 89 | 90 | module.exports = getClientEnvironment; 91 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 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