Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IPC #572

Merged
merged 21 commits into from
Mar 29, 2018
Merged

IPC #572

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 46 additions & 50 deletions app/src/main/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict'

let { app, BrowserWindow } = require('electron')
let { app, BrowserWindow, ipcMain } = require('electron')
let fs = require('fs-extra')
let { join } = require('path')
let { spawn } = require('child_process')
Expand All @@ -13,17 +13,17 @@ let axios = require('axios')
let Raven = require('raven')

let pkg = require('../../../package.json')
let relayServer = require('./relayServer.js')
let addMenu = require('./menu.js')
let config = require('../../../config.js')

let started = false
let shuttingDown = false
let mainWindow
let lcdProcess
let streams = []
let nodeIP
let connecting = false
let connecting = true
let crashingError = null
let seeds = null

const root = require('../root.js')
const networkPath = require('../network.js').path
Expand All @@ -34,11 +34,9 @@ const DEV = process.env.NODE_ENV === 'development'
const TEST = process.env.NODE_ENV === 'testing'
// TODO default logging or default disable logging?
const LOGGING = JSON.parse(process.env.LOGGING || 'true') !== false
const MOCK = JSON.parse(process.env.MOCK || DEV) !== false
const winURL = DEV
? `http://localhost:${config.wds_port}`
: `file://${__dirname}/index.html`
const RELAY_PORT = DEV ? config.relay_port : config.relay_port_prod
const LCD_PORT = DEV ? config.lcd_port : config.lcd_port_prod
const NODE = process.env.COSMOS_NODE
const ANALYTICS = process.env.COSMOS_ANALYTICS ? JSON.parse(process.env.COSMOS_ANALYTICS) : (process.env.NODE_ENV === 'production' && config.analytics_networks.indexOf(config.default_network) !== -1)
Expand Down Expand Up @@ -85,7 +83,8 @@ function expectCleanExit (process, errorMessage = 'Process exited unplanned') {
}

function handleCrash (error) {
mainWindow.loadURL(winURL + '?node=' + nodeIP + '&error=' + error.message)
crashingError = error
mainWindow.webContents.send('error', error)
}

function shutdown () {
Expand All @@ -105,10 +104,6 @@ function shutdown () {
)
}

function startVueApp () {
mainWindow.loadURL(winURL + '?node=' + nodeIP + '&relay_port=' + RELAY_PORT)
}

