Browse Source

feat(ux): keep app running on window close

The standard approach on Darwin is to keen applications running when the
window is closed. Clicking the apps icon again in the doc should bring
back the application window.

This changeset refactors things to handle this. It will keep Zap and lnd
running in the background when the main application window is closed.
Quitting the app will stop both the app and lnd.

Fix #586
Fix #601
renovate/lint-staged-8.x
Tom Kirkpatrick 7 years ago
parent
commit
b180287f6c
No known key found for this signature in database GPG Key ID: 72203A8EC5967EA8
  1. 16
      app/lnd/index.js
  2. 140
      app/lnd/lib/lightning.js
  3. 25
      app/lnd/lib/util.js
  4. 8
      app/lnd/subscribe/channelgraph.js
  5. 9
      app/lnd/subscribe/index.js
  6. 13
      app/lnd/subscribe/invoices.js
  7. 7
      app/lnd/subscribe/transactions.js
  8. 2
      app/lnd/walletUnlockerMethods/index.js
  9. 44
      app/main.dev.js
  10. 4
      app/reducers/channels.js
  11. 4
      app/reducers/ipc.js
  12. 2
      app/reducers/onboarding.js
  13. 2
      app/reducers/requestform.js
  14. 294
      app/zap.js
  15. 1
      package.json
  16. 20
      yarn.lock

16
app/lnd/index.js

