Skip to content
This repository has been archived by the owner on Nov 17, 2023. It is now read-only.

Commit

Permalink
fix(app): ensure single app instance
Browse files Browse the repository at this point in the history
Fix #229
  • Loading branch information
mrfelton committed Dec 19, 2018
1 parent 4055e7a commit e3c13de
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 137 deletions.
4 changes: 2 additions & 2 deletions app/lib/zap/controller.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @flow

import os from 'os'
import { app, ipcMain, dialog, BrowserWindow } from 'electron'
import pick from 'lodash.pick'
import StateMachine from 'javascript-state-machine'
Expand Down Expand Up @@ -109,7 +109,7 @@ class ZapController {

// When the window is closed, just hide it unless we are force closing.
this.mainWindow.on('close', e => {
if (process.platform === 'darwin' && !this.mainWindow.forceClose) {
if (os.platform() === 'darwin' && !this.mainWindow.forceClose) {
e.preventDefault()
this.mainWindow.hide()
}
Expand Down
3 changes: 2 additions & 1 deletion app/lib/zap/menuBuilder.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// @flow
import os from 'os'
import { app, Menu, shell, BrowserWindow, ipcMain } from 'electron'
import { getLanguageName, locales } from '../i18n'

Expand All @@ -17,7 +18,7 @@ export default class ZapMenuBuilder {
}

let template
if (process.platform === 'darwin') {
if (os.platform() === 'darwin') {
template = this.buildDarwinTemplate()
} else {
template = this.buildDefaultTemplate()
Expand Down
305 changes: 171 additions & 134 deletions app/main.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import installExtension, {
import get from 'lodash.get'
import path from 'path'
import url from 'url'
import os from 'os'
import querystring from 'querystring'
import { mainLog } from './lib/utils/log'
import ZapMenuBuilder from './lib/zap/menuBuilder'
Expand All @@ -22,14 +23,9 @@ import ZapUpdater from './lib/zap/updater'
import themes from './themes'
import { getDbName } from './store/db'

// Set up a couple of timers to track the app startup progress.
mainLog.time('Time until app is ready')

/**
* Fetch user settings from indexedDb.
* We do this by starting up a new browser window and accessing indexedDb from within it.
*
* @return {[type]} 'settings' store from indexedDb.
*/
const fetchSettings = () => {
const win = new BrowserWindow({ show: false, focusable: false })
Expand Down Expand Up @@ -84,163 +80,204 @@ const fetchSettings = () => {
})
}

/**
* Helper method to fetch a a settings property value.'
*/
const getSetting = (store, key) => {
const setting = store.find(s => s.key === key)
return setting && setting.hasOwnProperty('value') ? setting.value : null
}

/**
* Initialize Zap as soon as electron is ready.
* If we are not able to get a single instnace lock, quit immediately.
*/
app.on('ready', async () => {
mainLog.timeEnd('Time until app is ready')

// Get the users preference so that we can:
// - set the background colour of the window to avoid unwanted flicker.
// - Initialise the Language menu with the users locale selected by default.
let theme = {}
let locale

if (!process.env.DISABLE_INIT) {
try {
const settings = await fetchSettings()
locale = getSetting(settings, 'locale')
const themeKey = getSetting(settings, 'theme')
theme = themes[themeKey]
} catch (e) {
mainLog.warn('Unable to determine user locale and theme', e)
}
}

// Create a new browser window.
const mainWindow = new BrowserWindow({
show: false,
useContentSize: true,
titleBarStyle: 'hidden',
width: 1020,
height: 680,
minWidth: 900,
minHeight: 425,
backgroundColor: get(theme, 'colors.primaryColor', '#242633'),
webPreferences: {
preload: path.resolve(__dirname, 'preload.js')
}
})

// Initialise the updater.
const updater = new ZapUpdater(mainWindow)
updater.init()

// Initialise the application.
const zap = new ZapController(mainWindow)
zap.init()
const singleInstanceLock = app.requestSingleInstanceLock()
if (!singleInstanceLock) {
mainLog.error('Unable to get single instance lock. It looks like you already have Zap open?')
app.quit()
}

// Initialise the application menus.
const menuBuilder = new ZapMenuBuilder(mainWindow)
menuBuilder.buildMenu(locale)
// Otherwise, go ahead and initialize the app.
else {
// Set up a couple of timers to track the app startup progress.
mainLog.time('Time until app is ready')

/**
* In production mode, enable source map support.
* Initialize Zap as soon as electron is ready.
*/
if (process.env.NODE_ENV === 'production') {
const sourceMapSupport = require('source-map-support') // eslint-disable-line global-require
sourceMapSupport.install()
}
app.on('ready', async () => {
mainLog.timeEnd('Time until app is ready')

/**
* In development mode or when DEBUG_PROD is set, enable debugging tools.
*/
if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD) {
installExtension(REACT_DEVELOPER_TOOLS)
.then(name => mainLog.debug(`Added Extension: ${name}`))
.catch(err => mainLog.warn(`An error occurred when installing REACT_DEVELOPER_TOOLS: ${err}`))
// Get the users preference so that we can:
// - set the background colour of the window to avoid unwanted flicker.
// - Initialise the Language menu with the users locale selected by default.
let theme = {}
let locale

installExtension(REDUX_DEVTOOLS)
.then(name => mainLog.debug(`Added Extension: ${name}`))
.catch(err => mainLog.warn(`An error occurred when installing REDUX_DEVTOOLS: ${err}`))
if (!process.env.DISABLE_INIT) {
try {
const settings = await fetchSettings()
locale = getSetting(settings, 'locale')
const themeKey = getSetting(settings, 'theme')
theme = themes[themeKey]
} catch (e) {
mainLog.warn('Unable to determine user locale and theme', e)
}
}

zap.mainWindow.webContents.once('dom-ready', () => {
zap.mainWindow.openDevTools()
// Create a new browser window.
const mainWindow = new BrowserWindow({
show: false,
useContentSize: true,
titleBarStyle: 'hidden',
width: 1020,
height: 680,
minWidth: 900,
minHeight: 425,
backgroundColor: get(theme, 'colors.primaryColor', '#242633'),
webPreferences: {
preload: path.resolve(__dirname, 'preload.js')
}
})
}

/**
* Handler for lightning: links
*/
const handleLightningLink = input => {
const payReq = input.split(':')[1]
zap.sendMessage('lightningPaymentUri', { payReq })
zap.mainWindow.show()
}
// Initialise the updater.
const updater = new ZapUpdater(mainWindow)
updater.init()

/**
* Handler for lndconnect: links
*/
const handleLndconnectLink = input => {
const parsedUrl = url.parse(input)
const { host, cert, macaroon } = querystring.parse(parsedUrl.query)
zap.sendMessage('lndconnectUri', { host, cert, macaroon })
zap.mainWindow.show()
}
// Initialise the application.
const zap = new ZapController(mainWindow)
zap.init()

/**
* Add application event listeners:
* - lightning: Open zap payment form when lightning url is opened
* - lndconnect: Populate onboarding connection details form when lndconnect url is opened
*/
app.setAsDefaultProtocolClient('lightning')
app.setAsDefaultProtocolClient('lndconnect')
// Initialise the application menus.
const menuBuilder = new ZapMenuBuilder(mainWindow)
menuBuilder.buildMenu(locale)

app.on('open-url', (event, input) => {
mainLog.debug('open-url: %s', input)
event.preventDefault()
/**
* In production mode, enable source map support.
*/
if (process.env.NODE_ENV === 'production') {
const sourceMapSupport = require('source-map-support') // eslint-disable-line global-require
sourceMapSupport.install()
}

const type = input.split(':')[0]
/**
* In development mode or when DEBUG_PROD is set, enable debugging tools.
*/
if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD) {
installExtension(REACT_DEVELOPER_TOOLS)
.then(name => mainLog.debug(`Added Extension: ${name}`))
.catch(err =>
mainLog.warn(`An error occurred when installing REACT_DEVELOPER_TOOLS: ${err}`)
)

switch (type) {
case 'lightning':
handleLightningLink(input)
break
installExtension(REDUX_DEVTOOLS)
.then(name => mainLog.debug(`Added Extension: ${name}`))
.catch(err => mainLog.warn(`An error occurred when installing REDUX_DEVTOOLS: ${err}`))

case 'lndconnect':
handleLndconnectLink(input)
zap.mainWindow.webContents.once('dom-ready', () => {
zap.mainWindow.openDevTools()
})
}
})

// HACK: patch webrequest to fix devtools incompatibility with electron 2.x.
// See https://github.com/electron/electron/issues/13008#issuecomment-400261941
session.defaultSession.webRequest.onBeforeRequest({}, (details, callback) => {
if (details.url.indexOf('7accc8730b0f99b5e7c0702ea89d1fa7c17bfe33') !== -1) {
callback({
redirectURL: details.url.replace(
'7accc8730b0f99b5e7c0702ea89d1fa7c17bfe33',
'57c9d07b416b5a2ea23d28247300e4af36329bdc'
)
})
} else {
callback({ cancel: false })
/**
* Handler for lightning: links
*/
const handleLightningLink = input => {
const payReq = input.split(':')[1]
zap.sendMessage('lightningPaymentUri', { payReq })
zap.mainWindow.show()
}
})

/**
* 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.is('terminated')) {
/**
* Handler for lndconnect: links
*/
const handleLndconnectLink = input => {
const parsedUrl = url.parse(input)
const { host, cert, macaroon } = querystring.parse(parsedUrl.query)
zap.sendMessage('lndconnectUri', { host, cert, macaroon })
zap.mainWindow.show()
}

const handleOpenUrl = (event, input = '') => {
mainLog.debug('open-url: %s', input)
event.preventDefault()
zap.terminate()
} else {
if (zap.mainWindow) {
zap.mainWindow.forceClose = true

const type = input.split(':')[0]

switch (type) {
case 'lightning':
handleLightningLink(input)
break

case 'lndconnect':
handleLndconnectLink(input)
}
}
})

/**
* 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()
/**
* Add application event listeners:
* - lightning: Open zap payment form when lightning url is opened
* - lndconnect: Populate onboarding connection details form when lndconnect url is opened
*/
app.setAsDefaultProtocolClient('lightning')
app.setAsDefaultProtocolClient('lndconnect')

app.on('open-url', handleOpenUrl)

// HACK: patch webrequest to fix devtools incompatibility with electron 2.x.
// See https://github.com/electron/electron/issues/13008#issuecomment-400261941
session.defaultSession.webRequest.onBeforeRequest({}, (details, callback) => {
if (details.url.indexOf('7accc8730b0f99b5e7c0702ea89d1fa7c17bfe33') !== -1) {
callback({
redirectURL: details.url.replace(
'7accc8730b0f99b5e7c0702ea89d1fa7c17bfe33',
'57c9d07b416b5a2ea23d28247300e4af36329bdc'
)
})
} else {
callback({ cancel: false })
}
})

/**
* 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.is('terminated')) {
event.preventDefault()
zap.terminate()
} else {
if (zap.mainWindow) {
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()
})

/**
* Someone tried to run a second instance, we should focus our window.
*/
app.on('second-instance', (event, commandLine) => {
if (os.platform !== 'darwin') {
const protocolUrl = commandLine && commandLine.slice(1)[0]
if (protocolUrl) {
handleOpenUrl(event, protocolUrl)
}
}

if (zap.mainWindow) {
if (zap.mainWindow.isMinimized()) {
zap.mainWindow.restore()
}
zap.mainWindow.focus()
}
})
})
})
}

0 comments on commit e3c13de

Please sign in to comment.