function createWindow () {
mainWindow = new BrowserWindow({
minWidth: 320,
Expand All @@ -122,15 +117,11 @@ function createWindow () {
webPreferences: { webSecurity: false }
})

if (!started) {
mainWindow.loadURL(winURL)
} else {
startVueApp()
}
mainWindow.loadURL(winURL + '?node=' + nodeIP + '&lcd_port=' + LCD_PORT)

if (DEV || JSON.parse(process.env.COSMOS_DEVTOOLS || 'false')) {
mainWindow.webContents.openDevTools()
}

if (DEV) {
mainWindow.maximize()
}
Expand Down Expand Up @@ -325,6 +316,21 @@ function consistentConfigDir (appVersionPath, genesisPath, configPath, gaiaVersi
exists(gaiaVersionPath)
}

function handleIPC () {
ipcMain.on('successful-launch', () => {
console.log('[START SUCCESS] Vue app successfuly started')
})
ipcMain.on('reconnect', function (event) { return reconnect(seeds) })
ipcMain.on('booted', (event) => {
// if the webcontent shows after we have connected to a node or produced, we need to send those events again
if (crashingError) {
event.sender.send('error', crashingError)
} else if (!connecting && nodeIP) {
event.sender.send('connected', nodeIP)
}
})
}

// check if LCD is initialized as the configs could be corrupted
// we need to parse the error on initialization as there is no way to just get this status programmatically
function lcdInitialized (home) {
Expand Down Expand Up @@ -363,6 +369,12 @@ async function connect (seeds, nodeIP) {
lcdProcess = await startLCD(lcdHome, nodeIP)
log('gaia server ready')

console.log('connected')

mainWindow.webContents.send('connected', nodeIP)

connecting = false

return nodeIP
}

Expand All @@ -373,7 +385,7 @@ async function reconnect (seeds) {
let nodeAlive = false
while (!nodeAlive) {
let nodeIP = pickNode(seeds)
nodeAlive = await axios('http://' + nodeIP, { timeout: 3000 })
nodeAlive = await axios.get('http://' + nodeIP, { timeout: 3000 })
.then(() => true, () => false)
log(`${new Date().toLocaleTimeString()} ${nodeIP} is ${nodeAlive ? 'alive' : 'down'}`)

Expand All @@ -385,8 +397,6 @@ async function reconnect (seeds) {

await connect(seeds, nodeIP)

connecting = false

return nodeIP
}

Expand All @@ -412,6 +422,9 @@ async function main () {

setupLogging(root)

// handle ipc messages from the renderer process
handleIPC()

let init = true
if (rootExists) {
log(`root exists (${root})`)
Expand Down Expand Up @@ -493,7 +506,7 @@ async function main () {
throw new Error(`Can't open config.toml: ${e.message}`)
}
let configTOML = toml.parse(configText)
let seeds = configTOML.p2p.seeds.split(',').filter(x => x !== '')
seeds = configTOML.p2p.seeds.split(',').filter(x => x !== '')
if (seeds.length === 0) {
throw new Error('No seeds specified in config.toml')
}
Expand All @@ -506,34 +519,17 @@ async function main () {
await initLCD(chainId, lcdHome, nodeIP)
}

await connect(seeds, nodeIP)

// the view can communicate with the main process by sending requests to the relay server
// the relay server also proxies to the LCD
relayServer({
lcdPort: LCD_PORT,
relayServerPort: RELAY_PORT,
mock: MOCK,
onSuccesfulStart: () => {
console.log('[START SUCCESS] Vue app successfuly started')
},
onReconnectReq: reconnect.bind(this, seeds)
})
console.log('connecting')

started = true
if (mainWindow) {
startVueApp()
}
await connect(seeds, nodeIP)
}
module.exports = Object.assign(
main()
.catch(err => {
logError(err)
handleCrash(err)
})
.then(() => ({
shutdown,
processes: { lcdProcess },
analytics: ANALYTICS
}))
)
module.exports = main()
.catch(err => {
logError(err)
handleCrash(err)
})
.then(() => ({
shutdown,
processes: { lcdProcess },
analytics: ANALYTICS
}))
30 changes: 0 additions & 30 deletions app/src/main/relayServer.js

This file was deleted.

17 changes: 8 additions & 9 deletions app/src/renderer/connectors/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
const RestClient = require('./lcdClient.js')
const mockedRestClient = require('./lcdClientMock.js')
const RpcWrapper = require('./rpcWrapper.js')
const mockedRpcWrapper = require('./rpcWrapperMock.js')
const MockedRpcWrapper = require('./rpcWrapperMock.js')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is our standard for capital vs camel casing?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used capital here, as it is a constructor-like function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general only classes and constants should have capital letters


module.exports = function (nodeIP, relayPort, mocked = false) {
const RELAY_SERVER = 'http://localhost:' + relayPort
module.exports = function (lcdPort, mocked = false) {
const LCD_SERVER = 'http://localhost:' + lcdPort

let connector = {
relayPort,
lcdPort,
// activate or deactivate the mocked lcdClient
setMocked: (mocked) => {
let newRestClient = mocked ? mockedRestClient : new RestClient(RELAY_SERVER)
let newRpcClient = mocked ? mockedRpcWrapper(connector) : RpcWrapper(connector, nodeIP, RELAY_SERVER)
setup: (mocked) => {
let newRestClient = mocked ? mockedRestClient : new RestClient(LCD_SERVER)
let newRpcClient = mocked ? MockedRpcWrapper(connector) : RpcWrapper(connector)
Object.assign(connector, newRestClient, newRpcClient)
// we can't assign class functions to an object so we need to iterate over the prototype
if (!mocked) {
Expand All @@ -24,7 +24,6 @@ module.exports = function (nodeIP, relayPort, mocked = false) {
}
// TODO: eventually, get all data from light-client connection instead of RPC

connector.setMocked(mocked)
connector.initRPC(nodeIP)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why don't we need to call initRPC anymore?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

init is called only after the main process signals, that it has connected to a node

connector.setup(mocked)
return connector
}
37 changes: 11 additions & 26 deletions app/src/renderer/connectors/rpcWrapper.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
const RpcClient = require('tendermint')
const axios = require('axios')
const { ipcRenderer } = require('electron')

module.exports = function setRpcWrapper (container, nodeIP, relayServerAddress) {
module.exports = function setRpcWrapper (container) {
let rpcWrapper = {
// RPC
rpcInfo: {
nodeIP,
nodeIP: undefined,
connecting: false,
connected: true
},
initRPC (nodeIP) {
rpcConnect (nodeIP) {
rpcWrapper.rpcInfo.nodeIP = nodeIP

if (container.rpc) {
console.log('removing old websocket')

// ignore disconnect error
container.rpc.removeAllListeners('error')
container.rpc.on('error', () => {})
container.rpc.on('error', () => { })

container.rpc.ws.destroy()
}

console.log('init rpc with', nodeIP)
let newRpc = new RpcClient(`ws://${nodeIP}`)
rpcWrapper.rpcInfo.connected = true
// we need to check immediately if he connection fails. later we will not be able to check this error
// we need to check immediately if the connection fails. later we will not be able to check this error
newRpc.on('error', err => {
console.log('rpc error', err)
if (err.code === 'ECONNREFUSED' || err.code === 'ENETUNREACH') {
Expand All @@ -33,32 +35,15 @@ module.exports = function setRpcWrapper (container, nodeIP, relayServerAddress)

container.rpc = newRpc
},
rpcReconnect: async (alreadyConnecting = rpcWrapper.rpcInfo.connecting) => {
if (alreadyConnecting) return null
rpcReconnect: (alreadyConnecting = rpcWrapper.rpcInfo.connecting) => {
if (alreadyConnecting) return
rpcWrapper.rpcInfo.connecting = true

console.log('trying to reconnect')

let nodeIP = (await axios(relayServerAddress + '/reconnect')).data
if (nodeIP) {
console.log('Reconnected to', nodeIP)
rpcWrapper.rpcInfo.nodeIP = nodeIP
rpcWrapper.initRPC(nodeIP)
} else {
console.log('Reconnection failed, trying again')
// try again in 3s
await sleep(3000)
return rpcWrapper.rpcReconnect(false)
}

rpcWrapper.rpcInfo.connecting = false
return nodeIP
ipcRenderer.send('reconnect')
}
}

return rpcWrapper
}

function sleep (ms = 0) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
4 changes: 2 additions & 2 deletions app/src/renderer/connectors/rpcWrapperMock.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ let state = { blockMetas: [], blocks: [] }
createBlockMetas(state)

const RpcClientMock = {
on: () => {},
on: () => { },
subscribe: (args, cb) => {
if (args.query === "tm.event = 'NewBlock'") {
produceBlocks(cb)
Expand All @@ -31,7 +31,7 @@ module.exports = function setRPCWrapperMock (container) {
connecting: false,
connected: true
},
initRPC () {
rpcConnect () {
container.rpc = RpcClientMock
},
rpcReconnect: async () => {
Expand Down
Loading