@ -1,24 +1,9 @@
import config from './config'
import lightning from './lib/lightning'
import walletUnlocker from './lib/walletUnlocker'
import subscribe from './subscribe'
import methods from './methods'
import walletUnlockerMethods from './walletUnlockerMethods'
// use mainLog because lndLog is reserved for the lnd binary itself
import { mainLog } from '../utils/log'
const initLnd = async () => {
const lnd = await lightning()
const lndSubscribe = mainWindow => subscribe(mainWindow, lnd, mainLog)
const lndMethods = (event, msg, data) => methods(lnd, mainLog, event, msg, data)
return Promise.resolve({
lndSubscribe,
lndMethods
})
}
const initWalletUnlocker = () => {
const lndConfig = config.lnd()
const walletUnlockerObj = walletUnlocker(lndConfig.rpcProtoPath, lndConfig.host)
@ -29,6 +14,5 @@ const initWalletUnlocker = () => {
}
export default {
initLnd,
initWalletUnlocker
}

140
app/lnd/lib/lightning.js

@ -2,52 +2,112 @@ import grpc from 'grpc'
import { loadSync } from '@grpc/proto-loader'
import config from '../config'
import { getDeadline, validateHost, createSslCreds, createMacaroonCreds } from './util'
import methods from '../methods'
import { mainLog } from '../../utils/log'
import subscribeToTransactions from '../subscribe/transactions'
import subscribeToInvoices from '../subscribe/invoices'
import subscribeToChannelGraph from '../subscribe/channelgraph'
/**
* Creates an LND grpc client lightning service.
* @returns {rpc.lnrpc.Lightning}
* @returns {Lightning}
*/
const lightning = async () => {
const lndConfig = config.lnd()
const { host, rpcProtoPath, cert, macaroon } = lndConfig
// Verify that the host is valid before creating a gRPC client that is connected to it.
return await validateHost(host).then(async () => {
// Load the gRPC proto file.
// The following options object closely approximates the existing behavior of grpc.load.
// See https://github.com/grpc/grpc-node/blob/master/packages/grpc-protobufjs/README.md
const options = {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
class Lightning {
constructor() {
this.mainWindow = null
this.lnd = null
this.subscriptions = {
channelGraph: null,
invoices: null,
transactions: null
}
const packageDefinition = loadSync(rpcProtoPath, options)
// Load gRPC package definition as a gRPC object hierarchy.
const rpc = grpc.loadPackageDefinition(packageDefinition)
// Create ssl and macaroon credentials to use with the gRPC client.
const [sslCreds, macaroonCreds] = await Promise.all([
createSslCreds(cert),
createMacaroonCreds(macaroon)
])
const credentials = grpc.credentials.combineChannelCredentials(sslCreds, macaroonCreds)
// Instantiate a new connection to the Lightning interface.
const lnd = new rpc.lnrpc.Lightning(host, credentials)
// Call the getInfo method to ensure that we can make successful calls to the gRPC interface.
return new Promise((resolve, reject) => {
lnd.getInfo({}, { deadline: getDeadline(2) }, err => {
if (err) {
return reject(err)
}
return resolve(lnd)
}
/**
* Connect to the gRPC interface and verify it is functional.
* @return {Promise<rpc.lnrpc.Lightning>}
*/
async connect() {
const lndConfig = config.lnd()
const { host, rpcProtoPath, cert, macaroon } = lndConfig
// Verify that the host is valid before creating a gRPC client that is connected to it.
return await validateHost(host).then(async () => {
// Load the gRPC proto file.
// The following options object closely approximates the existing behavior of grpc.load.
// See https://github.com/grpc/grpc-node/blob/master/packages/grpc-protobufjs/README.md
const options = {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
}
const packageDefinition = loadSync(rpcProtoPath, options)
// Load gRPC package definition as a gRPC object hierarchy.
const rpc = grpc.loadPackageDefinition(packageDefinition)
// Create ssl and macaroon credentials to use with the gRPC client.
const [sslCreds, macaroonCreds] = await Promise.all([
createSslCreds(cert),
createMacaroonCreds(macaroon)
])
const credentials = grpc.credentials.combineChannelCredentials(sslCreds, macaroonCreds)
// Create a new gRPC client instance.
const lnd = new rpc.lnrpc.Lightning(host, credentials)
// Call the getInfo method to ensure that we can make successful calls to the gRPC interface.
return new Promise((resolve, reject) => {
lnd.getInfo({}, { deadline: getDeadline(2) }, err => {
if (err) {
return reject(err)
}
this.lnd = lnd
return resolve(lnd)
})
})
})
})
}
/**
* Discomnnect the gRPC service.
*/
disconnect() {
this.unsubscribe()
this.lnd.close()
}
/**
* Hook up lnd restful methods.
*/
lndMethods(event, msg, data) {
return methods(this.lnd, mainLog, event, msg, data)
}
/**
* Subscribe to all bi-directional streams.
*/
subscribe(mainWindow) {
this.mainWindow = mainWindow
this.subscriptions.channelGraph = subscribeToChannelGraph(this.mainWindow, this.lnd, mainLog)
this.subscriptions.invoices = subscribeToInvoices(this.mainWindow, this.lnd, mainLog)
this.subscriptions.transactions = subscribeToTransactions(this.mainWindow, this.lnd, mainLog)
}
/**
* Unsubscribe from all bi-directional streams.
*/
unsubscribe() {
Object.keys(this.subscriptions).forEach(subscription => {
if (this.subscriptions[subscription]) {
this.subscriptions[subscription].cancel()
this.subscriptions[subscription] = null
}
})
this.mainWindow = null
}
}
export default lightning
export default Lightning

25
app/lnd/lib/util.js

@ -2,6 +2,7 @@ import dns from 'dns'
import fs from 'fs'
import axios from 'axios'
import { promisify } from 'util'
import { lookup } from 'ps-node'
import path from 'path'
import grpc from 'grpc'
import isIP from 'validator/lib/isIP'
@ -137,3 +138,27 @@ export const createMacaroonCreds = async macaroonPath => {
callback(null, metadata)
)
}
/**
* Check to see if an LND process is running.
* @return {Promise} Boolean indicating wether an existing lnd process was found on the host machine.
*/
export const isLndRunning = () => {
return new Promise((resolve, reject) => {
mainLog.info('Looking for existing lnd process')
lookup({ command: 'lnd' }, (err, results) => {
// There was an error checking for the LND process.
if (err) {
return reject(err)
}
if (!results.length) {
// An LND process was found, no need to start our own.
mainLog.info('Existing lnd process not found')
return resolve(false)
}
mainLog.info('Found existing lnd process')
return resolve(true)
})
})
}

8
app/lnd/subscribe/channelgraph.js

@ -1,8 +1,14 @@
export default function subscribeToChannelGraph(mainWindow, lnd) {
import { status } from 'grpc'
export default function subscribeToChannelGraph(mainWindow, lnd, log) {
const call = lnd.subscribeChannelGraph({})
call.on('data', channelGraphData => mainWindow.send('channelGraphData', { channelGraphData }))
call.on('end', () => log.info('end'))
call.on('error', error => error.code !== status.CANCELLED && log.error(error))
call.on('status', channelGraphStatus =>
mainWindow.send('channelGraphStatus', { channelGraphStatus })
)
return call
}

9
app/lnd/subscribe/index.js

@ -1,9 +0,0 @@
import subscribeToTransactions from './transactions'
import subscribeToInvoices from './invoices'
import subscribeToChannelGraph from './channelgraph'
export default (mainWindow, lnd, log) => {
subscribeToTransactions(mainWindow, lnd, log)
subscribeToInvoices(mainWindow, lnd, log)
subscribeToChannelGraph(mainWindow, lnd)
}

13
app/lnd/subscribe/invoices.js

@ -1,8 +1,15 @@
import { status } from 'grpc'
export default function subscribeToInvoices(mainWindow, lnd, log) {
const call = lnd.subscribeInvoices({})
call.on('data', invoice => mainWindow.send('invoiceUpdate', { invoice }))
call.on('data', invoice => {
log.info('INVOICE:', invoice)
mainWindow.send('invoiceUpdate', { invoice })
})
call.on('end', () => log.info('end'))
call.on('error', error => log.error(error))
call.on('status', status => log.info('status:', status))
call.on('error', error => error.code !== status.CANCELLED && log.error(error))
call.on('status', status => log.info('INVOICE STATUS:', status))
return call
}

7
app/lnd/subscribe/transactions.js

@ -1,10 +1,15 @@
import { status } from 'grpc'
export default function subscribeToTransactions(mainWindow, lnd, log) {
const call = lnd.subscribeTransactions({})
call.on('data', transaction => {
log.info('TRANSACTION:', transaction)
mainWindow.send('newTransaction', { transaction })
})
call.on('end', () => log.info('end'))
call.on('error', error => log.error('error: ', error))
call.on('error', error => error.code !== status.CANCELLED && log.error(error))
call.on('status', status => log.info('TRANSACTION STATUS: ', status))
return call
}

2
app/lnd/walletUnlockerMethods/index.js

@ -33,7 +33,7 @@ export default function(walletUnlocker, log, event, msg, data) {
case 'initWallet':
walletController
.initWallet(walletUnlocker, data)
.then(() => event.sender.send('successfullyCreatedWallet'))
.then(() => event.sender.send('finishOnboarding'))
.catch(error => log.error('initWallet:', error))
break
default:

44
app/main.dev.js

@ -14,7 +14,6 @@ import ZapUpdater from './updater'
// Set up a couple of timers to track the app startup progress.
mainLog.time('Time until app is ready')
mainLog.time('Time until lnd process lookup finished')
/**
* Initialize Zap as soon as electron is ready.
@ -22,11 +21,7 @@ mainLog.time('Time until lnd process lookup finished')
app.on('ready', () => {
mainLog.timeEnd('Time until app is ready')
// Start a couple more timers to track the app loading time.
mainLog.time('Time until app is visible')
mainLog.time('Time until onboarding has started')
// Create the electron browser window.
// Create a new browser window.
const mainWindow = new BrowserWindow({
show: false,
titleBarStyle: 'hidden',
@ -71,22 +66,11 @@ app.on('ready', () => {
mainLog.error
)
mainWindow.webContents.once('dom-ready', () => {
mainWindow.openDevTools()
zap.mainWindow.webContents.once('dom-ready', () => {
zap.mainWindow.openDevTools()
})
}
/**
* Add application event listener:
* - Kill lnd process is killed when the app quits.
*/
app.on('quit', () => {
mainLog.debug('app.quit')
if (zap.neutrino) {
zap.neutrino.stop()
}
})
/**
* Add application event listener:
* - Open zap payment form when lightning url is opened
@ -97,7 +81,7 @@ app.on('ready', () => {
event.preventDefault()
const payreq = url.split(':')[1]
zap.sendMessage('lightningPaymentUri', { payreq })
mainWindow.show()
zap.mainWindow.show()
})
// HACK: patch webrequest to fix devtools incompatibility with electron 2.x.
@ -126,4 +110,24 @@ app.on('ready', () => {
app.quit()
}
})
/**
* Add application event listener:
* - Stop gRPC and kill lnd process before the app windows are closed and the app quits.
*/
app.on('before-quit', async event => {
if (zap.state !== 'terminated') {
event.preventDefault()
zap.terminate()
} else {
zap.mainWindow.forceClose = true
}
})
/**
* On OS X it's common to re-open a window in the app when the dock icon is clicked.
*/
app.on('activate', () => {
zap.mainWindow.show()
})
})

4
app/reducers/channels.js

@ -1,7 +1,7 @@
import { createSelector } from 'reselect'
import { ipcRenderer } from 'electron'
import { btc } from 'utils'
import { showNotification } from 'notifications'
import { showNotification } from '../notifications'
import { btc } from '../utils'
import { requestSuggestedNodes } from '../api'
import { setError } from './error'
// ------------------------------------

4
app/reducers/ipc.js

@ -44,7 +44,7 @@ import {
walletUnlockerGrpcActive,
receiveSeed,
receiveSeedError,
successfullyCreatedWallet,
finishOnboarding,
walletUnlocked,
unlockWalletError
} from './onboarding'
@ -111,7 +111,7 @@ const ipc = createIpc({
walletUnlockerGrpcActive,
receiveSeed,
receiveSeedError,
successfullyCreatedWallet,
finishOnboarding,
walletUnlocked,
unlockWalletError
})

2
app/reducers/onboarding.js

@ -260,7 +260,7 @@ export const createWallet = () => dispatch => {
dispatch({ type: CHANGE_STEP, step: 4 })
}
export const successfullyCreatedWallet = () => dispatch => dispatch({ type: ONBOARDING_FINISHED })
export const finishOnboarding = () => dispatch => dispatch({ type: ONBOARDING_FINISHED })
// Listener for when LND creates and sends us a generated seed
export const receiveSeed = (event, { cipher_seed_mnemonic }) => dispatch => {

2
app/reducers/requestform.js

@ -1,5 +1,5 @@
import { createSelector } from 'reselect'
import { btc } from 'utils'
import { btc } from '../utils'
import { tickerSelectors } from './ticker'
// Initial State

294
app/zap.js

@ -1,7 +1,9 @@
import { app, ipcMain, dialog } from 'electron'
import Store from 'electron-store'
import StateMachine from 'javascript-state-machine'
import lnd from './lnd'
import Neutrino from './lnd/lib/neutrino'
import Lightning from './lnd/lib/lightning'
import { mainLog } from './utils/log'
import { isLndRunning } from './lnd/lib/util'
@ -37,7 +39,7 @@ const grpcSslCipherSuites = connectionType =>
class ZapController {
/**
* Create a new ZapController instance.
* @param {BrowserWindow} mainWindow BrowserWindow instance to interact with.
* @param {BrowserWindow} mainWindow BrowserWindow instance to interact with.
*/
constructor(mainWindow) {
// Variable to hold the main window instance.
@ -46,14 +48,24 @@ class ZapController {
// Keep a reference any neutrino process started by us.
this.neutrino = null
// Keep a reference to the lightning gRPC instance.
this.lightning = null
// Time for the splash screen to remain visible.
this.splashScreenTime = 500
// Boolean indicating wether the lightning grpc is connected ot not.
this.lightningGrpcConnected = false
// Initialize the state machine.
this._fsm()
}
/**
* Initialize the application.
* Initialize the controller.
*/
init() {
// Load the application into the main window.
if (process.env.HOT) {
const port = process.env.PORT || 1212
this.mainWindow.loadURL(`http://localhost:${port}/dist/index.html`)
@ -68,51 +80,123 @@ class ZapController {
this.mainWindow.webContents.on('did-finish-load', () => {
this.mainWindow.show()
this.mainWindow.focus()
mainLog.timeEnd('Time until app is visible')
return setTimeout(async () => {
this.sendMessage('startOnboarding')
}, this.splashScreenTime)
// Show the splash screen and then start onboarding.
setTimeout(() => this.startOnboarding(), this.splashScreenTime)
})
this.mainWindow.on('closed', () => {
this.mainWindow = null
// When the window is closed, just hide it unless we are force closing.
this.mainWindow.on('close', e => {
if (this.mainWindow.forceClose) {
return
}
e.preventDefault()
this.mainWindow.hide()
})
}
// shut down zap when a user closes the window
app.quit()
// ------------------------------------
// FSM Callbacks
// ------------------------------------
onStartOnboarding() {
mainLog.debug('[FSM] onStartOnboarding...')
this.sendMessage('startOnboarding')
}
onStartLnd(options) {
mainLog.debug('[FSM] onStartLnd...')
return isLndRunning().then(res => {
if (res) {
mainLog.error('lnd already running: %s', res)
dialog.showMessageBox({
type: 'error',
message: 'Unable to start lnd because it is already running.'
})
return app.quit()
}
mainLog.info('Starting new lnd instance')
mainLog.info(' > alias:', options.alias)
mainLog.info(' > autopilot:', options.autopilot)
return this.startNeutrino(options.alias, options.autopilot)
})
}
onConnectLnd(options) {
mainLog.debug('[FSM] onConnectLnd...')
mainLog.info('Connecting to custom lnd instance')
mainLog.info(' > host:', options.host)
mainLog.info(' > cert:', options.cert)
mainLog.info(' > macaroon:', options.macaroon)
this.startLightningWallet()
.then(() => this.sendMessage('finishOnboarding'))
.catch(e => {
const errors = {}
// There was a problem connectig to the host.
if (e.code === 'LND_GRPC_HOST_ERROR') {
errors.host = e.message
}
// There was a problem accessing loading the ssl cert.
if (e.code === 'LND_GRPC_CERT_ERROR') {
errors.cert = e.message
}
// There was a problem accessing loading the macaroon file.
else if (e.code === 'LND_GRPC_MACAROON_ERROR') {
errors.macaroon = e.message
}
// Other error codes such as UNAVAILABLE most likely indicate that there is a problem with the host.
else {
errors.host = `Unable to connect to host: ${e.details || e.message}`
}
// Notify the app of errors.
return this.sendMessage('startLndError', errors)
})
}
onTerminated() {
mainLog.debug('[FSM] onTerminated...')
// Unsubscribe the gRPC streams before thhe window closes. This ensures that we can properly reestablish a fresh
// connection when a new window is opened.
this.disconnectLightningWallet()
// If Neutrino is running, kill it.
if (this.neutrino) {
this.neutrino.stop()
}
// Give the grpc connections a chance to be properly closed out.
return new Promise(resolve => setTimeout(resolve, 200))
}
onTerminate() {
mainLog.debug('[FSM] onTerminate...')
app.quit()
}
// ------------------------------------
// Helpers
// ------------------------------------
/**
* Send a message to the main window.
* @param {string} msg message to send.
* @param {[type]} data additional data to acompany the message.
*/
sendMessage(msg, data) {
mainLog.info('Sending message to renderer process: %o', { msg, data })
this.mainWindow.webContents.send(msg, data)
}
/**
* Create and subscribe to the Lightning grpc object.
*/
async startLightningWallet() {
mainLog.info('Starting lightning wallet...')
const { lndSubscribe, lndMethods } = await lnd.initLnd()
// Subscribe to bi-directional streams
lndSubscribe(this.mainWindow)
// Listen for all gRPC restful methods
ipcMain.on('lnd', (event, { msg, data }) => {
lndMethods(event, msg, data)
})
this.sendMessage('lightningGrpcActive')
if (this.mainWindow) {
mainLog.info('Sending message to renderer process: %o', { msg, data })
this.mainWindow.webContents.send(msg, data)
} else {
mainLog.warn('Unable to send message to renderer process (main window not available): %o', {
msg,
data
})
}
}
/**
* Create and subscribe to the WalletUnlocker grpc object.
* Start the wallet unlocker.
*/
startWalletUnlocker() {
mainLog.info('Starting wallet unlocker...')
@ -124,6 +208,7 @@ class ZapController {
walletUnlockerMethods(event, msg, data)
})
// Notify the renderer that the wallet unlocker is active.
this.sendMessage('walletUnlockerGrpcActive')
} catch (error) {
dialog.showMessageBox({
@ -134,13 +219,54 @@ class ZapController {
}
}
/**
* Create and subscribe to the Lightning service.
*/
async startLightningWallet() {
mainLog.info('Starting lightning wallet...')
this.lightning = new Lightning()
// Connect to the Lightning interface.
await this.lightning.connect()
// Subscribe the main window to receive streams.
this.lightning.subscribe(this.mainWindow)
// Listen for all gRPC restful methods and pass to gRPC.
ipcMain.on('lnd', (event, { msg, data }) => this.lightning.lndMethods(event, msg, data))
// Let the renderer know that we are connected.
this.sendMessage('lightningGrpcActive')
// Update our internal state.
this.lightningGrpcConnected = true
}
/**
* Unsubscribe from the Lightning service.
*/
disconnectLightningWallet() {
if (!this.lightningGrpcConnected) {
return
}
mainLog.info('Disconnecting lightning Wallet...')
// Disconnect streams.
this.lightning.unsubscribe()
// Update our internal state.
this.lightningGrpcConnected = false
}
/**
/**
* Starts the LND node and attach event listeners.
* @param {string} alias Alias to assign to the lnd node.
* @param {boolean} autopilot True if autopilot should be enabled.
* @return {Neutrino} Neutrino instance.
*/
startLnd(alias, autopilot) {
startNeutrino(alias, autopilot) {
mainLog.info('Starting Neutrino...')
this.neutrino = new Neutrino(alias, autopilot)
this.neutrino.on('error', error => {
@ -153,7 +279,13 @@ class ZapController {
this.neutrino.on('close', code => {
mainLog.info(`Lnd process has shut down (code ${code})`)
app.quit()
if (['running', 'connected'].includes(this.state)) {
dialog.showMessageBox({
type: 'error',
message: `Lnd has unexpectadly quit`
})
this.terminate()
}
})
this.neutrino.on('wallet-unlocker-grpc-active', () => {
@ -192,75 +324,43 @@ class ZapController {
this.neutrino.start()
}
finishOnboarding(options) {
// Trim any user supplied strings.
const cleanOptions = Object.keys(options).reduce((previous, current) => {
previous[current] =
typeof options[current] === 'string' ? options[current].trim() : options[current]
return previous
}, {})
// Save the options.
const store = new Store({ name: 'connection' })
store.store = cleanOptions
mainLog.info('Saved lnd config to %s: %o', store.path, store.store)
// Set up SSL with the cypher suits that we need based on the connection type.
process.env.GRPC_SSL_CIPHER_SUITES =
process.env.GRPC_SSL_CIPHER_SUITES || grpcSslCipherSuites(options.type)
// If the requested connection type is a local one then start up a new lnd instance.
// // Otherwise attempt to connect to an lnd instance using user supplied connection details.
cleanOptions.type === 'local' ? this.startLnd() : this.connectLnd()
}
/**
* Add IPC event listeners...
*/
_registerIpcListeners() {
ipcMain.on('startLnd', (event, options = {}) => {
// Trim any user supplied strings.
const cleanOptions = Object.keys(options).reduce((previous, current) => {
previous[current] =
typeof options[current] === 'string' ? options[current].trim() : options[current]
return previous
}, {})
// Save the options.
const store = new Store({ name: 'connection' })
store.store = cleanOptions
mainLog.info('Saved lnd config to %s: %o', store.path, store.store)
process.env.GRPC_SSL_CIPHER_SUITES =
process.env.GRPC_SSL_CIPHER_SUITES || grpcSslCipherSuites(options.type)
// If the requested connection type is a local one then start up a new lnd instance.
if (cleanOptions.type === 'local') {
return isLndRunning().then(res => {
if (res) {
mainLog.error('lnd already running: %s', res)
dialog.showMessageBox({
type: 'error',
message: 'Unable to start lnd because it is already running.'
})
return app.quit()
}
mainLog.info('Starting new lnd instance')
mainLog.info(' > alias:', cleanOptions.alias)
mainLog.info(' > autopilot:', cleanOptions.autopilot)
return this.startLnd(cleanOptions.alias, cleanOptions.autopilot)
})
}
// Otherwise attempt to connect to an lnd instance using user supplied connection details.
mainLog.info('Connecting to custom lnd instance')
mainLog.info(' > host:', cleanOptions.host)
mainLog.info(' > cert:', cleanOptions.cert)
mainLog.info(' > macaroon:', cleanOptions.macaroon)
this.startLightningWallet()
.then(() => this.sendMessage('successfullyCreatedWallet'))
.catch(e => {
const errors = {}
// There was a problem connectig to the host.
if (e.code === 'LND_GRPC_HOST_ERROR') {
errors.host = e.message
}
// There was a problem accessing loading the ssl cert.
if (e.code === 'LND_GRPC_CERT_ERROR') {
errors.cert = e.message
}
// There was a problem accessing loading the macaroon file.
else if (e.code === 'LND_GRPC_MACAROON_ERROR') {
errors.macaroon = e.message
}
// Other error codes such as UNAVAILABLE most likely indicate that there is a problem with the host.
else {
errors.host = `Unable to connect to host: ${e.details || e.message}`
}
// Notify the app of errors.
return this.sendMessage('startLndError', errors)
})
})
ipcMain.on('startLnd', (event, options = {}) => this.finishOnboarding(options))
}
}
StateMachine.factory(ZapController, {
transitions: [
{ name: 'startOnboarding', from: 'none', to: 'onboarding' },
{ name: 'startLnd', from: 'onboarding', to: 'running' },
{ name: 'connectLnd', from: 'onboarding', to: 'connected' },
{ name: 'terminate', from: '*', to: 'terminated' }
]
})
export default ZapController

1
package.json

@ -282,6 +282,7 @@
"electron-store": "^2.0.0",
"font-awesome": "^4.7.0",
"history": "^4.6.3",
"javascript-state-machine": "^3.1.0",
"lodash.get": "^4.4.2",
"moment": "^2.22.2",
"prop-types": "^15.5.10",

20
yarn.lock

@ -3142,6 +3142,10 @@ connect-history-api-fallback@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz#b06873934bc5e344fef611a196a6faae0aee015a"
connected-domain@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/connected-domain/-/connected-domain-1.0.0.tgz#bfe77238c74be453a79f0cb6058deeb4f2358e93"
console-browserify@1.1.x, console-browserify@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10"
@ -6630,6 +6634,10 @@ isurl@^1.0.0-alpha5:
has-to-string-tag-x "^1.2.0"
is-object "^1.0.1"
javascript-state-machine@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/javascript-state-machine/-/javascript-state-machine-3.1.0.tgz#06eeb2136a6a19ae1b56105c25caec283dd5cd14"
jest-changed-files@^23.0.1:
version "23.0.1"
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-23.0.1.tgz#f79572d0720844ea5df84c2a448e862c2254f60c"
@ -9587,6 +9595,12 @@ prr@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
ps-node@^0.1.6:
version "0.1.6"
resolved "https://registry.yarnpkg.com/ps-node/-/ps-node-0.1.6.tgz#9af67a99d7b1d0132e51a503099d38a8d2ace2c3"
dependencies:
table-parser "^0.1.3"
pseudomap@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
@ -11453,6 +11467,12 @@ symbol-tree@^3.2.1, symbol-tree@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
table-parser@^0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/table-parser/-/table-parser-0.1.3.tgz#0441cfce16a59481684c27d1b5a67ff15a43c7b0"
dependencies:
connected-domain "^1.0.0"
table@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/table/-/table-4.0.1.tgz#a8116c133fac2c61f4a420ab6cdf5c4d61f0e435"

Loading…
Cancel
Save