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
|
|||
import type Transport from '@ledgerhq/hw-transport' |
|||
import { createSocketDialog } from 'helpers/common' |
|||
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 ( |
|||
transport: Transport<*>, |
|||
{ targetId }: { targetId: string | number }, |
|||
): Promise<string> => |
|||
SKIP_GENUINE |
|||
app: { targetId: string | number, version: string }, |
|||
): Promise<string> => { |
|||
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)) |
|||
: 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
|
|||
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
|
|||
// GETTING APDUS FROM SERVER
|
|||
// SEND THE APDUS TO DEVICE
|
|||
export default async (): Result => new Promise(resolve => resolve(true)) |
|||
type Result = Promise<*> |
|||
|
|||
export default async ( |
|||
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 |
|||
fr: Français |
|||
en: English |
|||
fr: French |
|||
|
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 4.7 KiB |