diff --git a/package.json b/package.json
index f997d1cc..26b6e59e 100644
--- a/package.json
+++ b/package.json
@@ -64,7 +64,6 @@
"invariant": "^2.2.4",
"lodash": "^4.17.5",
"moment": "^2.22.1",
- "object-path": "^0.11.4",
"qrcode": "^1.2.0",
"qrcode-reader": "^1.0.4",
"qs": "^6.5.1",
diff --git a/src/actions/devices.js b/src/actions/devices.js
index 751d4b4c..4f745527 100644
--- a/src/actions/devices.js
+++ b/src/actions/devices.js
@@ -19,3 +19,7 @@ export const removeDevice: RemoveDevice = payload => ({
type: 'REMOVE_DEVICE',
payload,
})
+
+export const resetDevices = () => ({
+ type: 'RESET_DEVICES',
+})
diff --git a/src/commands/getAddress.js b/src/commands/getAddress.js
index 6029b1e7..2e0ae740 100644
--- a/src/commands/getAddress.js
+++ b/src/commands/getAddress.js
@@ -20,7 +20,6 @@ type Result = {
}
const cmd: Command = createCommand(
- 'devices',
'getAddress',
({ currencyId, devicePath, path, ...options }) =>
fromPromise(
diff --git a/src/commands/getDeviceInfo.js b/src/commands/getDeviceInfo.js
index c2fc4bd2..767f27f9 100644
--- a/src/commands/getDeviceInfo.js
+++ b/src/commands/getDeviceInfo.js
@@ -17,7 +17,7 @@ type Result = {
mcu: boolean,
}
-const cmd: Command = createCommand('devices', 'getDeviceInfo', ({ devicePath }) =>
+const cmd: Command = createCommand('getDeviceInfo', ({ devicePath }) =>
fromPromise(withDevice(devicePath)(transport => getDeviceInfo(transport))),
)
diff --git a/src/commands/getFirmwareInfo.js b/src/commands/getFirmwareInfo.js
index d20e7a12..dd1bdd42 100644
--- a/src/commands/getFirmwareInfo.js
+++ b/src/commands/getFirmwareInfo.js
@@ -12,7 +12,7 @@ type Input = {
type Result = *
-const cmd: Command = createCommand('devices', 'getFirmwareInfo', data =>
+const cmd: Command = createCommand('getFirmwareInfo', data =>
fromPromise(getFirmwareInfo(data)),
)
diff --git a/src/commands/getIsGenuine.js b/src/commands/getIsGenuine.js
index 9c5a0dbe..25f94d13 100644
--- a/src/commands/getIsGenuine.js
+++ b/src/commands/getIsGenuine.js
@@ -8,8 +8,6 @@ import getIsGenuine from 'helpers/devices/getIsGenuine'
type Input = *
type Result = boolean
-const cmd: Command = createCommand('devices', 'getIsGenuine', () =>
- fromPromise(getIsGenuine()),
-)
+const cmd: Command = createCommand('getIsGenuine', () => fromPromise(getIsGenuine()))
export default cmd
diff --git a/src/commands/getLatestFirmwareForDevice.js b/src/commands/getLatestFirmwareForDevice.js
index 7c8bc414..7a9621c1 100644
--- a/src/commands/getLatestFirmwareForDevice.js
+++ b/src/commands/getLatestFirmwareForDevice.js
@@ -12,7 +12,7 @@ type Input = {
type Result = *
-const cmd: Command = createCommand('devices', 'getLatestFirmwareForDevice', data =>
+const cmd: Command = createCommand('getLatestFirmwareForDevice', data =>
fromPromise(getLatestFirmwareForDevice(data)),
)
diff --git a/src/commands/getMemInfo.js b/src/commands/getMemInfo.js
index 8b175181..848fa032 100644
--- a/src/commands/getMemInfo.js
+++ b/src/commands/getMemInfo.js
@@ -12,7 +12,7 @@ type Input = {
type Result = *
-const cmd: Command = createCommand('devices', 'getMemInfo', ({ devicePath }) =>
+const cmd: Command = createCommand('getMemInfo', ({ devicePath }) =>
fromPromise(withDevice(devicePath)(transport => getMemInfo(transport))),
)
diff --git a/src/internals/devices/index.js b/src/commands/index.js
similarity index 75%
rename from src/internals/devices/index.js
rename to src/commands/index.js
index 4f830a2e..1b4e3abe 100644
--- a/src/internals/devices/index.js
+++ b/src/commands/index.js
@@ -1,6 +1,8 @@
// @flow
+
import type { Command } from 'helpers/ipc'
+import getMemInfo from 'commands/getMemInfo'
import libcoreScanAccounts from 'commands/libcoreScanAccounts'
import libcoreSignAndBroadcast from 'commands/libcoreSignAndBroadcast'
import getAddress from 'commands/getAddress'
@@ -16,8 +18,13 @@ import installOsuFirmware from 'commands/installOsuFirmware'
import installFinalFirmware from 'commands/installFinalFirmware'
import installMcu from 'commands/installMcu'
import listApps from 'commands/listApps'
+import testInterval from 'commands/testInterval'
+import testCrash from 'commands/testCrash'
-export const commands: Array> = [
+const all: Array> = [
+ getMemInfo,
+ libcoreScanAccounts,
+ libcoreSignAndBroadcast,
getAddress,
signTransaction,
getDeviceInfo,
@@ -25,12 +32,20 @@ export const commands: Array> = [
getIsGenuine,
getLatestFirmwareForDevice,
installApp,
- libcoreScanAccounts,
- libcoreSignAndBroadcast,
listenDevices,
uninstallApp,
installOsuFirmware,
installFinalFirmware,
installMcu,
listApps,
+ testInterval,
+ testCrash,
]
+
+all.forEach(cmd => {
+ if (all.some(c => c !== cmd && c.id === cmd.id)) {
+ throw new Error(`duplicate command '${cmd.id}'`)
+ }
+})
+
+export default all
diff --git a/src/commands/installApp.js b/src/commands/installApp.js
index 792a5661..c4f1df13 100644
--- a/src/commands/installApp.js
+++ b/src/commands/installApp.js
@@ -15,11 +15,8 @@ type Input = {
type Result = *
-const cmd: Command = createCommand(
- 'devices',
- 'installApp',
- ({ devicePath, ...rest }) =>
- fromPromise(withDevice(devicePath)(transport => installApp(transport, rest))),
+const cmd: Command = createCommand('installApp', ({ devicePath, ...rest }) =>
+ fromPromise(withDevice(devicePath)(transport => installApp(transport, rest))),
)
export default cmd
diff --git a/src/commands/installFinalFirmware.js b/src/commands/installFinalFirmware.js
index 775dd743..ff9f677f 100644
--- a/src/commands/installFinalFirmware.js
+++ b/src/commands/installFinalFirmware.js
@@ -19,7 +19,6 @@ type Result = {
}
const cmd: Command = createCommand(
- 'devices',
'installFinalFirmware',
({ devicePath, firmware }) =>
fromPromise(withDevice(devicePath)(transport => installFinalFirmware(transport, firmware))),
diff --git a/src/commands/installMcu.js b/src/commands/installMcu.js
index b1fa57ed..c16387dc 100644
--- a/src/commands/installMcu.js
+++ b/src/commands/installMcu.js
@@ -21,8 +21,6 @@ import installMcu from 'helpers/firmware/installMcu'
type Input = *
type Result = *
-const cmd: Command = createCommand('devices', 'installMcu', () =>
- fromPromise(installMcu()),
-)
+const cmd: Command = createCommand('installMcu', () => fromPromise(installMcu()))
export default cmd
diff --git a/src/commands/installOsuFirmware.js b/src/commands/installOsuFirmware.js
index 8d4a6f4f..7b397a55 100644
--- a/src/commands/installOsuFirmware.js
+++ b/src/commands/installOsuFirmware.js
@@ -19,7 +19,6 @@ type Result = {
}
const cmd: Command = createCommand(
- 'devices',
'installOsuFirmware',
({ devicePath, firmware }) =>
fromPromise(withDevice(devicePath)(transport => installOsuFirmware(transport, firmware))),
diff --git a/src/commands/libcoreScanAccounts.js b/src/commands/libcoreScanAccounts.js
index 0cb7fb6c..c20ab6d1 100644
--- a/src/commands/libcoreScanAccounts.js
+++ b/src/commands/libcoreScanAccounts.js
@@ -13,7 +13,6 @@ type Input = {
type Result = AccountRaw
const cmd: Command = createCommand(
- 'devices',
'libcoreScanAccounts',
({ devicePath, currencyId }) =>
Observable.create(o => {
diff --git a/src/commands/libcoreSignAndBroadcast.js b/src/commands/libcoreSignAndBroadcast.js
index 9e3bea3f..5727b9f7 100644
--- a/src/commands/libcoreSignAndBroadcast.js
+++ b/src/commands/libcoreSignAndBroadcast.js
@@ -23,7 +23,6 @@ type Input = {
type Result = $Exact
const cmd: Command = createCommand(
- 'devices',
'libcoreSignAndBroadcast',
({ account, transaction, deviceId }) => {
// TODO: investigate why importing it on file scope causes trouble
diff --git a/src/commands/listApps.js b/src/commands/listApps.js
index ecffac64..b600aef4 100644
--- a/src/commands/listApps.js
+++ b/src/commands/listApps.js
@@ -11,7 +11,7 @@ type Input = {
type Result = *
-const cmd: Command = createCommand('devices', 'listApps', ({ targetId }) =>
+const cmd: Command = createCommand('listApps', ({ targetId }) =>
fromPromise(listApps(targetId)),
)
diff --git a/src/commands/listenDevices.js b/src/commands/listenDevices.js
index d3ea3aeb..e523750a 100644
--- a/src/commands/listenDevices.js
+++ b/src/commands/listenDevices.js
@@ -4,6 +4,6 @@ import { createCommand } from 'helpers/ipc'
import { Observable } from 'rxjs'
import CommNodeHid from '@ledgerhq/hw-transport-node-hid'
-const cmd = createCommand('devices', 'listenDevices', () => Observable.create(CommNodeHid.listen))
+const cmd = createCommand('listenDevices', () => Observable.create(CommNodeHid.listen))
export default cmd
diff --git a/src/commands/signTransaction.js b/src/commands/signTransaction.js
index dfc1cfcb..7ffea26b 100644
--- a/src/commands/signTransaction.js
+++ b/src/commands/signTransaction.js
@@ -15,7 +15,6 @@ type Input = {
type Result = string
const cmd: Command = createCommand(
- 'devices',
'signTransaction',
({ currencyId, devicePath, path, transaction }) =>
fromPromise(
diff --git a/src/commands/testCrash.js b/src/commands/testCrash.js
new file mode 100644
index 00000000..0725988a
--- /dev/null
+++ b/src/commands/testCrash.js
@@ -0,0 +1,17 @@
+// @flow
+
+// This is a test example for dev testing purpose.
+
+import { Observable } from 'rxjs'
+import { createCommand, Command } from 'helpers/ipc'
+
+type Input = void
+type Result = void
+
+const cmd: Command = createCommand('testCrash', () =>
+ Observable.create(() => {
+ process.exit(1)
+ }),
+)
+
+export default cmd
diff --git a/src/commands/testInterval.js b/src/commands/testInterval.js
new file mode 100644
index 00000000..d6a8aab9
--- /dev/null
+++ b/src/commands/testInterval.js
@@ -0,0 +1,13 @@
+// @flow
+
+// This is a test example for dev testing purpose.
+
+import { interval } from 'rxjs/observable/interval'
+import { createCommand, Command } from 'helpers/ipc'
+
+type Input = number
+type Result = number
+
+const cmd: Command = createCommand('testInterval', interval)
+
+export default cmd
diff --git a/src/commands/uninstallApp.js b/src/commands/uninstallApp.js
index a07a2e8f..e8e3b3f7 100644
--- a/src/commands/uninstallApp.js
+++ b/src/commands/uninstallApp.js
@@ -15,11 +15,8 @@ type Input = {
type Result = *
-const cmd: Command = createCommand(
- 'devices',
- 'uninstallApp',
- ({ devicePath, ...rest }) =>
- fromPromise(withDevice(devicePath)(transport => uninstallApp(transport, rest))),
+const cmd: Command = createCommand('uninstallApp', ({ devicePath, ...rest }) =>
+ fromPromise(withDevice(devicePath)(transport => uninstallApp(transport, rest))),
)
export default cmd
diff --git a/src/components/ManagerPage/DeviceInfos.js b/src/components/ManagerPage/DeviceInfos.js
index aae54293..df2c87c2 100644
--- a/src/components/ManagerPage/DeviceInfos.js
+++ b/src/components/ManagerPage/DeviceInfos.js
@@ -2,8 +2,6 @@
import React, { PureComponent } from 'react'
-import runJob from 'renderer/runJob'
-
import Text from 'components/base/Text'
import Box, { Card } from 'components/base/Box'
import Button from 'components/base/Button'
@@ -30,16 +28,7 @@ class DeviceInfos extends PureComponent {
handleGetMemInfos = async () => {
try {
this.setState({ isLoading: true })
- const {
- device: { path: devicePath },
- } = this.props
- const memoryInfos = await runJob({
- channel: 'manager',
- job: 'getMemInfos',
- successResponse: 'manager.getMemInfosSuccess',
- errorResponse: 'manager.getMemInfosError',
- data: { devicePath },
- })
+ const memoryInfos = null // TODO
this.setState({ memoryInfos, isLoading: false })
} catch (err) {
this.setState({ isLoading: false })
diff --git a/src/components/ManagerPage/FinalFirmwareUpdate.js b/src/components/ManagerPage/FinalFirmwareUpdate.js
index 86c128c8..defaac39 100644
--- a/src/components/ManagerPage/FinalFirmwareUpdate.js
+++ b/src/components/ManagerPage/FinalFirmwareUpdate.js
@@ -4,8 +4,6 @@ import React, { PureComponent } from 'react'
import { translate } from 'react-i18next'
import type { Device, T } from 'types/common'
-// import runJob from 'renderer/runJob'
-
import Box, { Card } from 'components/base/Box'
// import Button from 'components/base/Button'
@@ -37,28 +35,6 @@ class FirmwareUpdate extends PureComponent {
_unmounting = false
- // handleInstallFinalFirmware = async () => {
- // try {
- // const { latestFirmware } = this.state
- // this.setState(state => ({ ...state, installing: true }))
- // const {
- // device: { path: devicePath },
- // } = this.props
- // await runJob({
- // channel: 'manager',
- // job: 'installFinalFirmware',
- // successResponse: 'device.finalFirmwareInstallSuccess',
- // errorResponse: 'device.finalFirmwareInstallError',
- // data: {
- // devicePath,
- // firmware: latestFirmware,
- // },
- // })
- // } catch (err) {
- // console.log(err)
- // }
- // }
-
render() {
const { t, ...props } = this.props
diff --git a/src/components/ManagerPage/FirmwareUpdate.js b/src/components/ManagerPage/FirmwareUpdate.js
index 84d95ac2..0391e283 100644
--- a/src/components/ManagerPage/FirmwareUpdate.js
+++ b/src/components/ManagerPage/FirmwareUpdate.js
@@ -6,7 +6,6 @@ import isEmpty from 'lodash/isEmpty'
import type { Device, T } from 'types/common'
-import runJob from 'renderer/runJob'
import getLatestFirmwareForDevice from 'commands/getLatestFirmwareForDevice'
import Box, { Card } from 'components/base/Box'
@@ -74,20 +73,7 @@ class FirmwareUpdate extends PureComponent {
installFirmware = async () => {
try {
- const { latestFirmware } = this.state
- const {
- device: { path: devicePath },
- } = this.props
- await runJob({
- channel: 'manager',
- job: 'installOsuFirmware',
- successResponse: 'device.osuFirmwareInstallSuccess',
- errorResponse: 'device.osuFirmwareInstallError',
- data: {
- devicePath,
- firmware: latestFirmware,
- },
- })
+ // TODO
} catch (err) {
console.log(err)
}
diff --git a/src/components/UpdateNotifier.js b/src/components/UpdateNotifier.js
index 04778ee2..92dfd3dc 100644
--- a/src/components/UpdateNotifier.js
+++ b/src/components/UpdateNotifier.js
@@ -66,7 +66,7 @@ class UpdateNotifier extends PureComponent {
sendEvent('msg', 'updater.quitAndInstall')}
+ onClick={() => sendEvent('updater', 'quitAndInstall')}
>
{t('update:relaunch')}
diff --git a/src/components/modals/Debug.js b/src/components/modals/Debug.js
index ae6769e0..98a4386c 100644
--- a/src/components/modals/Debug.js
+++ b/src/components/modals/Debug.js
@@ -8,12 +8,26 @@ import Box from 'components/base/Box'
import EnsureDevice from 'components/ManagerPage/EnsureDevice'
import { getDerivations } from 'helpers/derivations'
import getAddress from 'commands/getAddress'
+import testInterval from 'commands/testInterval'
+import testCrash from 'commands/testCrash'
class Debug extends Component<*, *> {
state = {
logs: [],
}
+ onStartPeriod = (period: number) => () => {
+ this.periodSubs.push(
+ testInterval.send(period).subscribe(n => this.log(`interval ${n}`), this.error),
+ )
+ }
+
+ onCrash = () => {
+ testCrash.send().subscribe({
+ error: this.error,
+ })
+ }
+
onClickStressDevice = (device: *) => async () => {
try {
const currency = getCryptoCurrencyById('bitcoin')
@@ -42,6 +56,12 @@ class Debug extends Component<*, *> {
this.setState({ logs: [] })
}
+ cancelAllPeriods = () => {
+ this.periodSubs.forEach(s => s.unsubscribe())
+ this.periodSubs = []
+ }
+ periodSubs = []
+
log = (txt: string) => {
this.setState(({ logs }) => ({ logs: logs.concat({ txt, type: 'log' }) }))
}
@@ -60,17 +80,30 @@ class Debug extends Component<*, *> {
onHide={this.onHide}
render={({ onClose }: *) => (
- DEBUG utils
+ developer internal tools
-
- {device => (
-
-
-
- )}
-
+
+
+
+ {device => (
+
+ )}
+
+
+
+
+
+
+
+
+
+
{
))}
+
)}
diff --git a/src/helpers/deviceAccess.js b/src/helpers/deviceAccess.js
index 637faaa4..a077248e 100644
--- a/src/helpers/deviceAccess.js
+++ b/src/helpers/deviceAccess.js
@@ -2,7 +2,6 @@
import createSemaphore from 'semaphore'
import type Transport from '@ledgerhq/hw-transport'
import TransportNodeHid from '@ledgerhq/hw-transport-node-hid'
-import { retry } from './promise'
// all open to device must use openDevice so we can prevent race conditions
// and guarantee we do one device access at a time. It also will handle the .close()
@@ -13,19 +12,12 @@ type WithDevice = (devicePath: string) => (job: (Transport<*>) => Promise)
const semaphorePerDevice = {}
export const withDevice: WithDevice = devicePath => {
- const { FORK_TYPE } = process.env
- if (FORK_TYPE !== 'devices') {
- console.warn(
- `deviceAccess is only expected to be used in process 'devices'. Any other usage may lead to race conditions. (Got: '${FORK_TYPE}')`,
- )
- }
-
const sem =
semaphorePerDevice[devicePath] || (semaphorePerDevice[devicePath] = createSemaphore(1))
return job =>
takeSemaphorePromise(sem, async () => {
- const t = await retry(() => TransportNodeHid.open(devicePath))
+ const t = await TransportNodeHid.open(devicePath)
try {
const res = await job(t)
// $FlowFixMe
diff --git a/src/helpers/generic.js b/src/helpers/generic.js
deleted file mode 100644
index f3798ba4..00000000
--- a/src/helpers/generic.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/* eslint-disable no-bitwise */
-
-import bitcoin from 'bitcoinjs-lib'
-import bs58 from 'bs58'
-
-export function toHexDigit(number) {
- const digits = '0123456789abcdef'
- return digits.charAt(number >> 4) + digits.charAt(number & 0x0f)
-}
-
-export function toHexInt(number) {
- return (
- toHexDigit((number >> 24) & 0xff) +
- toHexDigit((number >> 16) & 0xff) +
- toHexDigit((number >> 8) & 0xff) +
- toHexDigit(number & 0xff)
- )
-}
-
-export function encodeBase58Check(vchIn) {
- // vchIn = parseHexString(vchIn)
- let chksum = bitcoin.crypto.sha256(vchIn)
- chksum = bitcoin.crypto.sha256(chksum)
- chksum = chksum.slice(0, 4)
- const hash = vchIn.concat(Array.from(chksum))
- return bs58.encode(hash)
-}
-
-export function parseHexString(str) {
- const result = []
- while (str.length >= 2) {
- result.push(parseInt(str.substring(0, 2), 16))
- str = str.substring(2, str.length)
- }
- return result
-}
diff --git a/src/helpers/ipc.js b/src/helpers/ipc.js
index 3494dabc..f78bb81c 100644
--- a/src/helpers/ipc.js
+++ b/src/helpers/ipc.js
@@ -2,118 +2,104 @@
import { Observable } from 'rxjs'
import uuidv4 from 'uuid/v4'
-type Msg = {
- type: string,
- data?: A,
- options?: *,
-}
-
-function send(msg: Msg) {
- process.send(msg)
+export function createCommand(id: string, impl: In => Observable): Command {
+ return new Command(id, impl)
}
export class Command {
- channel: string
- type: string
id: string
impl: In => Observable
- constructor(channel: string, type: string, impl: In => Observable) {
- this.channel = channel
- this.type = type
- this.id = `${channel}.${type}`
- this.impl = impl
- }
- // ~~~ On exec side we can:
-
- exec(data: In, requestId: string) {
- return this.impl(data).subscribe({
- next: (data: A) => {
- send({
- type: `NEXT_${requestId}`,
- data,
- })
- },
- complete: () => {
- send({
- type: `COMPLETE_${requestId}`,
- options: { kill: true },
- })
- },
- error: error => {
- console.log('exec error:', error)
- send({
- type: `ERROR_${requestId}`,
- data: {
- name: error && error.name,
- message: error && error.message,
- },
- options: { kill: true },
- })
- },
- })
+ constructor(id: string, impl: In => Observable) {
+ this.id = id
+ this.impl = impl
}
- // ~~~ On renderer side we can:
-
/**
* Usage example:
- * sub = send(data).subscribe({ next: ... })
+ * sub = cmd.send(data).subscribe({ next: ... })
* // or
- * const res = await send(data).toPromise()
+ * const res = await cmd.send(data).toPromise()
*/
send(data: In): Observable {
- const { ipcRenderer } = require('electron')
- return Observable.create(o => {
- const { channel, type, id } = this
- const requestId: string = uuidv4()
+ return ipcRendererSendCommand(this.id, data)
+ }
+}
- const unsubscribe = () => {
- ipcRenderer.removeListener('msg', handleMsgEvent)
- }
+type Msg = {
+ type: 'NEXT' | 'COMPLETE' | 'ERROR',
+ requestId: string,
+ data?: A,
+}
- function handleMsgEvent(e, msg: Msg) {
- switch (msg.type) {
- case `NEXT_${requestId}`:
- if (msg.data) {
- o.next(msg.data)
- }
- break
-
- case `COMPLETE_${requestId}`:
- o.complete()
- unsubscribe()
- break
-
- case `ERROR_${requestId}`:
- o.error(msg.data)
- unsubscribe()
- break
-
- default:
- }
+// Implements command message of (Renderer proc -> Main proc)
+function ipcRendererSendCommand(id: string, data: In): Observable {
+ const { ipcRenderer } = require('electron')
+ return Observable.create(o => {
+ const requestId: string = uuidv4()
+
+ const unsubscribe = () => {
+ ipcRenderer.send('command-unsubscribe', { requestId })
+ ipcRenderer.removeListener('command-event', handleCommandEvent)
+ }
+
+ function handleCommandEvent(e, msg: Msg) {
+ if (requestId !== msg.requestId) return
+ switch (msg.type) {
+ case 'NEXT':
+ if (msg.data) {
+ o.next(msg.data)
+ }
+ break
+
+ case 'COMPLETE':
+ o.complete()
+ ipcRenderer.removeListener('command-event', handleCommandEvent)
+ break
+
+ case 'ERROR':
+ o.error(msg.data)
+ ipcRenderer.removeListener('command-event', handleCommandEvent)
+ break
+
+ default:
}
+ }
+
+ ipcRenderer.on('command-event', handleCommandEvent)
- ipcRenderer.on('msg', handleMsgEvent)
+ ipcRenderer.send('command', { id, data, requestId })
- ipcRenderer.send(channel, {
- type,
- data: {
- id,
- data,
- requestId,
- },
- })
+ return unsubscribe
+ })
+}
- return unsubscribe
+// Implements command message of (Main proc -> Renderer proc)
+// (dual of ipcRendererSendCommand)
+export function ipcMainListenReceiveCommands(o: {
+ onUnsubscribe: (requestId: string) => void,
+ onCommand: (
+ command: { id: string, data: *, requestId: string },
+ notifyCommandEvent: (Msg<*>) => void,
+ ) => void,
+}) {
+ const { ipcMain } = require('electron')
+
+ const onCommandUnsubscribe = (event, { requestId }) => {
+ o.onUnsubscribe(requestId)
+ }
+
+ const onCommand = (event, command) => {
+ o.onCommand(command, payload => {
+ event.sender.send('command-event', payload)
})
}
-}
-export function createCommand(
- channel: string,
- type: string,
- impl: In => Observable,
-): Command {
- return new Command(channel, type, impl)
+ ipcMain.on('command-unsubscribe', onCommandUnsubscribe)
+ ipcMain.on('command', onCommand)
+
+ return () => {
+ ipcMain.removeListener('command-unsubscribe', onCommandUnsubscribe)
+ ipcMain.removeListener('command', onCommand)
+ }
}
diff --git a/src/internals/index.js b/src/internals/index.js
index 36ec3c28..2e5fe74d 100644
--- a/src/internals/index.js
+++ b/src/internals/index.js
@@ -1,44 +1,58 @@
// @flow
-
-import objectPath from 'object-path'
-import capitalize from 'lodash/capitalize'
+import commands from 'commands'
require('../env')
require('../init-sentry')
-const { FORK_TYPE } = process.env
-
-process.title = `${require('../../package.json').productName} ${capitalize(FORK_TYPE)}`
-
-function sendEvent(type: string, data: any, options: Object = { kill: true }) {
- process.send({ type, data, options })
-}
+process.title = 'Internal'
-// $FlowFixMe
-let handlers = require(`./${FORK_TYPE}`) // eslint-disable-line import/no-dynamic-require
-// handle babel export object syntax
-if (handlers.default) {
- handlers = handlers.default
-}
+const subscriptions = {}
-process.on('message', payload => {
- if (payload.data && payload.data.requestId) {
- const { data, requestId, id } = payload.data
- // this is the new type of "command" payload!
- const cmd = (handlers.commands || []).find(cmd => cmd.id === id)
+process.on('message', m => {
+ console.log(m)
+ if (m.type === 'command') {
+ const { data, requestId, id } = m.command
+ const cmd = commands.find(cmd => cmd.id === id)
if (!cmd) {
console.warn(`command ${id} not found`)
- } else {
- cmd.exec(data, requestId)
- }
- } else {
- // this will be deprecated!
- const { type, data } = payload
- const handler = objectPath.get(handlers, type)
- if (!handler) {
- console.warn(`No handler found for ${type}`)
return
}
- handler(sendEvent, data)
+ subscriptions[requestId] = cmd.impl(data).subscribe({
+ next: data => {
+ process.send({
+ type: 'NEXT',
+ requestId,
+ data,
+ })
+ },
+ complete: () => {
+ delete subscriptions[requestId]
+ process.send({
+ type: 'COMPLETE',
+ requestId,
+ })
+ },
+ error: error => {
+ console.warn('Command error:', error)
+ delete subscriptions[requestId]
+ process.send({
+ type: 'ERROR',
+ requestId,
+ data: {
+ name: error && error.name,
+ message: error && error.message,
+ },
+ })
+ },
+ })
+ } else if (m.type === 'command-unsubscribe') {
+ const { requestId } = m
+ const sub = subscriptions[requestId]
+ if (sub) {
+ sub.unsubscribe()
+ delete subscriptions[requestId]
+ }
}
})
+
+console.log('Internal process is ready!')
diff --git a/src/internals/manager/index.js b/src/internals/manager/index.js
deleted file mode 100644
index cb51bae3..00000000
--- a/src/internals/manager/index.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// @flow
-import type { Command } from 'helpers/ipc'
-
-import getMemInfo from 'commands/getMemInfo'
-
-/**
- * Manager
- * -------
- *
- * xXx
- * xXx
- * xXx
- * xxxXxxx
- * xxXxx
- * xXx
- * xX x Xx
- * xX Xx
- * xxXXXXXXXxx
- *
- */
-
-export const commands: Array> = [getMemInfo]
diff --git a/src/main/autoUpdate.js b/src/main/autoUpdate.js
index 76c71ff7..1b50e078 100644
--- a/src/main/autoUpdate.js
+++ b/src/main/autoUpdate.js
@@ -6,12 +6,12 @@ import { autoUpdater } from 'electron-updater'
type SendFunction = (type: string, data: *) => void
export default (notify: SendFunction) => {
- autoUpdater.on('checking-for-update', () => notify('updater.checking'))
- autoUpdater.on('update-available', info => notify('updater.updateAvailable', info))
- autoUpdater.on('update-not-available', () => notify('updater.updateNotAvailable'))
- autoUpdater.on('error', err => notify('updater.error', err))
- autoUpdater.on('download-progress', progress => notify('updater.downloadProgress', progress))
- autoUpdater.on('update-downloaded', () => notify('updater.downloaded'))
+ autoUpdater.on('checking-for-update', () => notify('checking'))
+ autoUpdater.on('update-available', info => notify('updateAvailable', info))
+ autoUpdater.on('update-not-available', () => notify('updateNotAvailable'))
+ autoUpdater.on('error', err => notify('error', err))
+ autoUpdater.on('download-progress', progress => notify('downloadProgress', progress))
+ autoUpdater.on('update-downloaded', () => notify('downloaded'))
autoUpdater.checkForUpdatesAndNotify()
}
diff --git a/src/main/bridge.js b/src/main/bridge.js
index efe03485..d5aca91d 100644
--- a/src/main/bridge.js
+++ b/src/main/bridge.js
@@ -1,100 +1,89 @@
// @flow
import '@babel/polyfill'
+import invariant from 'invariant'
import { fork } from 'child_process'
-import { BrowserWindow, ipcMain, app } from 'electron'
-import objectPath from 'object-path'
+import { ipcMain, app } from 'electron'
+import { ipcMainListenReceiveCommands } from 'helpers/ipc'
import path from 'path'
import setupAutoUpdater, { quitAndInstall } from './autoUpdate'
-const { DEV_TOOLS } = process.env
-
// sqlite files will be located in the app local data folder
const LEDGER_LIVE_SQLITE_PATH = path.resolve(app.getPath('userData'), 'sqlite')
-const processes = []
+let internalProcess
-function cleanProcesses() {
- processes.forEach(kill => kill())
+const killInternalProcess = () => {
+ if (internalProcess) {
+ console.log('killing internal process...')
+ internalProcess.kill('SIGINT')
+ internalProcess = null
+ }
}
-function sendEventToWindow(name, { type, data }) {
- const anotherWindow = BrowserWindow.getAllWindows().find(w => w.name === name)
- if (anotherWindow) {
- anotherWindow.webContents.send('msg', { type, data })
- }
+const forkBundlePath = path.resolve(__dirname, `${__DEV__ ? '../../' : './'}dist/internals`)
+
+const bootInternalProcess = () => {
+ console.log('booting internal process...')
+ internalProcess = fork(forkBundlePath, {
+ env: { LEDGER_LIVE_SQLITE_PATH },
+ })
+ internalProcess.on('exit', code => {
+ console.log(`Internal process ended with code ${code}`)
+ internalProcess = null
+ })
}
-function onForkChannel(forkType) {
- return (event: any, payload) => {
- const { type, data } = payload
+process.on('exit', () => {
+ killInternalProcess()
+})
- let compute = fork(path.resolve(__dirname, `${__DEV__ ? '../../' : './'}dist/internals`), {
- env: {
- DEV_TOOLS,
- FORK_TYPE: forkType,
- LEDGER_LIVE_SQLITE_PATH,
- },
- })
+ipcMain.on('clean-processes', () => {
+ killInternalProcess()
+})
- const kill = () => {
- if (compute) {
- compute.kill('SIGINT')
- compute = null
- }
+ipcMainListenReceiveCommands({
+ onUnsubscribe: requestId => {
+ if (!internalProcess) return
+ internalProcess.send({ type: 'command-unsubscribe', requestId })
+ },
+ onCommand: (command, notifyCommandEvent) => {
+ if (!internalProcess) bootInternalProcess()
+ const p = internalProcess
+ invariant(p, 'internalProcess not started !?')
+
+ const handleExit = code => {
+ p.removeListener('message', handleMessage)
+ p.removeListener('exit', handleExit)
+ notifyCommandEvent({
+ type: 'ERROR',
+ requestId: command.requestId,
+ data: { message: `Internal process error (${code})`, name: 'InternalError' },
+ })
}
- processes.push(kill)
-
- const onMessage = payload => {
- const { type, data, options = {} } = payload
-
- if (options.window) {
- sendEventToWindow(options.window, { type, data })
- } else {
- event.sender.send('msg', { type, data })
- }
- if (options.kill && compute) {
- kill()
+ const handleMessage = payload => {
+ if (payload.requestId !== command.requestId) return
+ notifyCommandEvent(payload)
+ if (payload.type === 'ERROR' || payload.type === 'COMPLETE') {
+ p.removeListener('message', handleMessage)
+ p.removeListener('exit', handleExit)
}
}
- compute.on('message', onMessage)
- compute.send({ type, data })
-
- process.on('exit', kill)
- }
-}
-
-// Forwards every `type` messages to another process
-ipcMain.on('devices', onForkChannel('devices'))
-ipcMain.on('accounts', onForkChannel('accounts'))
-ipcMain.on('manager', onForkChannel('manager'))
-
-ipcMain.on('clean-processes', cleanProcesses)
+ p.on('exit', handleExit)
+ p.on('message', handleMessage)
+ p.send({ type: 'command', command })
+ },
+})
-const handlers = {
- updater: {
+// TODO move this to "command" pattern
+ipcMain.on('updater', (event, { type, data }) => {
+ const handler = {
init: setupAutoUpdater,
quitAndInstall,
- },
- kill: {
- process: (send, { pid }) => {
- try {
- process.kill(pid, 'SIGINT')
- } catch (e) {} // eslint-disable-line no-empty
- },
- },
-}
-
-ipcMain.on('msg', (event: any, payload) => {
- const { type, data } = payload
- const handler = objectPath.get(handlers, type)
- if (!handler) {
- console.warn(`No handler found for ${type}`)
- return
- }
- const send = (type: string, data: *) => event.sender.send('msg', { type, data })
+ }[type]
+ const send = (type: string, data: *) => event.sender.send('updater', { type, data })
handler(send, data, type)
})
diff --git a/src/reducers/devices.js b/src/reducers/devices.js
index ed2154da..430eda36 100644
--- a/src/reducers/devices.js
+++ b/src/reducers/devices.js
@@ -9,7 +9,7 @@ export type DevicesState = {
devices: Device[],
}
-const state: DevicesState = {
+const initialState: DevicesState = {
currentDevice: null,
devices: [],
}
@@ -20,6 +20,7 @@ function setCurrentDevice(state) {
}
const handlers: Object = {
+ RESET_DEVICES: () => initialState,
ADD_DEVICE: (state: DevicesState, { payload: device }: { payload: Device }) =>
setCurrentDevice({
...state,
@@ -49,4 +50,4 @@ export function getDevices(state: { devices: DevicesState }) {
return state.devices.devices
}
-export default handleActions(handlers, state)
+export default handleActions(handlers, initialState)
diff --git a/src/renderer/events.js b/src/renderer/events.js
index 1833ea72..d5987f7e 100644
--- a/src/renderer/events.js
+++ b/src/renderer/events.js
@@ -8,20 +8,19 @@
// both of these implementation should have a unique requestId to ensure there is no collision
// events should all appear in the promise result / observer msgs as soon as they have this requestId
+import 'commands'
+
import { ipcRenderer } from 'electron'
-import objectPath from 'object-path'
import debug from 'debug'
import { CHECK_UPDATE_DELAY } from 'config/constants'
import { setUpdateStatus } from 'reducers/update'
-import { addDevice, removeDevice } from 'actions/devices'
+import { addDevice, removeDevice, resetDevices } from 'actions/devices'
import listenDevices from 'commands/listenDevices'
-import i18n from 'renderer/i18n/electron'
-
const d = {
device: debug('lwd:device'),
sync: debug('lwd:sync'),
@@ -33,6 +32,7 @@ type MsgPayload = {
data: any,
}
+// TODO port remaining to command pattern
export function sendEvent(channel: string, msgType: string, data: any) {
ipcRenderer.send(channel, {
type: msgType,
@@ -40,61 +40,71 @@ export function sendEvent(channel: string, msgType: string, data: any) {
})
}
-export function sendSyncEvent(channel: string, msgType: string, data: any): any {
- return ipcRenderer.sendSync(`${channel}:sync`, {
- type: msgType,
- data,
- })
-}
-
+let syncDeviceSub
export default ({ store }: { store: Object, locked: boolean }) => {
- const handlers = {
- dispatch: ({ type, payload }) => store.dispatch({ type, payload }),
- application: {
- changeLanguage: lang => i18n.changeLanguage(lang),
- },
- updater: {
+ // Ensure all sub-processes are killed before creating new ones (dev mode...)
+ ipcRenderer.send('clean-processes')
+
+ if (syncDeviceSub) {
+ syncDeviceSub.unsubscribe()
+ syncDeviceSub = null
+ }
+
+ function syncDevices() {
+ syncDeviceSub = listenDevices.send().subscribe(
+ ({ device, type }) => {
+ if (device) {
+ if (type === 'add') {
+ d.device('Device - add')
+ store.dispatch(addDevice(device))
+ } else if (type === 'remove') {
+ d.device('Device - remove')
+ store.dispatch(removeDevice(device))
+ }
+ }
+ },
+ error => {
+ console.warn('listenDevices error', error)
+ store.dispatch(resetDevices())
+ syncDevices()
+ },
+ () => {
+ console.warn('listenDevices ended unexpectedly. restarting')
+ store.dispatch(resetDevices())
+ syncDevices()
+ },
+ )
+ }
+
+ syncDevices()
+
+ if (__PROD__) {
+ // TODO move this to "command" pattern
+ const updaterHandlers = {
checking: () => store.dispatch(setUpdateStatus('checking')),
updateAvailable: info => store.dispatch(setUpdateStatus('available', info)),
updateNotAvailable: () => store.dispatch(setUpdateStatus('unavailable')),
error: err => store.dispatch(setUpdateStatus('error', err)),
downloadProgress: progress => store.dispatch(setUpdateStatus('progress', progress)),
downloaded: () => store.dispatch(setUpdateStatus('downloaded')),
- },
- }
- ipcRenderer.on('msg', (event: any, payload: MsgPayload) => {
- const { type, data } = payload
- const handler = objectPath.get(handlers, type)
- if (!handler) {
- return
}
- handler(data)
- })
-
- // Ensure all sub-processes are killed before creating new ones (dev mode...)
- ipcRenderer.send('clean-processes')
+ ipcRenderer.on('updater', (event: any, payload: MsgPayload) => {
+ const { type, data } = payload
+ updaterHandlers[type](data)
+ })
- listenDevices.send().subscribe({
- next: ({ device, type }) => {
- if (device) {
- if (type === 'add') {
- d.device('Device - add')
- store.dispatch(addDevice(device))
- } else if (type === 'remove') {
- d.device('Device - remove')
- store.dispatch(removeDevice(device))
- }
- }
- },
- })
-
- if (__PROD__) {
// Start check of eventual updates
checkUpdates()
}
}
+if (module.hot) {
+ module.hot.accept('commands', () => {
+ ipcRenderer.send('clean-processes')
+ })
+}
+
export function checkUpdates() {
d.update('Update - check')
- setTimeout(() => sendEvent('msg', 'updater.init'), CHECK_UPDATE_DELAY)
+ setTimeout(() => sendEvent('updater', 'init'), CHECK_UPDATE_DELAY)
}
diff --git a/src/renderer/runJob.js b/src/renderer/runJob.js
deleted file mode 100644
index b2fc8e64..00000000
--- a/src/renderer/runJob.js
+++ /dev/null
@@ -1,34 +0,0 @@
-// @flow
-
-import { ipcRenderer } from 'electron'
-
-export default function runJob({
- channel,
- job,
- successResponse,
- errorResponse,
- data,
-}: {
- channel: string,
- job: string,
- successResponse: string,
- errorResponse: string,
- data?: any,
-}): Promise {
- return new Promise((resolve, reject) => {
- ipcRenderer.send(channel, { type: job, data })
- ipcRenderer.on('msg', handler)
- function handler(e, res) {
- const { type, data } = res
- if (![successResponse, errorResponse].includes(type)) {
- return
- }
- ipcRenderer.removeListener('msg', handler)
- if (type === successResponse) {
- resolve(data)
- } else if (type === errorResponse) {
- reject(data)
- }
- }
- })
-}