Gaëtan Renaudeau
7 years ago
36 changed files with 367 additions and 443 deletions
@ -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<Input, Result> = createCommand('testCrash', () => |
|||
Observable.create(() => { |
|||
process.exit(1) |
|||
}), |
|||
) |
|||
|
|||
export default cmd |
@ -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<Input, Result> = createCommand('testInterval', interval) |
|||
|
|||
export default cmd |
@ -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 |
|||
} |
@ -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!') |
|||
|
@ -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<Command<any, any>> = [getMemInfo] |
@ -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) |
|||
}) |
|||
|
@ -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<any> { |
|||
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) |
|||
} |
|||
} |
|||
}) |
|||
} |
Loading…
Reference in new issue