meriadec
6 years ago
158 changed files with 2046 additions and 2434 deletions
@ -1,91 +0,0 @@ |
|||
// @flow
|
|||
import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types' |
|||
import { BigNumber } from 'bignumber.js' |
|||
import { LedgerAPINotAvailable } from 'config/errors' |
|||
import network from './network' |
|||
import { blockchainBaseURL } from './Ledger' |
|||
|
|||
export type Block = { height: number } // TODO more fields actually
|
|||
export type Tx = { |
|||
hash: string, |
|||
received_at: string, |
|||
nonce: string, |
|||
value: number, |
|||
gas: number, |
|||
gas_price: number, |
|||
cumulative_gas_used: number, |
|||
gas_used: number, |
|||
from: string, |
|||
to: string, |
|||
input: string, |
|||
index: number, |
|||
block?: { |
|||
hash: string, |
|||
height: number, |
|||
time: string, |
|||
}, |
|||
confirmations: number, |
|||
} |
|||
|
|||
export type API = { |
|||
getTransactions: ( |
|||
address: string, |
|||
blockHash: ?string, |
|||
) => Promise<{ |
|||
truncated: boolean, |
|||
txs: Tx[], |
|||
}>, |
|||
getCurrentBlock: () => Promise<Block>, |
|||
getAccountNonce: (address: string) => Promise<number>, |
|||
broadcastTransaction: (signedTransaction: string) => Promise<string>, |
|||
getAccountBalance: (address: string) => Promise<BigNumber>, |
|||
} |
|||
|
|||
export const apiForCurrency = (currency: CryptoCurrency): API => { |
|||
const baseURL = blockchainBaseURL(currency) |
|||
if (!baseURL) { |
|||
throw new LedgerAPINotAvailable(`LedgerAPINotAvailable ${currency.id}`, { |
|||
currencyName: currency.name, |
|||
}) |
|||
} |
|||
return { |
|||
async getTransactions(address, blockHash) { |
|||
const { data } = await network({ |
|||
method: 'GET', |
|||
url: `${baseURL}/addresses/${address}/transactions`, |
|||
params: { blockHash, noToken: 1 }, |
|||
}) |
|||
return data |
|||
}, |
|||
async getCurrentBlock() { |
|||
const { data } = await network({ |
|||
method: 'GET', |
|||
url: `${baseURL}/blocks/current`, |
|||
}) |
|||
return data |
|||
}, |
|||
async getAccountNonce(address) { |
|||
const { data } = await network({ |
|||
method: 'GET', |
|||
url: `${baseURL}/addresses/${address}/nonce`, |
|||
}) |
|||
return data[0].nonce |
|||
}, |
|||
async broadcastTransaction(tx) { |
|||
const { data } = await network({ |
|||
method: 'POST', |
|||
url: `${baseURL}/transactions/send`, |
|||
data: { tx }, |
|||
}) |
|||
return data.result |
|||
}, |
|||
async getAccountBalance(address) { |
|||
const { data } = await network({ |
|||
method: 'GET', |
|||
url: `${baseURL}/addresses/${address}/balance`, |
|||
}) |
|||
// FIXME precision lost here. nothing we can do easily
|
|||
return BigNumber(data[0].balance) |
|||
}, |
|||
} |
|||
} |
@ -1,31 +0,0 @@ |
|||
// @flow
|
|||
import invariant from 'invariant' |
|||
import LRU from 'lru-cache' |
|||
import type { Currency } from '@ledgerhq/live-common/lib/types' |
|||
import { FeeEstimationFailed } from 'config/errors' |
|||
import { blockchainBaseURL } from './Ledger' |
|||
import network from './network' |
|||
|
|||
export type Fees = { |
|||
[_: string]: number, |
|||
} |
|||
|
|||
const cache = LRU({ |
|||
maxAge: 5 * 60 * 1000, |
|||
}) |
|||
|
|||
export const getEstimatedFees = async (currency: Currency): Promise<Fees> => { |
|||
const key = currency.id |
|||
let promise = cache.get(key) |
|||
if (promise) return promise.then(r => r.data) |
|||
const baseURL = blockchainBaseURL(currency) |
|||
invariant(baseURL, `Fees for ${currency.id} are not supported`) |
|||
promise = network({ method: 'GET', url: `${baseURL}/fees` }) |
|||
cache.set(key, promise) |
|||
const { data, status } = await promise |
|||
if (status < 200 || status >= 300) cache.del(key) |
|||
if (data) { |
|||
return data |
|||
} |
|||
throw new FeeEstimationFailed(`FeeEstimationFailed ${status}`, { httpStatus: status }) |
|||
} |
@ -1,6 +0,0 @@ |
|||
// @flow
|
|||
import type { Currency } from '@ledgerhq/live-common/lib/types' |
|||
import { LEDGER_REST_API_BASE } from 'config/constants' |
|||
|
|||
export const blockchainBaseURL = ({ ledgerExplorerId }: Currency): ?string => |
|||
ledgerExplorerId ? `${LEDGER_REST_API_BASE}/blockchain/v2/${ledgerExplorerId}` : null |
@ -1,47 +0,0 @@ |
|||
// @flow
|
|||
import logger from 'logger' |
|||
import { BigNumber } from 'bignumber.js' |
|||
import { RippleAPI } from 'ripple-lib' |
|||
import { |
|||
parseCurrencyUnit, |
|||
getCryptoCurrencyById, |
|||
formatCurrencyUnit, |
|||
} from '@ledgerhq/live-common/lib/currencies' |
|||
|
|||
const rippleUnit = getCryptoCurrencyById('ripple').units[0] |
|||
|
|||
export const defaultEndpoint = 'wss://s2.ripple.com' |
|||
|
|||
export const apiForEndpointConfig = (endpointConfig: ?string = null) => { |
|||
const server = endpointConfig || defaultEndpoint |
|||
const api = new RippleAPI({ server }) |
|||
api.on('error', (errorCode, errorMessage) => { |
|||
logger.warn(`Ripple API error: ${errorCode}: ${errorMessage}`) |
|||
}) |
|||
return api |
|||
} |
|||
|
|||
export const parseAPIValue = (value: string) => parseCurrencyUnit(rippleUnit, value) |
|||
|
|||
export const parseAPICurrencyObject = ({ |
|||
currency, |
|||
value, |
|||
}: { |
|||
currency: string, |
|||
value: string, |
|||
}) => { |
|||
if (currency !== 'XRP') { |
|||
logger.warn(`RippleJS: attempt to parse unknown currency ${currency}`) |
|||
return BigNumber(0) |
|||
} |
|||
return parseAPIValue(value) |
|||
} |
|||
|
|||
export const formatAPICurrencyXRP = (amount: BigNumber) => { |
|||
const value = formatCurrencyUnit(rippleUnit, amount, { |
|||
showAllDigits: true, |
|||
disableRounding: true, |
|||
useGrouping: false, |
|||
}) |
|||
return { currency: 'XRP', value } |
|||
} |
Binary file not shown.
@ -0,0 +1,17 @@ |
|||
// @flow
|
|||
|
|||
import main from '@ledgerhq/live-common/lib/hw/firmwareUpdate-main' |
|||
import type { FirmwareUpdateContext } from '@ledgerhq/live-common/lib/types/manager' |
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
|
|||
type Input = FirmwareUpdateContext |
|||
|
|||
type Result = { progress: number, installing: ?string } |
|||
|
|||
const cmd: Command<Input, Result> = createCommand( |
|||
'firmwareMain', |
|||
firmware => main('', firmware), |
|||
// devicePath='' HACK to not depend on a devicePath because it's dynamic
|
|||
) |
|||
|
|||
export default cmd |
@ -0,0 +1,18 @@ |
|||
// @flow
|
|||
|
|||
import prepare from '@ledgerhq/live-common/lib/hw/firmwareUpdate-prepare' |
|||
import type { FirmwareUpdateContext } from '@ledgerhq/live-common/lib/types/manager' |
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
|
|||
type Input = { |
|||
devicePath: string, |
|||
firmware: FirmwareUpdateContext, |
|||
} |
|||
|
|||
type Result = { progress: number } |
|||
|
|||
const cmd: Command<Input, Result> = createCommand('firmwarePrepare', ({ devicePath, firmware }) => |
|||
prepare(devicePath, firmware), |
|||
) |
|||
|
|||
export default cmd |
@ -0,0 +1,17 @@ |
|||
// @flow
|
|||
|
|||
import repair from '@ledgerhq/live-common/lib/hw/firmwareUpdate-repair' |
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
|
|||
type Input = { |
|||
version: ?string, |
|||
} |
|||
|
|||
type Result = { progress: number } |
|||
|
|||
const cmd: Command<Input, Result> = createCommand( |
|||
'firmwareRepair', |
|||
({ version }) => repair('', version), // devicePath='' HACK to not depend on a devicePath because it's dynamic
|
|||
) |
|||
|
|||
export default cmd |
@ -1,21 +0,0 @@ |
|||
// @flow
|
|||
|
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|||
|
|||
import getCurrentFirmware from 'helpers/devices/getCurrentFirmware' |
|||
import type { FinalFirmware } from 'helpers/types' |
|||
|
|||
type Input = { |
|||
deviceId: string | number, |
|||
fullVersion: string, |
|||
provider: number, |
|||
} |
|||
|
|||
type Result = FinalFirmware |
|||
|
|||
const cmd: Command<Input, Result> = createCommand('getCurrentFirmware', data => |
|||
fromPromise(getCurrentFirmware(data)), |
|||
) |
|||
|
|||
export default cmd |
@ -1,15 +1,14 @@ |
|||
// @flow
|
|||
|
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|||
import type { DeviceInfo, OsuFirmware } from 'helpers/types' |
|||
import { from } from 'rxjs' |
|||
import type { DeviceInfo, FirmwareUpdateContext } from '@ledgerhq/live-common/lib/types/manager' |
|||
import manager from '@ledgerhq/live-common/lib/manager' |
|||
|
|||
import getLatestFirmwareForDevice from '../helpers/devices/getLatestFirmwareForDevice' |
|||
type Result = ?FirmwareUpdateContext |
|||
|
|||
type Result = ?(OsuFirmware & { shouldFlashMcu: boolean }) |
|||
|
|||
const cmd: Command<DeviceInfo, Result> = createCommand('getLatestFirmwareForDevice', data => |
|||
fromPromise(getLatestFirmwareForDevice(data)), |
|||
const cmd: Command<DeviceInfo, Result> = createCommand('getLatestFirmwareForDevice', deviceInfo => |
|||
from(manager.getLatestFirmwareForDevice(deviceInfo)), |
|||
) |
|||
|
|||
export default cmd |
|||
|
@ -1,19 +0,0 @@ |
|||
// @flow
|
|||
|
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|||
|
|||
import { withDevice } from 'helpers/deviceAccess' |
|||
import getMemInfo from 'helpers/devices/getMemInfo' |
|||
|
|||
type Input = { |
|||
devicePath: string, |
|||
} |
|||
|
|||
type Result = * |
|||
|
|||
const cmd: Command<Input, Result> = createCommand('getMemInfo', ({ devicePath }) => |
|||
fromPromise(withDevice(devicePath)(transport => getMemInfo(transport))), |
|||
) |
|||
|
|||
export default cmd |
@ -1,25 +1,19 @@ |
|||
// @flow
|
|||
|
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|||
|
|||
import { withDevice } from 'helpers/deviceAccess' |
|||
import installApp from 'helpers/apps/installApp' |
|||
|
|||
import type { ApplicationVersion } from 'helpers/types' |
|||
import installApp from '@ledgerhq/live-common/lib/hw/installApp' |
|||
import { withDevice } from '@ledgerhq/live-common/lib/hw/deviceAccess' |
|||
import type { ApplicationVersion } from '@ledgerhq/live-common/lib/types/manager' |
|||
|
|||
type Input = { |
|||
app: ApplicationVersion, |
|||
devicePath: string, |
|||
targetId: string | number, |
|||
app: ApplicationVersion, |
|||
} |
|||
|
|||
type Result = void |
|||
type Result = { progress: number } |
|||
|
|||
const cmd: Command<Input, Result> = createCommand( |
|||
'installApp', |
|||
({ devicePath, targetId, ...app }) => |
|||
fromPromise(withDevice(devicePath)(transport => installApp(transport, targetId, app))), |
|||
const cmd: Command<Input, Result> = createCommand('installApp', ({ devicePath, targetId, app }) => |
|||
withDevice(devicePath)(transport => installApp(transport, targetId, app)), |
|||
) |
|||
|
|||
export default cmd |
|||
|
@ -1,21 +0,0 @@ |
|||
// @flow
|
|||
|
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|||
import { withDevice } from 'helpers/deviceAccess' |
|||
|
|||
import installFinalFirmware from 'helpers/firmware/installFinalFirmware' |
|||
|
|||
type Input = { |
|||
devicePath: string, |
|||
} |
|||
|
|||
type Result = { |
|||
success: boolean, |
|||
} |
|||
|
|||
const cmd: Command<Input, Result> = createCommand('installFinalFirmware', ({ devicePath }) => |
|||
fromPromise(withDevice(devicePath)(transport => installFinalFirmware(transport))), |
|||
) |
|||
|
|||
export default cmd |
@ -1,19 +0,0 @@ |
|||
// @flow
|
|||
|
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|||
|
|||
import { withDevice } from 'helpers/deviceAccess' |
|||
import installMcu from 'helpers/firmware/installMcu' |
|||
|
|||
type Input = { |
|||
devicePath: string, |
|||
} |
|||
|
|||
type Result = void |
|||
|
|||
const cmd: Command<Input, Result> = createCommand('installMcu', ({ devicePath }) => |
|||
fromPromise(withDevice(devicePath)(transport => installMcu(transport))), |
|||
) |
|||
|
|||
export default cmd |
@ -1,27 +0,0 @@ |
|||
// @flow
|
|||
|
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|||
|
|||
import { withDevice } from 'helpers/deviceAccess' |
|||
import installOsuFirmware from 'helpers/firmware/installOsuFirmware' |
|||
|
|||
import type { Firmware } from 'components/modals/UpdateFirmware' |
|||
|
|||
type Input = { |
|||
devicePath: string, |
|||
targetId: string | number, |
|||
firmware: Firmware, |
|||
} |
|||
|
|||
type Result = { success: boolean } |
|||
|
|||
const cmd: Command<Input, Result> = createCommand( |
|||
'installOsuFirmware', |
|||
({ devicePath, firmware, targetId }) => |
|||
fromPromise( |
|||
withDevice(devicePath)(transport => installOsuFirmware(transport, targetId, firmware)), |
|||
), |
|||
) |
|||
|
|||
export default cmd |
@ -1,19 +0,0 @@ |
|||
// @flow
|
|||
|
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|||
import { withDevice } from 'helpers/deviceAccess' |
|||
|
|||
import isDashboardOpen from '../helpers/devices/isDashboardOpen' |
|||
|
|||
type Input = { |
|||
devicePath: string, |
|||
} |
|||
|
|||
type Result = boolean |
|||
|
|||
const cmd: Command<Input, Result> = createCommand('isDashboardOpen', ({ devicePath }) => |
|||
fromPromise(withDevice(devicePath)(transport => isDashboardOpen(transport))), |
|||
) |
|||
|
|||
export default cmd |
@ -1,15 +0,0 @@ |
|||
// @flow
|
|||
|
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|||
import type { DeviceInfo, ApplicationVersion } from 'helpers/types' |
|||
|
|||
import listAppVersions from 'helpers/apps/listAppVersions' |
|||
|
|||
type Result = Array<ApplicationVersion> |
|||
|
|||
const cmd: Command<DeviceInfo, Result> = createCommand('listAppVersions', deviceInfo => |
|||
fromPromise(listAppVersions(deviceInfo)), |
|||
) |
|||
|
|||
export default cmd |
@ -1,15 +0,0 @@ |
|||
// @flow
|
|||
|
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|||
|
|||
import listApps from 'helpers/apps/listApps' |
|||
import type { Application } from 'helpers/types' |
|||
|
|||
type Input = void |
|||
|
|||
type Result = Array<Application> |
|||
|
|||
const cmd: Command<Input, Result> = createCommand('listApps', () => fromPromise(listApps())) |
|||
|
|||
export default cmd |
@ -1,17 +0,0 @@ |
|||
// @flow
|
|||
|
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|||
|
|||
import listCategories from 'helpers/apps/listCategories' |
|||
import type { Category } from 'helpers/types' |
|||
|
|||
type Input = void |
|||
|
|||
type Result = Array<Category> |
|||
|
|||
const cmd: Command<Input, Result> = createCommand('listCategories', () => |
|||
fromPromise(listCategories()), |
|||
) |
|||
|
|||
export default cmd |
@ -1,15 +0,0 @@ |
|||
// @flow
|
|||
|
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|||
import shouldFlashMcu from 'helpers/devices/shouldFlashMcu' |
|||
|
|||
import type { DeviceInfo } from 'helpers/types' |
|||
|
|||
type Result = boolean |
|||
|
|||
const cmd: Command<DeviceInfo, Result> = createCommand('shouldFlashMcu', data => |
|||
fromPromise(shouldFlashMcu(data)), |
|||
) |
|||
|
|||
export default cmd |
@ -0,0 +1,67 @@ |
|||
// @flow
|
|||
|
|||
import React from 'react' |
|||
|
|||
const styles = { |
|||
fill: '#0086fb', |
|||
} |
|||
|
|||
const inner = ( |
|||
<> |
|||
<path |
|||
style={styles} |
|||
d="M528.34,146.17,561.36,24H465.6l-19.81,76c-17.61-6.61-36.32-11-55-14.31L407.27,24H311.51L296.1,83.44C213.56,94.44,137.61,137.37,87,204.51-18.68,342.09,7.73,540.21,145.32,645.87L112.3,768H207l19.81-75.94c17.61,6.6,36.33,11,55,14.31L265.29,768h94.65l16.51-59.43C459,697.6,535,654.68,585.58,587.54,692.34,448.85,665.92,251.84,528.34,146.17ZM336.83,469.77a74.85,74.85,0,1,1,74.84-74.85C411.67,436.75,378.65,469.77,336.83,469.77Z" |
|||
/> |
|||
<path |
|||
style={styles} |
|||
d="M590,713c0-9.91-6.61-15.41-17.61-15.41H553.66v42.93h11V728.42h8.81l6.6,12.11h12.11l-9.91-13.21C587.78,725.12,591.08,719.61,590,713Zm-9.91,0c0,4.4-2.2,7.7-6.6,7.7h-8.81v-14.3h7.71c3.3-1.1,6.6,2.2,7.7,5.5Z" |
|||
/> |
|||
<path |
|||
style={styles} |
|||
d="M571.27,675.59a46.23,46.23,0,1,0,46.22,46.23C616.39,696.5,596.58,675.59,571.27,675.59Zm0,81.45a35.23,35.23,0,1,1,35.22-35.22A34.77,34.77,0,0,1,571.27,757Z" |
|||
/> |
|||
<path |
|||
style={styles} |
|||
d="M1072.06,392.72c35.22-7.7,58.34-34.12,58.34-75.94,0-50.63-37.42-83.65-95.76-83.65H888.26V558.92H1039c59.44,0,96.86-35.22,96.86-90.25C1135.9,425.74,1109.49,397.12,1072.06,392.72Zm-95.75-96.86h35.22A32,32,0,0,1,1041.25,330v4.41c-1.1,17.61-16.51,30.81-35.22,29.71H976.31ZM1044.55,461c-1.1,17.61-17.61,31.92-36.32,29.72H977.41V420.24h36.32a33.24,33.24,0,0,1,30.82,35.22Z" |
|||
/> |
|||
<polygon |
|||
style={styles} |
|||
points="1152.41 315.68 1230.56 316.77 1230.56 558.92 1320.81 558.92 1320.81 316.77 1397.86 316.77 1397.86 233.13 1152.41 233.13 1152.41 315.68" |
|||
/> |
|||
<path |
|||
style={styles} |
|||
d="M1575.47,310c36.7,0,59.37,19.61,65.85,49h92.85c-6.48-85-66.94-134-158.7-134-97.16,0-164.1,70.8-164.1,171,0,99.12,65.86,171,164.1,171,91.76,0,151.14-49,158.7-134h-92.85c-5.4,29.41-29.15,49-65.85,49-45.34,0-72.33-32.67-72.33-86.05S1530.13,310,1575.47,310Z" |
|||
/> |
|||
<path |
|||
style={styles} |
|||
d="M1871.75,233.13h-90.26V558.92h90.26c100.16,0,154.09-79.25,154.09-162.9S1971.91,233.13,1871.75,233.13Zm-2.2,296.07h-59.44V261.74h59.44c83.65,0,126.57,66,126.57,134.28C1996.12,463.16,1953.2,529.2,1869.55,529.2Z" |
|||
/> |
|||
<path |
|||
style={styles} |
|||
d="M2087.85,248.54c-14.31,1.1-25.32,13.2-24.22,27.51v3.3c1.1,14.31,13.21,25.32,27.52,24.22s25.31-13.21,24.21-27.52v-3.3C2114.26,258.44,2102.16,247.43,2087.85,248.54Z" |
|||
/> |
|||
<rect style={styles} x="2075.19" y="346.49" width="28.62" height="212.43" /> |
|||
<path |
|||
style={styles} |
|||
d="M2199.74,402.63V346.49h-27.51V557.82h27.51V466.46c0-46.22,17.61-95.75,62.74-95.75,6.6,0,12.11,0,18.71,1.1V344.29a96.38,96.38,0,0,0-17.61-2.2C2235,342.09,2208.55,360.8,2199.74,402.63Z" |
|||
/> |
|||
<path |
|||
style={styles} |
|||
d="M2403.36,341c-57.23,0-95.75,42.93-95.75,111.17s37.42,112.26,96.85,112.26c44,0,79.25-27.51,86.95-66H2463.9c-8.81,24.22-33,40.73-59.44,38.53-40.72,0-68.24-29.72-68.24-77h158.5c0-6.6,1.1-13.21,0-19.81C2494.72,382.81,2458.39,341,2403.36,341Zm-67.14,94.66c2.2-41.83,27.52-70.45,67.14-70.45s63.84,28.62,64.94,70.45Z" |
|||
/> |
|||
<path |
|||
style={styles} |
|||
d="M2627.89,369.61c35.23,0,57.24,16.51,62.74,49.53h27.52c-5.51-47.33-39.63-77.05-90.26-77.05-60.53,0-97.95,44-97.95,110.07s37.42,111.16,97.95,112.26c51.74,0,85.86-29.71,90.26-75.94h-27.52c-5.5,31.92-28.61,48.43-62.74,48.43-42.92,0-69.34-33-69.34-83.65S2585,369.61,2627.89,369.61Z" |
|||
/> |
|||
<path |
|||
style={styles} |
|||
d="M2843.62,534.71c-18.71,0-34.12-6.61-34.12-50.64V372.91h53.93V346.49H2809.5V276.05H2782v70.44h-37.43v26.42H2782V489.58c0,66,31.91,71.54,56.13,71.54,11,1.1,22-1.1,31.92-3.3V530.3C2861.23,532.5,2852.43,533.6,2843.62,534.71Z" |
|||
/> |
|||
</> |
|||
) |
|||
|
|||
export default ({ width }: { width: number }) => ( |
|||
<svg width={width} viewBox="0 0 2892.16 792.05"> |
|||
{inner} |
|||
</svg> |
|||
) |
@ -0,0 +1,116 @@ |
|||
// @flow
|
|||
|
|||
import React from 'react' |
|||
|
|||
const styles = { |
|||
blue: { |
|||
fill: '#334F93', |
|||
}, |
|||
pink: { |
|||
fill: '#EC2D6E', |
|||
}, |
|||
} |
|||
|
|||
const inner = ( |
|||
<> |
|||
<path |
|||
style={styles.blue} |
|||
d="M146.3,101.8c5.2,0,9.4-4,9.4-9.4c0-5.2-4.1-9.5-9.4-9.5c-5.5,0-9.5,4.4-9.5,9.5 |
|||
C136.8,97.7,140.7,101.8,146.3,101.8" |
|||
/> |
|||
<path |
|||
style={styles.blue} |
|||
d="M102.3,164.7c8.4,0,9.4-10.3,9.4-25.7c0-18.7-2-25.8-9.5-25.8c-8.9,0-9.6,12.1-9.6,25.8 |
|||
C92.6,152.1,92.6,164.7,102.3,164.7 M102.2,109.4c15.9,0,27.3,12.7,27.3,29.6c0,16.1-10.8,29.5-27.3,29.5 |
|||
c-16.9,0-27.4-13.7-27.4-29.5C74.8,123.4,85.4,109.4,102.2,109.4" |
|||
/> |
|||
<path |
|||
style={styles.blue} |
|||
d="M246.2,155c0,3.4,1.6,9.6,8.3,9.6c7.3,0,11.5-6.1,11.5-25.2c0-7.1-0.8-23.3-11.3-23.3c-5.6,0-7.4,4.5-8.5,7.1 |
|||
V155z M222.6,84.9h23.6v31.4c2-2.2,6.5-6.8,14.7-6.8c14,0,22.8,12.3,22.8,28.1c0,15.4-8.3,30.9-28.3,30.9c-2.8,0-6.8-0.9-11-3.5 |
|||
c-2.8-1.8-6.3-1.8-9,0.1l-4.7,3.3h-1.4V94.8c0-5.7-2.3-6.1-6.7-7V84.9z" |
|||
/> |
|||
<path |
|||
style={styles.blue} |
|||
d="M77.8,137.3H76c-0.2,6.7-2.4,12.8-5.9,16.9c-0.6,0.8-2.6,2.6-3.5,3.4c-2,1.7-4.8,2.8-8.8,2.8 |
|||
c-9.5,0-16.9-9.8-16.9-26.8c0-5.5,0.9-20.3,10.3-20.3c1.8,0,4.9,1.1,4.9,4.7c0,6.6,0,12.5,6.8,12.5c1,0,8.3,0,8.3-7.7 |
|||
c0-8.7-10.2-13.2-18.8-13.2c-11.4,0-28.6,7.4-28.6,31.3c0,15,10.4,27.8,25.9,27.8c7.6,0,14.3-3,19.5-8.3 |
|||
C70.2,159.2,77.8,152.3,77.8,137.3" |
|||
/> |
|||
<path |
|||
style={styles.blue} |
|||
d="M370.1,166.7h-32.3v-2.9c3.9-0.8,6.6-1.2,6.6-7.3V121c0-6-2.9-6.7-6.6-7.1v-3h22.9l0.1,4.4c0,1.6,2,2.4,3.2,1.1 |
|||
c3-3.4,7.3-7,12.8-7c8.3,0,10.1,6.6,10.1,9.5c0,3.1-1.7,8.1-7.7,8.1c-7,0-6.9-7.1-10.8-7.1c-1.8,0-7.1,2.7-7.1,11.5v22.9 |
|||
c0,7.4,1.5,9.3,8.8,9.6v2.9H370.1z" |
|||
/> |
|||
<path |
|||
style={styles.blue} |
|||
d="M218.1,157v-30.4c0-13.3-9.6-17.1-16.2-17.1c-6.6,0-11.9,4.2-14.8,7.2c-1,1-2.6,0.3-2.6-1.1v-4.6h-23.1v2.9 |
|||
c4.8,0.8,6.5,1.7,6.5,7.1v35.6v0.3v1.8c0,2.8-2.3,5.1-5.1,5.1H160c-2.8,0-5.1-2.3-5.1-5.1v-24.9v-22.9h-23.2v2.9 |
|||
c4.6,0.9,6.5,1.7,6.5,7.1v35.6c0,5.8-2,6.1-6.5,7.3v2.9h27.4h3.6h28v-2.9c-5.6-0.6-5.9-4.1-5.9-6.9v-32.4c0-1.1,5.2-7,10.1-7 |
|||
c6.6,0,6.7,6.7,6.7,10.1v29.4c0,4.5-1.7,6.6-5.7,6.9v2.9h28.5V164C218.1,163.6,218.1,159.9,218.1,157" |
|||
/> |
|||
<path |
|||
style={styles.blue} |
|||
d="M475.2,110.9v2.9c3,0.1,6.7,0.1,6.7,4c0,1.8-0.6,3.4-1.3,5.5l-8.3,23.2l-8.7-22.4c-1.1-2.7-2.5-5.8-2.5-7 |
|||
c0-2.9,3.2-3,6.5-3.2v-3H446c-3,0-5,3.1-3.8,5.8l7.2,15.9l14.5,35.7l-2.2,6.5c-0.9,2.7-3.9,9.9-8,9.9c-1.5,0-2-1.5-2-2 |
|||
c0-0.5,0.3-1.1,0.3-2.2c0-2.2-1.3-6.1-6.8-6.1c-7.3,0-8,6.1-8,7.9c0,3.9,3.2,9.5,11.2,9.5c11.4,0,14-6.9,20-23l18.1-48.1 |
|||
c2.3-6,4.4-6.3,7-6.7v-3H475.2z" |
|||
/> |
|||
<path |
|||
style={styles.blue} |
|||
d="M420.9,166.7h-32.3v-2.9c3.9-0.8,6.6-1.2,6.6-7.3V121c0-6-2.9-6.7-6.6-7.1v-3h22.9l0.1,4.4c0,1.6,2,2.4,3.2,1.1 |
|||
c3-3.4,7.3-7,12.8-7c8.3,0,10.1,6.6,10.1,9.5c0,3.1-1.7,8.1-7.7,8.1c-7,0-6.9-7.1-10.8-7.1c-1.8,0-7.1,2.7-7.1,11.5v22.9 |
|||
c0,7.4,1.5,9.3,8.8,9.6v2.9H420.9z" |
|||
/> |
|||
<path |
|||
style={styles.blue} |
|||
d="M487.5,159.5h0.9c1,0,1.4-0.8,1.4-1.8c0-1.2-0.6-1.5-1.4-1.5h-0.9V159.5z M485.2,155.8h3.5 |
|||
c1.2,0,2.4,0.5,2.4,1.8c0,1.1-0.6,1.8-1.6,2.2l1.2,1.6c0.5,0.8,1.2,1.4,1.5,1.7v0.2h-1.3c-0.6,0-1.2-1.4-2.5-3.2h-1v2 |
|||
c0,0.9,0,0.9,1,1v0.3h-3.2v-0.3c1-0.1,1-0.1,1-1v-4.8c0-0.9,0-0.9-1-1V155.8z M488.6,153.4c-3.3,0-5.8,2.9-5.8,6.1s2.6,6.1,5.8,6.1 |
|||
c3.2,0,5.8-2.9,5.8-6.1C494.3,156.3,491.8,153.4,488.6,153.4 M488.6,166.5c-3.9,0-7-3.1-7-7c0-3.9,3-7,7-7c3.9,0,6.9,3.1,6.9,7 |
|||
C495.5,163.4,492.4,166.5,488.6,166.5" |
|||
/> |
|||
<path |
|||
style={styles.blue} |
|||
d="M321.8,132.6c-0.1-3.8-0.1-10.1-1.1-13.7c-0.8-2.7-2.3-5.7-6.6-5.7c-4.5,0-8.6,2.7-8.6,19.4H321.8z |
|||
M337.3,153.3c-7.4,11.3-14.3,15.2-24,15.2c-12.3,0-24.6-8.9-24.6-29.7c0-18,11.1-29.4,25-29.4c21.1,0,22.7,21.1,23,27.7h-30.6 |
|||
c0.3,10.3,3,22.7,14.5,22.7c7.1,0,11.4-5.4,13.7-8.1L337.3,153.3z" |
|||
/> |
|||
<path |
|||
style={styles.blue} |
|||
d="M579,43c-7.2,0-9.7,6.1-18.8,3.3c-0.8-0.3-1.5,0.7-0.8,1.3c5.5,4.6,14.1,11.2,24,10.6 |
|||
C591.3,57.6,590.7,43,579,43" |
|||
/> |
|||
<path |
|||
style={styles.pink} |
|||
d="M588.3,63.8c-0.5,2.5-1.3,5.5-2,7.4c-0.2,0.4-0.7,0.6-1.1,0.4c-18.2-8.8-38.9,11.7-60.7,5.1 |
|||
c-17.7-5.3-18.7-40,3.9-40.5c18.3-0.4,32.8,30.4,59,26.6C587.9,62.7,588.4,63,588.3,63.8" |
|||
/> |
|||
<path |
|||
style={styles.pink} |
|||
d="M533,94.4c9.5,1.2,22.8,8.9,30.8,9.3c0.7,0,0.9,0.9,0.4,1.4c-4.7,3.9-9.5,6.3-13.8,6.3c-6.8,0-15-6.1-22.3-15.2 |
|||
C526.9,94.5,531,94.2,533,94.4" |
|||
/> |
|||
<path |
|||
style={styles.pink} |
|||
d="M525.2,90.4c5.4-3.4,16.5,0.4,27.6,4.9c10.6,4.3,16.1,4.5,20.3,0.3c4.8-4.8,14.5-18.8,7.5-20.6 |
|||
c-16.1-4.1-38.2,15.1-60.6,5.3c-0.7-0.3-1.3,0.3-1,1c0.4,1.1,2.8,6.1,4.9,8.9C524.3,90.6,524.6,90.8,525.2,90.4" |
|||
/> |
|||
<path |
|||
style={styles.pink} |
|||
d="M541.8,15.1c6,0,6.8,8.1,13.1,15.4l0,0c0.5,0.7-0.1,1.3-0.5,1.3C534.4,31,533.4,15.1,541.8,15.1" |
|||
/> |
|||
<path |
|||
style={styles.pink} |
|||
d="M560.4,28.9c-1.1-0.7-2.3-1.8-3.2-3.2c-0.2-0.2-0.2-0.5,0-0.8c1.8-2.9,3.4-6.7,5.8-8.3 |
|||
c3.7-2.8,10.6,0.6,7.8,6.2c-1.1,2.2-3.6,4.5-6.2,6.1C562.8,30,561.2,29.5,560.4,28.9" |
|||
/> |
|||
</> |
|||
) |
|||
|
|||
export default ({ width }: { width: number }) => ( |
|||
<svg width={width} viewBox="0 0 612 205.1"> |
|||
{inner} |
|||
</svg> |
|||
) |
@ -0,0 +1,171 @@ |
|||
// @flow
|
|||
|
|||
import React, { PureComponent } from 'react' |
|||
import { warnings } from '@ledgerhq/live-common/lib/api/socket' |
|||
import { translate } from 'react-i18next' |
|||
import styled from 'styled-components' |
|||
|
|||
import { colors } from 'styles/theme' |
|||
import uniqueId from 'lodash/uniqueId' |
|||
import { openURL } from 'helpers/linking' |
|||
import IconCross from 'icons/Cross' |
|||
import IconExclamationCircle from 'icons/ExclamationCircle' |
|||
import IconChevronRight from 'icons/ChevronRight' |
|||
|
|||
import Box from 'components/base/Box' |
|||
import { SHOW_MOCK_HSMWARNINGS } from '../config/constants' |
|||
import { urls } from '../config/urls' |
|||
|
|||
const CloseIconContainer = styled.div` |
|||
position: absolute; |
|||
top: 0; |
|||
right: 0; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 10px; |
|||
border-bottom-left-radius: 4px; |
|||
` |
|||
|
|||
const CloseIcon = (props: *) => ( |
|||
<CloseIconContainer {...props}> |
|||
<IconCross size={16} color="white" /> |
|||
</CloseIconContainer> |
|||
) |
|||
|
|||
type Props = { |
|||
t: *, |
|||
} |
|||
|
|||
type State = { |
|||
pendingMessages: HSMStatus[], |
|||
} |
|||
|
|||
type HSMStatus = { |
|||
id: string, |
|||
message: string, |
|||
} |
|||
|
|||
class HSMStatusBanner extends PureComponent<Props, State> { |
|||
state = { |
|||
pendingMessages: SHOW_MOCK_HSMWARNINGS |
|||
? [ |
|||
{ |
|||
id: 'mock1', |
|||
message: 'Lorem Ipsum dolor sit amet #1', |
|||
}, |
|||
] |
|||
: [], |
|||
} |
|||
|
|||
componentDidMount() { |
|||
this.warningSub = warnings.subscribe({ |
|||
next: message => { |
|||
this.setState(prevState => ({ |
|||
pendingMessages: [...prevState.pendingMessages, { id: uniqueId(), message }], |
|||
})) |
|||
}, |
|||
}) |
|||
} |
|||
|
|||
componentWillUnmount() { |
|||
if (this.warningSub) { |
|||
this.warningSub.unsubscribe() |
|||
} |
|||
} |
|||
|
|||
warningSub = null |
|||
|
|||
dismiss = dismissedItem => |
|||
this.setState(prevState => ({ |
|||
pendingMessages: prevState.pendingMessages.filter(item => item.id !== dismissedItem.id), |
|||
})) |
|||
|
|||
render() { |
|||
const { t } = this.props |
|||
const { pendingMessages } = this.state |
|||
|
|||
if (!pendingMessages.length) return null |
|||
const item = pendingMessages[0] |
|||
|
|||
return ( |
|||
<Box flow={2} style={styles.container}> |
|||
<BannerItem key={item.id} t={t} item={item} onItemDismiss={this.dismiss} /> |
|||
</Box> |
|||
) |
|||
} |
|||
} |
|||
|
|||
class BannerItem extends PureComponent<{ |
|||
item: HSMStatus, |
|||
onItemDismiss: HSMStatus => void, |
|||
t: *, |
|||
}> { |
|||
onLinkClick = () => openURL(urls.contactSupport) |
|||
dismiss = () => this.props.onItemDismiss(this.props.item) |
|||
|
|||
render() { |
|||
const { item, t } = this.props |
|||
return ( |
|||
<Box relative key={item.id} style={styles.banner}> |
|||
<CloseIcon onClick={this.dismiss} /> |
|||
<Box horizontal flow={2}> |
|||
<IconExclamationCircle size={16} color="white" /> |
|||
<Box shrink ff="Open Sans|SemiBold" style={styles.message}> |
|||
{item.message} |
|||
</Box> |
|||
</Box> |
|||
<BannerItemLink t={t} onClick={this.onLinkClick} /> |
|||
</Box> |
|||
) |
|||
} |
|||
} |
|||
|
|||
const UnderlinedLink = styled.span` |
|||
border-bottom: 1px solid transparent; |
|||
&:hover { |
|||
border-bottom-color: white; |
|||
} |
|||
` |
|||
|
|||
const BannerItemLink = ({ t, onClick }: { t: *, onClick: void => * }) => ( |
|||
<Box |
|||
mt={2} |
|||
ml={4} |
|||
flow={1} |
|||
horizontal |
|||
align="center" |
|||
cursor="pointer" |
|||
onClick={onClick} |
|||
color="white" |
|||
> |
|||
<IconChevronRight size={16} color="white" /> |
|||
<UnderlinedLink>{t('common.learnMore')}</UnderlinedLink> |
|||
</Box> |
|||
) |
|||
|
|||
const styles = { |
|||
container: { |
|||
position: 'fixed', |
|||
left: 32, |
|||
bottom: 32, |
|||
zIndex: 100, |
|||
}, |
|||
banner: { |
|||
background: colors.orange, |
|||
overflow: 'hidden', |
|||
borderRadius: 4, |
|||
fontSize: 13, |
|||
paddingTop: 17, |
|||
padding: 15, |
|||
color: 'white', |
|||
fontWeight: 'bold', |
|||
paddingRight: 30, |
|||
width: 350, |
|||
}, |
|||
message: { |
|||
marginTop: -3, |
|||
}, |
|||
} |
|||
|
|||
export default translate()(HSMStatusBanner) |
@ -0,0 +1,64 @@ |
|||
// @flow
|
|||
|
|||
import React, { PureComponent } from 'react' |
|||
import styled, { css, keyframes } from 'styled-components' |
|||
|
|||
import { colors } from 'styles/theme' |
|||
|
|||
const animIndeterminate = keyframes` |
|||
0% { |
|||
transform: scaleX(0) translate3d(0, 0, 0); |
|||
} |
|||
50% { |
|||
transform: scaleX(1) translate3d(100%, 0, 0); |
|||
} |
|||
100% { |
|||
transform: scaleX(0) translate3d(0, 0, 0); |
|||
} |
|||
` |
|||
|
|||
const Outer = styled.div` |
|||
background-color: ${colors.fog}; |
|||
border-radius: 3px; |
|||
overflow: hidden; |
|||
height: 5px; |
|||
width: ${p => p.width}px; |
|||
position: relative; |
|||
` |
|||
|
|||
const Inner = styled.div` |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background-color: ${colors.wallet}; |
|||
transform-origin: center left; |
|||
|
|||
${p => |
|||
p.progress === 0 |
|||
? css` |
|||
animation: ${animIndeterminate} 2s cubic-bezier(0.61, 0.01, 0.39, 1.03) infinite; |
|||
` |
|||
: css` |
|||
transform: scaleX(${p => p.progress}); |
|||
`};
|
|||
` |
|||
|
|||
type Props = { |
|||
progress: number, |
|||
width: number, |
|||
} |
|||
|
|||
class ProgressBar extends PureComponent<Props> { |
|||
render() { |
|||
const { progress, width } = this.props |
|||
return ( |
|||
<Outer width={width}> |
|||
<Inner progress={progress} /> |
|||
</Outer> |
|||
) |
|||
} |
|||
} |
|||
|
|||
export default ProgressBar |
@ -0,0 +1,16 @@ |
|||
// @flow
|
|||
|
|||
import React from 'react' |
|||
import { storiesOf } from '@storybook/react' |
|||
import { number } from '@storybook/addon-knobs' |
|||
|
|||
import ProgressBar from 'components/ProgressBar' |
|||
|
|||
const stories = storiesOf('Components', module) |
|||
|
|||
stories.add('ProgressBar', () => ( |
|||
<ProgressBar |
|||
progress={number('progress', 0, { min: 0, max: 1, step: 0.05 })} |
|||
width={number('width', 200, { min: 50, max: 500, step: 10 })} |
|||
/> |
|||
)) |
@ -0,0 +1,98 @@ |
|||
// @flow
|
|||
|
|||
import React, { PureComponent } from 'react' |
|||
import styled, { css, keyframes } from 'styled-components' |
|||
|
|||
import { colors } from 'styles/theme' |
|||
|
|||
import Text from 'components/base/Text' |
|||
|
|||
const STROKE_WIDTH = 5 |
|||
|
|||
type Props = { |
|||
progress: number, |
|||
size: number, |
|||
} |
|||
|
|||
const animIndeterminate = keyframes` |
|||
0% { |
|||
} |
|||
50% { |
|||
} |
|||
100% { |
|||
} |
|||
` |
|||
|
|||
const InnerCircle = styled.circle` |
|||
transform-origin: 50% 50%; |
|||
${p => |
|||
p.progress === 0 |
|||
? css` |
|||
animation: ${animIndeterminate} 3s cubic-bezier(0.61, 0.01, 0.39, 1.03) infinite; |
|||
` |
|||
: css` |
|||
transition: stroke-dashoffset 0.35s; |
|||
transform: rotate(-90deg); |
|||
`};
|
|||
` |
|||
|
|||
const Container = styled.div` |
|||
position: relative; |
|||
width: ${p => p.size}px; |
|||
height: ${p => p.size}px; |
|||
` |
|||
|
|||
const TextContainer = styled.div` |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
` |
|||
|
|||
class ProgressCircle extends PureComponent<Props> { |
|||
render() { |
|||
const { size, progress } = this.props |
|||
const radius = size / 2 |
|||
const normalizedRadius = radius - STROKE_WIDTH / 2 |
|||
const circumference = normalizedRadius * 2 * Math.PI |
|||
const strokeDashoffset = circumference - progress * circumference |
|||
|
|||
return ( |
|||
<Container size={size}> |
|||
<TextContainer> |
|||
<Text ff="Rubik|Regular" color="graphite" fontSize={5}> |
|||
{`${Math.round(progress * 100)}%`} |
|||
</Text> |
|||
</TextContainer> |
|||
<svg height={size} width={size}> |
|||
<circle |
|||
stroke={colors.fog} |
|||
fill="transparent" |
|||
strokeWidth={STROKE_WIDTH} |
|||
style={{ strokeDashoffset }} |
|||
r={normalizedRadius} |
|||
cx={radius} |
|||
cy={radius} |
|||
/> |
|||
<InnerCircle |
|||
progress={progress} |
|||
stroke={colors.wallet} |
|||
fill="transparent" |
|||
strokeWidth={STROKE_WIDTH} |
|||
strokeDasharray={`${circumference} ${circumference}`} |
|||
style={{ strokeDashoffset }} |
|||
r={normalizedRadius} |
|||
cx={radius} |
|||
cy={radius} |
|||
/> |
|||
</svg> |
|||
</Container> |
|||
) |
|||
} |
|||
} |
|||
|
|||
export default ProgressCircle |
@ -0,0 +1,16 @@ |
|||
// @flow
|
|||
|
|||
import React from 'react' |
|||
import { storiesOf } from '@storybook/react' |
|||
import { number } from '@storybook/addon-knobs' |
|||
|
|||
import ProgressCircle from 'components/ProgressCircle' |
|||
|
|||
const stories = storiesOf('Components', module) |
|||
|
|||
stories.add('ProgressCircle', () => ( |
|||
<ProgressCircle |
|||
progress={number('progress', 0, { min: 0, max: 1, step: 0.01 })} |
|||
size={number('width', 150, { min: 50, max: 500, step: 10 })} |
|||
/> |
|||
)) |
@ -0,0 +1,102 @@ |
|||
// @flow
|
|||
|
|||
import React, { Fragment, PureComponent } from 'react' |
|||
import { compose } from 'redux' |
|||
import { connect } from 'react-redux' |
|||
import { withRouter } from 'react-router' |
|||
import { translate } from 'react-i18next' |
|||
import { push } from 'react-router-redux' |
|||
|
|||
import type { T } from 'types/common' |
|||
import firmwareRepair from 'commands/firmwareRepair' |
|||
import Button from 'components/base/Button' |
|||
import { RepairModal } from 'components/base/Modal' |
|||
|
|||
type Props = { |
|||
t: T, |
|||
push: string => void, |
|||
} |
|||
|
|||
type State = { |
|||
opened: boolean, |
|||
isLoading: boolean, |
|||
error: ?Error, |
|||
progress: number, |
|||
} |
|||
|
|||
class RepairDeviceButton extends PureComponent<Props, State> { |
|||
state = { |
|||
opened: false, |
|||
isLoading: false, |
|||
error: null, |
|||
progress: 0, |
|||
} |
|||
|
|||
open = () => this.setState({ opened: true, error: null }) |
|||
|
|||
sub: * |
|||
|
|||
close = () => { |
|||
if (this.sub) this.sub.unsubscribe() |
|||
this.setState({ opened: false, isLoading: false, error: null, progress: 0 }) |
|||
} |
|||
|
|||
repair = (version = null) => { |
|||
if (this.state.isLoading) return |
|||
const { push } = this.props |
|||
this.setState({ isLoading: true }) |
|||
this.sub = firmwareRepair.send({ version }).subscribe({ |
|||
next: patch => { |
|||
this.setState(patch) |
|||
}, |
|||
error: error => { |
|||
this.setState({ error, isLoading: false, progress: 0 }) |
|||
}, |
|||
complete: () => { |
|||
this.setState({ opened: false, isLoading: false, progress: 0 }, () => { |
|||
push('/manager') |
|||
}) |
|||
}, |
|||
}) |
|||
} |
|||
|
|||
render() { |
|||
const { t } = this.props |
|||
const { opened, isLoading, error, progress } = this.state |
|||
|
|||
return ( |
|||
<Fragment> |
|||
<Button small primary onClick={this.open} event="RepairDeviceButton"> |
|||
{t('settings.repairDevice.button')} |
|||
</Button> |
|||
|
|||
<RepairModal |
|||
cancellable |
|||
analyticsName="RepairDevice" |
|||
isOpened={opened} |
|||
onClose={this.close} |
|||
onReject={this.close} |
|||
repair={this.repair} |
|||
isLoading={isLoading} |
|||
title={t('settings.repairDevice.title')} |
|||
desc={t('settings.repairDevice.desc')} |
|||
progress={progress} |
|||
error={error} |
|||
/> |
|||
</Fragment> |
|||
) |
|||
} |
|||
} |
|||
|
|||
const mapDispatchToProps = { |
|||
push, |
|||
} |
|||
|
|||
export default compose( |
|||
translate(), |
|||
withRouter, |
|||
connect( |
|||
null, |
|||
mapDispatchToProps, |
|||
), |
|||
)(RepairDeviceButton) |
@ -0,0 +1,94 @@ |
|||
// @flow
|
|||
|
|||
import React, { PureComponent } from 'react' |
|||
import WebSocket from 'ws' |
|||
import IP from 'ip' |
|||
import { createStructuredSelector } from 'reselect' |
|||
import { activeAccountsSelector } from 'reducers/accounts' |
|||
import { exportSettingsSelector } from 'reducers/settings' |
|||
import { encode } from '@ledgerhq/live-common/lib/cross' |
|||
import connect from 'react-redux/es/connect/connect' |
|||
import Button from '../base/Button' |
|||
import QRCode from '../base/QRCode' |
|||
|
|||
type Props = { |
|||
accounts: *, |
|||
settings: *, |
|||
} |
|||
|
|||
type State = { |
|||
active: boolean, |
|||
} |
|||
|
|||
const mapStateToProps = createStructuredSelector({ |
|||
accounts: activeAccountsSelector, |
|||
settings: exportSettingsSelector, |
|||
}) |
|||
|
|||
class SocketExport extends PureComponent<Props, State> { |
|||
state = { |
|||
active: false, |
|||
} |
|||
|
|||
componentWillMount() { |
|||
this.resetServer() |
|||
} |
|||
|
|||
componentDidUpdate() { |
|||
if (!this.state.active) return |
|||
if (!this.server) { |
|||
this.resetServer() |
|||
} |
|||
} |
|||
|
|||
componentWillUnmount() { |
|||
if (this.server) this.server.close() |
|||
} |
|||
|
|||
resetServer = () => { |
|||
this.server = new WebSocket.Server({ port: 1234 }) |
|||
|
|||
const { accounts, settings } = this.props |
|||
|
|||
const data = encode({ |
|||
accounts, |
|||
settings, |
|||
exporterName: 'desktop', |
|||
exporterVersion: __APP_VERSION__, |
|||
}) |
|||
|
|||
// Secret handshake to avoid intruders
|
|||
this.secret = Math.random() |
|||
.toString(36) |
|||
.slice(2) |
|||
|
|||
if (this.server) { |
|||
this.server.on('connection', ws => { |
|||
ws.on('message', message => { |
|||
if (message === this.secret) { |
|||
ws.send(data) |
|||
ws.close() |
|||
this.setState({ active: false }) |
|||
this.server = undefined |
|||
} |
|||
}) |
|||
}) |
|||
} |
|||
} |
|||
|
|||
secret: string |
|||
server: * |
|||
canvas = React.createRef() |
|||
|
|||
render() { |
|||
return this.state.active ? ( |
|||
<QRCode size={50} data={`${this.secret}~${IP.address()}`} /> |
|||
) : ( |
|||
<Button primary small onClick={() => this.setState({ active: true })}> |
|||
{'Generate Code'} |
|||
</Button> |
|||
) |
|||
} |
|||
} |
|||
|
|||
export default connect(mapStateToProps)(SocketExport) |
@ -0,0 +1,234 @@ |
|||
// @flow
|
|||
|
|||
import React, { PureComponent } from 'react' |
|||
import { translate } from 'react-i18next' |
|||
import styled from 'styled-components' |
|||
import { forceRepairChoices } from '@ledgerhq/live-common/lib/hw/firmwareUpdate-repair' |
|||
|
|||
import type { T } from 'types/common' |
|||
|
|||
import { i } from 'helpers/staticPath' |
|||
import TrackPage from 'analytics/TrackPage' |
|||
import Button from 'components/base/Button' |
|||
import Box from 'components/base/Box' |
|||
import Text from 'components/base/Text' |
|||
import Select from 'components/base/Select' |
|||
import ProgressCircle from 'components/ProgressCircle' |
|||
import TranslatedError from 'components/TranslatedError' |
|||
import ExclamationCircleThin from 'icons/ExclamationCircleThin' |
|||
|
|||
import { Modal, ModalContent, ModalBody, ModalTitle, ModalFooter } from './index' |
|||
|
|||
const Container = styled(Box).attrs({ |
|||
alignItems: 'center', |
|||
fontSize: 4, |
|||
color: 'dark', |
|||
})`` |
|||
|
|||
const Bullet = styled.span` |
|||
font-weight: 600; |
|||
color: #142533; |
|||
` |
|||
|
|||
const Separator = styled(Box).attrs({ |
|||
color: 'fog', |
|||
})` |
|||
height: 1px; |
|||
width: 100%; |
|||
background-color: currentColor; |
|||
` |
|||
|
|||
const DisclaimerStep = ({ desc }: { desc?: string }) => ( |
|||
<ModalContent> |
|||
{desc ? ( |
|||
<Box ff="Open Sans" color="smoke" fontSize={4} textAlign="center" mb={2}> |
|||
{desc} |
|||
</Box> |
|||
) : null} |
|||
</ModalContent> |
|||
) |
|||
|
|||
const FlashStep = ({ progress, t }: { progress: number, t: * }) => |
|||
progress === 0 ? ( |
|||
<ModalContent> |
|||
<Box mx={7}> |
|||
<Text ff="Open Sans|Regular" align="center" color="smoke"> |
|||
<Bullet>{'1.'}</Bullet> |
|||
{t('manager.modal.mcuFirst')} |
|||
</Text> |
|||
<img |
|||
src={i('logos/unplugDevice.png')} |
|||
style={{ width: '100%', maxWidth: 368, marginTop: 30 }} |
|||
alt={t('manager.modal.mcuFirst')} |
|||
/> |
|||
</Box> |
|||
<Separator my={6} /> |
|||
<Box mx={7}> |
|||
<Text ff="Open Sans|Regular" align="center" color="smoke"> |
|||
<Bullet>{'2.'}</Bullet> |
|||
{t('manager.modal.mcuSecond')} |
|||
</Text> |
|||
<img |
|||
src={i('logos/bootloaderMode.png')} |
|||
style={{ width: '100%', maxWidth: 368, marginTop: 30 }} |
|||
alt={t('manager.modal.mcuFirst')} |
|||
/> |
|||
</Box> |
|||
</ModalContent> |
|||
) : ( |
|||
<ModalContent> |
|||
<Box mx={7} align="center"> |
|||
<ProgressCircle size={64} progress={progress} /> |
|||
</Box> |
|||
<Box mx={7} mt={3} mb={2} ff="Museo Sans|Regular" color="dark" textAlign="center"> |
|||
{t(`manager.modal.steps.flash`)} |
|||
</Box> |
|||
<Box mx={7} mt={2} mb={2}> |
|||
<Text ff="Open Sans|Regular" align="center" color="graphite" fontSize={4}> |
|||
{t('manager.modal.mcuPin')} |
|||
</Text> |
|||
</Box> |
|||
</ModalContent> |
|||
) |
|||
|
|||
const ErrorStep = ({ error }: { error: Error }) => ( |
|||
<ModalContent> |
|||
<Container> |
|||
<Box color="alertRed"> |
|||
<ExclamationCircleThin size={44} /> |
|||
</Box> |
|||
<Box |
|||
color="dark" |
|||
mt={4} |
|||
fontSize={6} |
|||
ff="Museo Sans|Regular" |
|||
textAlign="center" |
|||
style={{ maxWidth: 350 }} |
|||
> |
|||
<TranslatedError error={error} field="title" /> |
|||
</Box> |
|||
<Box |
|||
color="graphite" |
|||
mt={4} |
|||
fontSize={6} |
|||
ff="Open Sans" |
|||
textAlign="center" |
|||
style={{ maxWidth: 350 }} |
|||
> |
|||
<TranslatedError error={error} field="description" /> |
|||
</Box> |
|||
</Container> |
|||
</ModalContent> |
|||
) |
|||
|
|||
type Props = { |
|||
isOpened: boolean, |
|||
isDanger: boolean, |
|||
title: string, |
|||
subTitle?: string, |
|||
desc: string, |
|||
renderIcon?: Function, |
|||
confirmText?: string, |
|||
cancelText?: string, |
|||
onReject: Function, |
|||
repair: (?string) => *, |
|||
t: T, |
|||
isLoading?: boolean, |
|||
analyticsName: string, |
|||
cancellable?: boolean, |
|||
progress: number, |
|||
error?: Error, |
|||
} |
|||
|
|||
class RepairModal extends PureComponent<Props, *> { |
|||
state = { |
|||
selectedOption: forceRepairChoices[0], |
|||
} |
|||
|
|||
onChange = selectedOption => { |
|||
this.setState({ selectedOption: selectedOption || forceRepairChoices[0] }) |
|||
} |
|||
|
|||
renderOption = option => (option && this.props.t(`settings.repairDevice.${option.label}`)) || null |
|||
|
|||
renderValue = option => |
|||
(option && this.props.t(`settings.repairDevice.${option.data.label}`)) || null |
|||
|
|||
render() { |
|||
const { |
|||
cancellable, |
|||
isOpened, |
|||
title, |
|||
desc, |
|||
confirmText, |
|||
isDanger, |
|||
onReject, |
|||
repair, |
|||
isLoading, |
|||
renderIcon, |
|||
t, |
|||
analyticsName, |
|||
progress, |
|||
error, |
|||
...props |
|||
} = this.props |
|||
const { selectedOption } = this.state |
|||
|
|||
return ( |
|||
<Modal |
|||
isOpened={isOpened} |
|||
preventBackdropClick={isLoading} |
|||
{...props} |
|||
render={({ onClose }) => ( |
|||
<ModalBody onClose={!cancellable && isLoading ? undefined : onClose}> |
|||
<TrackPage category="Modal" name={analyticsName} /> |
|||
<ModalTitle>{title}</ModalTitle> |
|||
{error ? ( |
|||
<ErrorStep error={error} /> |
|||
) : isLoading ? ( |
|||
<FlashStep t={t} progress={progress} /> |
|||
) : ( |
|||
<DisclaimerStep desc={desc} /> |
|||
)} |
|||
|
|||
{!isLoading && !error ? ( |
|||
<Box py={2} px={5}> |
|||
<Select |
|||
isSearchable={false} |
|||
isClearable={false} |
|||
value={selectedOption} |
|||
onChange={this.onChange} |
|||
autoFocus |
|||
options={forceRepairChoices} |
|||
renderOption={this.renderOption} |
|||
renderValue={this.renderValue} |
|||
/> |
|||
</Box> |
|||
) : null} |
|||
|
|||
{!isLoading ? ( |
|||
<ModalFooter horizontal align="center" justify="flex-end" flow={2}> |
|||
{error ? <Button onClick={onReject}>{t(`common.close`)}</Button> : null} |
|||
{error ? null : ( |
|||
<> |
|||
<Button |
|||
onClick={() => repair(selectedOption.value)} |
|||
primary={!isDanger} |
|||
danger={isDanger} |
|||
isLoading={isLoading} |
|||
disabled={isLoading} |
|||
> |
|||
{t('settings.repairDevice.button')} |
|||
</Button> |
|||
</> |
|||
)} |
|||
</ModalFooter> |
|||
) : null} |
|||
</ModalBody> |
|||
)} |
|||
/> |
|||
) |
|||
} |
|||
} |
|||
|
|||
export default translate()(RepairModal) |
@ -1,58 +0,0 @@ |
|||
// @flow
|
|||
|
|||
// TODO we need to start porting all custom errors here.
|
|||
|
|||
import { createCustomErrorClass } from 'helpers/errors' |
|||
|
|||
export const AccountNameRequiredError = createCustomErrorClass('AccountNameRequired') |
|||
export const BtcUnmatchedApp = createCustomErrorClass('BtcUnmatchedApp') |
|||
export const CantOpenDevice = createCustomErrorClass('CantOpenDevice') |
|||
export const DeviceAppVerifyNotSupported = createCustomErrorClass('DeviceAppVerifyNotSupported') |
|||
export const DeviceGenuineSocketEarlyClose = createCustomErrorClass('DeviceGenuineSocketEarlyClose') |
|||
export const DeviceNotGenuineError = createCustomErrorClass('DeviceNotGenuine') |
|||
export const DeviceSocketFail = createCustomErrorClass('DeviceSocketFail') |
|||
export const DeviceSocketNoBulkStatus = createCustomErrorClass('DeviceSocketNoBulkStatus') |
|||
export const DeviceSocketNoHandler = createCustomErrorClass('DeviceSocketNoHandler') |
|||
export const DisconnectedDevice = createCustomErrorClass('DisconnectedDevice') |
|||
export const EnpointConfigError = createCustomErrorClass('EnpointConfig') |
|||
export const FeeEstimationFailed = createCustomErrorClass('FeeEstimationFailed') |
|||
export const HardResetFail = createCustomErrorClass('HardResetFail') |
|||
export const InvalidAddress = createCustomErrorClass('InvalidAddress') |
|||
export const LatestMCUInstalledError = createCustomErrorClass('LatestMCUInstalledError') |
|||
export const LedgerAPIError = createCustomErrorClass('LedgerAPIError') |
|||
export const LedgerAPIErrorWithMessage = createCustomErrorClass('LedgerAPIErrorWithMessage') |
|||
export const LedgerAPINotAvailable = createCustomErrorClass('LedgerAPINotAvailable') |
|||
export const ManagerAppAlreadyInstalledError = createCustomErrorClass('ManagerAppAlreadyInstalled') |
|||
export const ManagerAppRelyOnBTCError = createCustomErrorClass('ManagerAppRelyOnBTC') |
|||
export const ManagerDeviceLockedError = createCustomErrorClass('ManagerDeviceLocked') |
|||
export const ManagerNotEnoughSpaceError = createCustomErrorClass('ManagerNotEnoughSpace') |
|||
export const ManagerUninstallBTCDep = createCustomErrorClass('ManagerUninstallBTCDep') |
|||
export const NetworkDown = createCustomErrorClass('NetworkDown') |
|||
export const NoAddressesFound = createCustomErrorClass('NoAddressesFound') |
|||
export const NotEnoughBalance = createCustomErrorClass('NotEnoughBalance') |
|||
export const NotEnoughBalanceBecauseDestinationNotCreated = createCustomErrorClass( |
|||
'NotEnoughBalanceBecauseDestinationNotCreated', |
|||
) |
|||
export const PasswordsDontMatchError = createCustomErrorClass('PasswordsDontMatch') |
|||
export const PasswordIncorrectError = createCustomErrorClass('PasswordIncorrect') |
|||
export const TimeoutTagged = createCustomErrorClass('TimeoutTagged') |
|||
export const UpdateYourApp = createCustomErrorClass('UpdateYourApp') |
|||
export const UserRefusedAddress = createCustomErrorClass('UserRefusedAddress') |
|||
export const UserRefusedFirmwareUpdate = createCustomErrorClass('UserRefusedFirmwareUpdate') |
|||
export const UserRefusedOnDevice = createCustomErrorClass('UserRefusedOnDevice') // TODO rename because it's just for transaction refusal
|
|||
export const WebsocketConnectionError = createCustomErrorClass('WebsocketConnectionError') |
|||
export const WebsocketConnectionFailed = createCustomErrorClass('WebsocketConnectionFailed') |
|||
export const WrongDeviceForAccount = createCustomErrorClass('WrongDeviceForAccount') |
|||
export const ETHAddressNonEIP = createCustomErrorClass('ETHAddressNonEIP') |
|||
export const CantScanQRCode = createCustomErrorClass('CantScanQRCode') |
|||
export const FeeNotLoaded = createCustomErrorClass('FeeNotLoaded') |
|||
|
|||
// db stuff, no need to translate
|
|||
export const NoDBPathGiven = createCustomErrorClass('NoDBPathGiven') |
|||
export const DBWrongPassword = createCustomErrorClass('DBWrongPassword') |
|||
export const DBNotReset = createCustomErrorClass('DBNotReset') |
|||
|
|||
// auto-update errors
|
|||
export const UpdateIncorrectHash = createCustomErrorClass('UpdateIncorrectHash') |
|||
export const UpdateIncorrectSig = createCustomErrorClass('UpdateIncorrectSig') |
|||
export const UpdateFetchFileFail = createCustomErrorClass('UpdateFetchFileFail') |
@ -1,52 +0,0 @@ |
|||
// @flow
|
|||
import type Transport from '@ledgerhq/hw-transport' |
|||
|
|||
import { createDeviceSocket } from 'helpers/socket' |
|||
|
|||
import type { ApplicationVersion } from 'helpers/types' |
|||
import { WS_INSTALL } from 'helpers/urls' |
|||
|
|||
import { |
|||
ManagerNotEnoughSpaceError, |
|||
ManagerDeviceLockedError, |
|||
ManagerAppAlreadyInstalledError, |
|||
ManagerAppRelyOnBTCError, |
|||
} from 'config/errors' |
|||
|
|||
function remapError(promise) { |
|||
return promise.catch((e: Error) => { |
|||
switch (true) { |
|||
case e.message.endsWith('6982'): |
|||
throw new ManagerDeviceLockedError() |
|||
case e.message.endsWith('6a84') || e.message.endsWith('6a85'): |
|||
throw new ManagerNotEnoughSpaceError() |
|||
case e.message.endsWith('6a80') || e.message.endsWith('6a81'): |
|||
throw new ManagerAppAlreadyInstalledError() |
|||
case e.message.endsWith('6a83'): |
|||
throw new ManagerAppRelyOnBTCError() |
|||
default: |
|||
throw e |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* Install an app on the device |
|||
*/ |
|||
export default async function installApp( |
|||
transport: Transport<*>, |
|||
targetId: string | number, |
|||
{ app }: { app: ApplicationVersion }, |
|||
): Promise<void> { |
|||
const params = { |
|||
targetId, |
|||
perso: app.perso, |
|||
deleteKey: app.delete_key, |
|||
firmware: app.firmware, |
|||
firmwareKey: app.firmware_key, |
|||
hash: app.hash, |
|||
} |
|||
|
|||
const url = WS_INSTALL(params) |
|||
await remapError(createDeviceSocket(transport, url).toPromise()) |
|||
} |
@ -1,30 +0,0 @@ |
|||
// @flow
|
|||
import network from 'api/network' |
|||
import type { DeviceInfo, DeviceVersion, FinalFirmware, ApplicationVersion } from 'helpers/types' |
|||
|
|||
import { APPLICATIONS_BY_DEVICE } from 'helpers/urls' |
|||
import getDeviceVersion from 'helpers/devices/getDeviceVersion' |
|||
import getCurrentFirmware from 'helpers/devices/getCurrentFirmware' |
|||
|
|||
type NetworkResponse = { data: { application_versions: Array<ApplicationVersion> } } |
|||
|
|||
export default async (deviceInfo: DeviceInfo): Promise<Array<ApplicationVersion>> => { |
|||
const deviceData: DeviceVersion = await getDeviceVersion( |
|||
deviceInfo.targetId, |
|||
deviceInfo.providerId, |
|||
) |
|||
const firmwareData: FinalFirmware = await getCurrentFirmware({ |
|||
deviceId: deviceData.id, |
|||
fullVersion: deviceInfo.fullVersion, |
|||
provider: deviceInfo.providerId, |
|||
}) |
|||
const params = { |
|||
provider: deviceInfo.providerId, |
|||
current_se_firmware_final_version: firmwareData.id, |
|||
device_version: deviceData.id, |
|||
} |
|||
const { |
|||
data: { application_versions }, |
|||
}: NetworkResponse = await network({ method: 'POST', url: APPLICATIONS_BY_DEVICE, data: params }) |
|||
return application_versions.length > 0 ? application_versions : [] |
|||
} |
@ -1,10 +0,0 @@ |
|||
// @flow
|
|||
import network from 'api/network' |
|||
|
|||
import { GET_APPLICATIONS } from 'helpers/urls' |
|||
import type { Application } from 'helpers/types' |
|||
|
|||
export default async (): Promise<Array<Application>> => { |
|||
const { data } = await network({ method: 'GET', url: GET_APPLICATIONS }) |
|||
return data.length > 0 ? data : [] |
|||
} |
@ -1,10 +0,0 @@ |
|||
// @flow
|
|||
import network from 'api/network' |
|||
|
|||
import { GET_CATEGORIES } from 'helpers/urls' |
|||
import type { Category } from 'helpers/types' |
|||
|
|||
export default async (): Promise<Array<Category>> => { |
|||
const { data }: { data: Array<Category> } = await network({ method: 'GET', url: GET_CATEGORIES }) |
|||
return data.length > 0 ? data : [] |
|||
} |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue