After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 101 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 135 KiB |
Before Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 201 KiB |
After Width: | Height: | Size: 201 KiB |
@ -0,0 +1,19 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import { createCommand, Command } from 'helpers/ipc' |
||||
|
import { fromPromise } from 'rxjs/observable/fromPromise' |
||||
|
|
||||
|
import getCurrentFirmware from 'helpers/devices/getCurrentFirmware' |
||||
|
|
||||
|
type Input = { |
||||
|
deviceId: string | number, |
||||
|
version: string, |
||||
|
} |
||||
|
|
||||
|
type Result = * |
||||
|
|
||||
|
const cmd: Command<Input, Result> = createCommand('getCurrentFirmware', data => |
||||
|
fromPromise(getCurrentFirmware(data)), |
||||
|
) |
||||
|
|
||||
|
export default cmd |
@ -1,19 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import { createCommand, Command } from 'helpers/ipc' |
|
||||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|
||||
|
|
||||
import getFirmwareInfo from 'helpers/devices/getFirmwareInfo' |
|
||||
|
|
||||
type Input = { |
|
||||
targetId: string | number, |
|
||||
version: string, |
|
||||
} |
|
||||
|
|
||||
type Result = * |
|
||||
|
|
||||
const cmd: Command<Input, Result> = createCommand('getFirmwareInfo', data => |
|
||||
fromPromise(getFirmwareInfo(data)), |
|
||||
) |
|
||||
|
|
||||
export default cmd |
|
@ -0,0 +1,23 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React from 'react' |
||||
|
import { ThemeProvider } from 'styled-components' |
||||
|
import { I18nextProvider } from 'react-i18next' |
||||
|
import theme from 'styles/theme' |
||||
|
import i18n from 'renderer/i18n/electron' |
||||
|
import TriggerAppReady from './TriggerAppReady' |
||||
|
import RenderError from './RenderError' |
||||
|
|
||||
|
// Like App except it just render an error
|
||||
|
|
||||
|
const App = ({ language, error }: { error: Error, language: string }) => ( |
||||
|
<I18nextProvider i18n={i18n} initialLanguage={language}> |
||||
|
<ThemeProvider theme={theme}> |
||||
|
<RenderError disableExport error={error}> |
||||
|
<TriggerAppReady /> |
||||
|
</RenderError> |
||||
|
</ThemeProvider> |
||||
|
</I18nextProvider> |
||||
|
) |
||||
|
|
||||
|
export default App |
@ -0,0 +1,38 @@ |
|||||
|
import React, { PureComponent } from 'react' |
||||
|
import styled from 'styled-components' |
||||
|
|
||||
|
const Indicator = styled.div` |
||||
|
opacity: ${p => (p.busy ? 0.1 : 0)}; |
||||
|
width: 6px; |
||||
|
height: 6px; |
||||
|
border-radius: 3px; |
||||
|
background-color: black; |
||||
|
position: fixed; |
||||
|
top: 4px; |
||||
|
right: 4px; |
||||
|
z-index: 999; |
||||
|
` |
||||
|
|
||||
|
// NB this is done like this to be extremely performant. we don't want redux for this..
|
||||
|
const perPaths = {} |
||||
|
const instances = [] |
||||
|
export const onSetDeviceBusy = (path, busy) => { |
||||
|
perPaths[path] = busy |
||||
|
instances.forEach(i => i.forceUpdate()) |
||||
|
} |
||||
|
|
||||
|
class DeviceBusyIndicator extends PureComponent<{}> { |
||||
|
componentDidMount() { |
||||
|
instances.push(this) |
||||
|
} |
||||
|
componentWillUnmount() { |
||||
|
const i = instances.indexOf(this) |
||||
|
instances.splice(i, 1) |
||||
|
} |
||||
|
render() { |
||||
|
const busy = Object.values(perPaths).reduce((busy, b) => busy || b, false) |
||||
|
return <Indicator busy={busy} /> |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default DeviceBusyIndicator |
@ -0,0 +1,37 @@ |
|||||
|
import React, { PureComponent } from 'react' |
||||
|
import styled from 'styled-components' |
||||
|
|
||||
|
const Indicator = styled.div` |
||||
|
opacity: ${p => (p.busy ? 0.1 : 0)}; |
||||
|
width: 6px; |
||||
|
height: 6px; |
||||
|
border-radius: 3px; |
||||
|
background-color: black; |
||||
|
position: fixed; |
||||
|
bottom: 4px; |
||||
|
right: 4px; |
||||
|
z-index: 999; |
||||
|
` |
||||
|
|
||||
|
// NB this is done like this to be extremely performant. we don't want redux for this..
|
||||
|
let busy = false |
||||
|
const instances = [] |
||||
|
export const onSetLibcoreBusy = b => { |
||||
|
busy = b |
||||
|
instances.forEach(i => i.forceUpdate()) |
||||
|
} |
||||
|
|
||||
|
class LibcoreBusyIndicator extends PureComponent<{}> { |
||||
|
componentDidMount() { |
||||
|
instances.push(this) |
||||
|
} |
||||
|
componentWillUnmount() { |
||||
|
const i = instances.indexOf(this) |
||||
|
instances.splice(i, 1) |
||||
|
} |
||||
|
render() { |
||||
|
return <Indicator busy={busy} /> |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default LibcoreBusyIndicator |
@ -0,0 +1,55 @@ |
|||||
|
// @flow
|
||||
|
import React, { PureComponent } from 'react' |
||||
|
|
||||
|
import type { Device } from 'types/common' |
||||
|
import installMcu from 'commands/installMcu' |
||||
|
|
||||
|
type DeviceInfo = { |
||||
|
targetId: number | string, |
||||
|
version: string, |
||||
|
final: boolean, |
||||
|
mcu: boolean, |
||||
|
} |
||||
|
|
||||
|
type Props = { |
||||
|
device: Device, |
||||
|
deviceInfo: DeviceInfo, |
||||
|
} |
||||
|
|
||||
|
type State = { |
||||
|
flashing: boolean, |
||||
|
} |
||||
|
|
||||
|
class FlashMcu extends PureComponent<Props, State> { |
||||
|
state = { |
||||
|
flashing: false, |
||||
|
} |
||||
|
|
||||
|
flashMCU = async () => { |
||||
|
const { device, deviceInfo } = this.props |
||||
|
const { flashing } = this.state |
||||
|
|
||||
|
if (!flashing) { |
||||
|
this.setState({ flashing: true }) |
||||
|
await installMcu |
||||
|
.send({ |
||||
|
devicePath: device.path, |
||||
|
targetId: deviceInfo.targetId, |
||||
|
version: deviceInfo.version, |
||||
|
}) |
||||
|
.toPromise() |
||||
|
this.setState({ flashing: false }) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
return ( |
||||
|
<div> |
||||
|
<h1>Flashing MCU</h1> |
||||
|
<button onClick={this.flashMCU}>flash</button> |
||||
|
</div> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default FlashMcu |
@ -0,0 +1,9 @@ |
|||||
|
import styled from 'styled-components' |
||||
|
|
||||
|
export const PlaceholderLine = styled.div` |
||||
|
background-color: ${p => (p.dark ? '#C2C2C2' : '#D6D6D6')}; |
||||
|
width: ${p => p.width}px; |
||||
|
height: 10px; |
||||
|
border-radius: 5px; |
||||
|
margin: 5px 0; |
||||
|
` |
@ -0,0 +1,131 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { PureComponent } from 'react' |
||||
|
import { shell, remote } from 'electron' |
||||
|
import qs from 'querystring' |
||||
|
import { translate } from 'react-i18next' |
||||
|
|
||||
|
import { i } from 'helpers/staticPath' |
||||
|
import hardReset from 'helpers/hardReset' |
||||
|
|
||||
|
import type { T } from 'types/common' |
||||
|
|
||||
|
import Spoiler from 'components/base/Spoiler' |
||||
|
import ExportLogsBtn from 'components/ExportLogsBtn' |
||||
|
import Box from 'components/base/Box' |
||||
|
import Space from 'components/base/Space' |
||||
|
import Button from 'components/base/Button' |
||||
|
import TranslatedError from './TranslatedError' |
||||
|
|
||||
|
type Props = { |
||||
|
error: Error, |
||||
|
t: T, |
||||
|
disableExport?: boolean, |
||||
|
children?: *, |
||||
|
} |
||||
|
|
||||
|
class RenderError extends PureComponent<Props, { isHardResetting: boolean }> { |
||||
|
state = { |
||||
|
isHardResetting: false, |
||||
|
} |
||||
|
|
||||
|
handleCreateIssue = () => { |
||||
|
const { error } = this.props |
||||
|
if (!error) { |
||||
|
return |
||||
|
} |
||||
|
const q = qs.stringify({ |
||||
|
title: `Error: ${error.message}`, |
||||
|
body: `Error was thrown:
|
||||
|
|
||||
|
\`\`\` |
||||
|
${error.stack} |
||||
|
\`\`\` |
||||
|
`,
|
||||
|
}) |
||||
|
shell.openExternal(`https://github.com/LedgerHQ/ledger-live-desktop/issues/new?${q}`) |
||||
|
} |
||||
|
|
||||
|
handleRestart = () => { |
||||
|
remote.getCurrentWindow().webContents.reloadIgnoringCache() |
||||
|
} |
||||
|
|
||||
|
handleHardReset = async () => { |
||||
|
this.setState({ isHardResetting: true }) |
||||
|
try { |
||||
|
await hardReset() |
||||
|
remote.getCurrentWindow().webContents.reloadIgnoringCache() |
||||
|
} catch (err) { |
||||
|
this.setState({ isHardResetting: false }) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { error, t, disableExport, children } = this.props |
||||
|
const { isHardResetting } = this.state |
||||
|
return ( |
||||
|
<Box align="center" grow> |
||||
|
<Space of={100} /> |
||||
|
<img alt="" src={i('crash-screen.svg')} width={380} /> |
||||
|
<Space of={40} /> |
||||
|
<Box ff="Museo Sans|Regular" fontSize={7} color="dark"> |
||||
|
{t('app:crash.oops')} |
||||
|
</Box> |
||||
|
<Space of={15} /> |
||||
|
<Box |
||||
|
style={{ width: 500 }} |
||||
|
textAlign="center" |
||||
|
ff="Open Sans|Regular" |
||||
|
color="smoke" |
||||
|
fontSize={4} |
||||
|
> |
||||
|
{t('app:crash.uselessText')} |
||||
|
</Box> |
||||
|
<Space of={30} /> |
||||
|
<Box horizontal flow={2}> |
||||
|
<Button primary onClick={this.handleRestart}> |
||||
|
{t('app:crash.restart')} |
||||
|
</Button> |
||||
|
{!disableExport ? <ExportLogsBtn /> : null} |
||||
|
<Button primary onClick={this.handleCreateIssue}> |
||||
|
{t('app:crash.createTicket')} |
||||
|
</Button> |
||||
|
<Button danger onClick={this.handleHardReset} isLoading={isHardResetting}> |
||||
|
{t('app:crash.reset')} |
||||
|
</Button> |
||||
|
</Box> |
||||
|
<Space of={20} /> |
||||
|
<Spoiler color="wallet" title={t('app:crash.showError')}> |
||||
|
<ErrContainer> |
||||
|
<TranslatedError error={error} /> |
||||
|
</ErrContainer> |
||||
|
</Spoiler> |
||||
|
<Space of={10} /> |
||||
|
<Spoiler color="wallet" title={t('app:crash.showDetails')}> |
||||
|
<ErrContainer>{error.stack}</ErrContainer> |
||||
|
</Spoiler> |
||||
|
<Space of={100} /> |
||||
|
{children} |
||||
|
</Box> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const ErrContainer = ({ children }: { children: any }) => ( |
||||
|
<pre |
||||
|
style={{ |
||||
|
marginTop: 10, |
||||
|
maxWidth: '80%', |
||||
|
overflow: 'auto', |
||||
|
fontSize: 10, |
||||
|
fontFamily: 'monospace', |
||||
|
background: 'rgba(0, 0, 0, 0.05)', |
||||
|
cursor: 'text', |
||||
|
userSelect: 'text', |
||||
|
}} |
||||
|
> |
||||
|
{children} |
||||
|
</pre> |
||||
|
) |
||||
|
|
||||
|
export default translate()(RenderError) |
@ -0,0 +1,16 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { PureComponent } from 'react' |
||||
|
import moment from 'moment' |
||||
|
import { translate } from 'react-i18next' |
||||
|
import type { T } from 'types/common' |
||||
|
import Box from './base/Box' |
||||
|
|
||||
|
class SyncAgo extends PureComponent<{ t: T, date: Date }> { |
||||
|
render() { |
||||
|
const { t, date } = this.props |
||||
|
return <Box p={4}>{t('app:common.sync.ago', { time: moment(date).fromNow() })}</Box> |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default translate()(SyncAgo) |
@ -0,0 +1,16 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import { PureComponent } from 'react' |
||||
|
|
||||
|
export default class TriggerAppReady extends PureComponent<{}> { |
||||
|
componentDidMount() { |
||||
|
window.requestAnimationFrame(() => (this._timeout = setTimeout(() => window.onAppReady(), 300))) |
||||
|
} |
||||
|
componentWillUnmount() { |
||||
|
clearTimeout(this._timeout) |
||||
|
} |
||||
|
_timeout: * |
||||
|
render() { |
||||
|
return null |
||||
|
} |
||||
|
} |
@ -1,15 +0,0 @@ |
|||||
// @flow
|
|
||||
import { PureComponent } from 'react' |
|
||||
|
|
||||
type Props = { |
|
||||
callback: () => void, |
|
||||
} |
|
||||
|
|
||||
class TriggerOnMount extends PureComponent<Props> { |
|
||||
componentDidMount() { |
|
||||
const { callback } = this.props |
|
||||
callback() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export default TriggerOnMount |
|
@ -0,0 +1,26 @@ |
|||||
|
// @flow
|
||||
|
import axios from 'axios' |
||||
|
|
||||
|
import { GET_CURRENT_FIRMWARE } from 'helpers/urls' |
||||
|
|
||||
|
type Input = { |
||||
|
version: string, |
||||
|
deviceId: string | number, |
||||
|
} |
||||
|
|
||||
|
let error |
||||
|
export default async (input: Input): Promise<*> => { |
||||
|
try { |
||||
|
const provider = 1 |
||||
|
const { data } = await axios.post(GET_CURRENT_FIRMWARE, { |
||||
|
device_version: input.deviceId, |
||||
|
version_name: input.version, |
||||
|
provider, |
||||
|
}) |
||||
|
return data |
||||
|
} catch (err) { |
||||
|
error = Error(err.message) |
||||
|
error.stack = err.stack |
||||
|
throw error |
||||
|
} |
||||
|
} |
@ -0,0 +1,19 @@ |
|||||
|
// @flow
|
||||
|
import axios from 'axios' |
||||
|
|
||||
|
import { GET_DEVICE_VERSION } from 'helpers/urls' |
||||
|
|
||||
|
export default async (targetId: string | number): Promise<*> => { |
||||
|
try { |
||||
|
const provider = 1 |
||||
|
const { data } = await axios.post(GET_DEVICE_VERSION, { |
||||
|
provider, |
||||
|
target_id: targetId, |
||||
|
}) |
||||
|
return data |
||||
|
} catch (err) { |
||||
|
const error = Error(err.message) |
||||
|
error.stack = err.stack |
||||
|
throw err |
||||
|
} |
||||
|
} |
@ -1,34 +0,0 @@ |
|||||
// @flow
|
|
||||
import axios from 'axios' |
|
||||
import isEmpty from 'lodash/isEmpty' |
|
||||
|
|
||||
import { MANAGER_API_BASE } from 'config/constants' |
|
||||
|
|
||||
type Input = { |
|
||||
version: string, |
|
||||
targetId: string | number, |
|
||||
} |
|
||||
|
|
||||
let error |
|
||||
export default async (data: Input) => { |
|
||||
try { |
|
||||
const { data: seFirmwareVersion } = await axios.post( |
|
||||
`${MANAGER_API_BASE}/firmware_versions_name`, |
|
||||
{ |
|
||||
se_firmware_name: data.version, |
|
||||
target_id: data.targetId, |
|
||||
}, |
|
||||
) |
|
||||
|
|
||||
if (!isEmpty(seFirmwareVersion)) { |
|
||||
return seFirmwareVersion |
|
||||
} |
|
||||
|
|
||||
error = Error('could not retrieve firmware informations, try again later') |
|
||||
throw error |
|
||||
} catch (err) { |
|
||||
error = Error(err.message) |
|
||||
error.stack = err.stack |
|
||||
throw error |
|
||||
} |
|
||||
} |
|
@ -1,12 +1,26 @@ |
|||||
// @flow
|
// @flow
|
||||
import type Transport from '@ledgerhq/hw-transport' |
import type Transport from '@ledgerhq/hw-transport' |
||||
import { createSocketDialog } from 'helpers/common' |
|
||||
import { SKIP_GENUINE } from 'config/constants' |
import { SKIP_GENUINE } from 'config/constants' |
||||
|
import { WS_GENUINE } from 'helpers/urls' |
||||
|
|
||||
|
import { createDeviceSocket } from 'helpers/socket' |
||||
|
import getCurrentFirmware from './getCurrentFirmware' |
||||
|
import getDeviceVersion from './getDeviceVersion' |
||||
|
|
||||
export default async ( |
export default async ( |
||||
transport: Transport<*>, |
transport: Transport<*>, |
||||
{ targetId }: { targetId: string | number }, |
app: { targetId: string | number, version: string }, |
||||
): Promise<string> => |
): Promise<string> => { |
||||
SKIP_GENUINE |
const { targetId, version } = app |
||||
|
const device = await getDeviceVersion(app.targetId) |
||||
|
const firmware = await getCurrentFirmware({ deviceId: device.id, version }) |
||||
|
const params = { |
||||
|
targetId, |
||||
|
version, |
||||
|
perso: firmware.perso, |
||||
|
} |
||||
|
const url = WS_GENUINE(params) |
||||
|
return SKIP_GENUINE |
||||
? new Promise(resolve => setTimeout(() => resolve('0000'), 1000)) |
? new Promise(resolve => setTimeout(() => resolve('0000'), 1000)) |
||||
: createSocketDialog(transport, '/genuine', { targetId }, true) |
: createDeviceSocket(transport, url).toPromise() |
||||
|
} |
||||
|
@ -0,0 +1,26 @@ |
|||||
|
// @flow
|
||||
|
import axios from 'axios' |
||||
|
|
||||
|
import { GET_NEXT_MCU } from 'helpers/urls' |
||||
|
import createCustomErrorClass from 'helpers/createCustomErrorClass' |
||||
|
|
||||
|
const LatestMCUInstalledError = createCustomErrorClass('LatestMCUInstalledError') |
||||
|
|
||||
|
export default async (bootloaderVersion: string): Promise<*> => { |
||||
|
try { |
||||
|
const { data } = await axios.post(GET_NEXT_MCU, { |
||||
|
bootloader_version: bootloaderVersion, |
||||
|
}) |
||||
|
|
||||
|
// FIXME: nextVersion will not be able to "default" when Error
|
||||
|
// handling is standardize on the API side
|
||||
|
if (data === 'default' || !data.name) { |
||||
|
throw new LatestMCUInstalledError('there is no next mcu version to install') |
||||
|
} |
||||
|
return data |
||||
|
} catch (err) { |
||||
|
const error = Error(err.message) |
||||
|
error.stack = err.stack |
||||
|
throw err |
||||
|
} |
||||
|
} |
@ -1,8 +1,23 @@ |
|||||
// @flow
|
// @flow
|
||||
|
import type Transport from '@ledgerhq/hw-transport' |
||||
|
|
||||
type Result = Promise<boolean> |
import { WS_MCU } from 'helpers/urls' |
||||
|
import { createDeviceSocket } from 'helpers/socket' |
||||
|
import getNextMCU from 'helpers/devices/getNextMCU' |
||||
|
|
||||
// TODO: IMPLEMENTATION FOR FLASHING FIRMWARE
|
type Result = Promise<*> |
||||
// GETTING APDUS FROM SERVER
|
|
||||
// SEND THE APDUS TO DEVICE
|
export default async ( |
||||
export default async (): Result => new Promise(resolve => resolve(true)) |
transport: Transport<*>, |
||||
|
args: { targetId: string | number, version: string }, |
||||
|
): Result => { |
||||
|
const { version } = args |
||||
|
const nextVersion = await getNextMCU(version) |
||||
|
|
||||
|
const params = { |
||||
|
targetId: args.targetId, |
||||
|
version: nextVersion.name, |
||||
|
} |
||||
|
const url = WS_MCU(params) |
||||
|
return createDeviceSocket(transport, url).toPromise() |
||||
|
} |
||||
|
@ -0,0 +1,27 @@ |
|||||
|
// @flow
|
||||
|
import qs from 'qs' |
||||
|
|
||||
|
import { MANAGER_API_BASE, BASE_SOCKET_URL_SECURE } from 'config/constants' |
||||
|
import type { LedgerScriptParams } from 'helpers/common' |
||||
|
|
||||
|
const urlBuilder = (base: string) => (endpoint: string): string => `${base}/${endpoint}` |
||||
|
|
||||
|
const managerUrlbuilder = urlBuilder(MANAGER_API_BASE) |
||||
|
|
||||
|
const wsURLBuilder = (endpoint: string) => (params?: Object) => |
||||
|
`${BASE_SOCKET_URL_SECURE}/${endpoint}${params ? `?${qs.stringify(params)}` : ''}` |
||||
|
|
||||
|
// const wsURLBuilderProxy = (endpoint: string) => (params?: Object) =>
|
||||
|
// `ws://manager.ledger.fr:3501/${endpoint}${params ? `?${qs.stringify(params)}` : ''}`
|
||||
|
|
||||
|
export const GET_DEVICE_VERSION: string = managerUrlbuilder('get_device_version') |
||||
|
export const APPLICATIONS_BY_DEVICE: string = managerUrlbuilder('get_apps') |
||||
|
export const GET_CURRENT_FIRMWARE: string = managerUrlbuilder('get_firmware_version') |
||||
|
export const GET_LATEST_FIRMWARE: string = managerUrlbuilder('get_latest_firmware') |
||||
|
export const GET_NEXT_MCU: string = managerUrlbuilder('mcu_versions_bootloader') |
||||
|
|
||||
|
export const WS_INSTALL: (arg: LedgerScriptParams) => string = wsURLBuilder('install') |
||||
|
export const WS_GENUINE: (arg: { targetId: string | number }) => string = wsURLBuilder('genuine') |
||||
|
export const WS_MCU: (arg: { targetId: string | number, version: string }) => string = wsURLBuilder( |
||||
|
'mcu', |
||||
|
) |
@ -1,3 +1,3 @@ |
|||||
--- |
--- |
||||
en: Anglais |
en: English |
||||
fr: Français |
fr: French |
||||
|
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 4.7 KiB |