Ledger types (#1690)

* Do not truncate errors, pretty output

* Introduce helpers for sagas

* Update yarn lock

* Initial types

* Finish types

* cleanup

* Fix imports and filenames to cooperate with internal typings
This commit is contained in:
HenryNguyen5 2018-05-21 16:47:25 -04:00 committed by Daniel Ternyak
parent 7e605c4058
commit c763b2ac98
6 changed files with 526 additions and 0 deletions

View File

@ -0,0 +1,56 @@
declare module '@ledgerhq/hw-app-eth' {
import LedgerTransport from '@ledgerhq/hw-transport';
export default class Eth<T extends LedgerTransport<any>> {
constructor(transport: T);
/**
*
* @description get Ethereum address for a given BIP 32 path.
* @param {string} path a path in BIP 32 format
* @param {boolean} [boolDisplay] enable or not the display
* @param {boolean} [boolChaincode] enable or not the chaincode request
* @returns {Promise<{ publicKey: string; address: string; chainCode?: string }>}
* @memberof Eth
*/
public getAddress(
path: string,
boolDisplay?: boolean,
boolChaincode?: boolean
): Promise<{ publicKey: string; address: string; chainCode?: string }>;
/**
*
* @description signs a raw transaction and returns v,r,s
* @param {string} path
* @param {string} rawTxHex
* @returns {Promise<{s: string, v: string, r: string}>}
* @memberof Eth
*/
public signTransaction(
path: string,
rawTxHex: string
): Promise<{ s: string; v: string; r: string }>;
/**
*
*
* @returns {Promise<{ arbitraryDataEnabled: number; version: string }>}
* @memberof Eth
*/
public getAppConfiguration(): Promise<{ arbitraryDataEnabled: number; version: string }>;
/**
*
* @description sign a message according to eth_sign RPC call
* @param {string} path
* @param {string} messageHex
* @returns {Promise<{v: number, s: string, r: string}>}
* @memberof Eth
*/
public signPersonalMessage(
path: string,
messageHex: string
): Promise<{ v: number; s: string; r: string }>;
}
}

View File

@ -0,0 +1,97 @@
declare module '@ledgerhq/hw-transport-node-hid' {
import LedgerTransport, { Observer, DescriptorEvent, Subscription } from '@ledgerhq/hw-transport';
import { HID, Device } from 'node-hid';
export default class TransportNodeHid extends LedgerTransport<string> {
/**
* @description Creates an instance of TransportNodeHid.
* @example
*
* ```ts
* import TransportNodeHid from "@ledgerhq/hw-transport-node-u2f";
* TransportNodeHid.create().then(transport => ...);
* ```
*
* @param {HID} device
* @param {boolean} [ledgerTransport]
* @param {number} [timeout]
* @param {boolean} [debug]
* @memberof TransportNodeHid
*/
constructor(device: HID, ledgerTransport?: boolean, timeout?: number, debug?: boolean);
/**
*
* @description Check if an HID instance is active
* @static
* @returns {Promise<boolean>}
* @memberof TransportNodeHid
*/
public static isSupported(): Promise<boolean>;
/**
*
* @description Lists all available HID device's paths
* @static
* @returns {Promise<string[]>}
* @memberof TransportNodeHid
*/
public static list<Descriptor = string>(): Promise<Descriptor[]>;
/**
*
* @description Listen all device events for a given Transport.
* The method takes an Observer of DescriptorEvent and returns a Subscription
* according to Observable paradigm https://github.com/tc39/proposal-observable
* a DescriptorEvent is a { descriptor, type } object.
* Type can be "add" or "remove" and descriptor is a value you can pass to open(descriptor).
* Each listen() call will first emit all potential device already connected and then will emit events can come over times,
* for instance if you plug a USB device after listen().
* @static
* @template Descriptor
* @template Device
* @template Err
* @param {Observer<DescriptorEvent<Descriptor, Device>, Err>} observer
* @returns {Subscription}
* @memberof TransportNodeHid
*/
public static listen<Descriptor = string, Device = HID, Err = void>(
observer: Observer<DescriptorEvent<Descriptor, Device>, Err>
): Subscription;
/**
*
* @description Attempt to create a Transport instance with a descriptor.
* @static
* @template Descriptor
* @param {Descriptor} devicePath
* @returns {Promise<TransportNodeHid>}
* @memberof TransportNodeHid
*/
public static open<Descriptor = string>(devicePath: Descriptor): Promise<TransportNodeHid>;
/**
*
* @description Low level api to communicate with the device.
* @param {Buffer} apdu
* @returns {Promise<Buffer>}
* @memberof TransportNodeHid
*/
public exchange(apdu: Buffer): Promise<Buffer>;
/**
*
* @description Does nothing
* @memberof TransportNodeHid
*/
public setScrambleKey(): void;
/**
*
* @description Close the exchange with the device.
* @returns {Promise<void>}
* @memberof TransportNodeHid
*/
public close(): Promise<void>;
}
}

View File

@ -0,0 +1,84 @@
declare module '@ledgerhq/hw-transport-u2f' {
import LedgerTransport, {
Observer,
DescriptorEvent,
Subscription,
TransportError
} from '@ledgerhq/hw-transport';
import { isSupported, sign } from 'u2f-api';
export default class TransportU2F extends LedgerTransport<null> {
public static isSupported: typeof isSupported;
/**
* @description this transport is not discoverable but we are going to guess if it is here with isSupported()
* @static
* @template Descriptor An array with [null] if supported device
* @returns {Descriptor}
* @memberof TransportU2F
*/
public static list<Descriptor = [null] | never[]>(): Descriptor;
/**
*
* @description Listen all device events for a given Transport.
* The method takes an Observer of DescriptorEvent and returns a Subscription
* according to Observable paradigm https://github.com/tc39/proposal-observable
* a DescriptorEvent is a { descriptor, type } object.
* Type can be "add" or "remove" and descriptor is a value you can pass to open(descriptor).
* Each listen() call will first emit all potential device already connected and then will emit events can come over times,
* @static
* @template Descriptor
* @template Device
* @template Err
* @param {Observer<
* DescriptorEvent<Device, Descriptor>,
* ErrParam
* >} observer
* @returns {Subscription}
* @memberof TransportU2F
*/
public static listen<Descriptor = undefined, Device = null, Err = TransportError>(
observer: Observer<DescriptorEvent<Device, Descriptor>, Err>
): Subscription;
/**
*
* @description static function to create a new Transport from a connected
* Ledger device discoverable via U2F (browser support)
*
* @static
* @param {*} _
* @param {number} [_openTimeout]
* @returns {Promise<TransportU2F>}
* @memberof TransportU2F
*/
public static open(_?: any, __?: number): Promise<TransportU2F>;
/**
*
* @description Low level api to communicate with the device.
* @param {Buffer} adpu
* @returns {Promise<Buffer>}
* @memberof TransportU2F
*/
public exchange(adpu: Buffer): Promise<Buffer>;
/**
*
* @description Set the "scramble key" for the next exchange with the device.
* Each App can have a different scramble key and they internally will set it at instantiation.
* @param {string} scrambleKey
* @memberof TransportU2F
*/
public setScrambleKey(scrambleKey: string): void;
/**
*
* @description Close the exchange with the device.
* @returns {Promise<void>}
* @memberof TransportU2F
*/
public close(): Promise<void>;
}
}

View File

@ -0,0 +1,274 @@
declare module '@ledgerhq/hw-transport' {
/**
* @description all possible status codes.
* @see https://github.com/LedgerHQ/blue-app-btc/blob/d8a03d10f77ca5ef8b22a5d062678eef788b824a/include/btchip_apdu_constants.h#L85-L115
* @example
* import { StatusCodes } from "@ledgerhq/hw-transport";
* @export
* @enum {number}
*/
export enum StatusCodes {
PIN_REMAINING_ATTEMPTS = 0x63c0,
INCORRECT_LENGTH = 0x6700,
COMMAND_INCOMPATIBLE_FILE_STRUCTURE = 0x6981,
SECURITY_STATUS_NOT_SATISFIED = 0x6982,
CONDITIONS_OF_USE_NOT_SATISFIED = 0x6985,
INCORRECT_DATA = 0x6a80,
NOT_ENOUGH_MEMORY_SPACE = 0x6a84,
REFERENCED_DATA_NOT_FOUND = 0x6a88,
FILE_ALREADY_EXISTS = 0x6a89,
INCORRECT_P1_P2 = 0x6b00,
INS_NOT_SUPPORTED = 0x6d00,
CLA_NOT_SUPPORTED = 0x6e00,
TECHNICAL_PROBLEM = 0x6f00,
OK = 0x9000,
MEMORY_PROBLEM = 0x9240,
NO_EF_SELECTED = 0x9400,
INVALID_OFFSET = 0x9402,
FILE_NOT_FOUND = 0x9404,
INCONSISTENT_FILE = 0x9408,
ALGORITHM_NOT_SUPPORTED = 0x9484,
INVALID_KCV = 0x9485,
CODE_NOT_INITIALIZED = 0x9802,
ACCESS_CONDITION_NOT_FULFILLED = 0x9804,
CONTRADICTION_SECRET_CODE_STATUS = 0x9808,
CONTRADICTION_INVALIDATION = 0x9810,
CODE_BLOCKED = 0x9840,
MAX_VALUE_REACHED = 0x9850,
GP_AUTH_FAILED = 0x6300,
LICENSING = 0x6f42,
HALTED = 0x6faa
}
export enum AltStatusCodes {
'Incorrect length' = 0x6700,
'Security not satisfied (dongle locked or have invalid access rights)' = 0x6982,
'Condition of use not satisfied (denied by the user?)' = 0x6985,
'Invalid data received' = 0x6a80,
'Invalid parameter received' = 0x6b00,
INTERNAL_ERROR = 'Internal error, please report'
}
export interface Subscription {
unsubscribe: () => void;
}
export interface ITransportError extends Error {
name: 'TransportError';
message: string;
stack?: string;
id: string;
}
/**
* TransportError is used for any generic transport errors.
* e.g. Error thrown when data received by exchanges are incorrect or if exchanged failed to communicate with the device for various reason.
*/
export class TransportError extends Error {
new(message: string, id: string): ITransportError;
}
export interface ITransportStatusError extends Error {
name: 'TransportStatusError';
message: string;
stack?: string;
statusCode: number;
statusText: keyof typeof StatusCodes | 'UNKNOWN_ERROR';
}
/**
* Error thrown when a device returned a non success status.
* the error.statusCode is one of the `StatusCodes` exported by this library.
*/
export class TransportStatusError extends Error {
new(statusCode: number): ITransportStatusError;
}
export interface Observer<Ev, Err> {
next: (event: Ev) => void;
error: (e: Err) => void;
complete: () => void;
}
export interface DescriptorEvent<Descriptor, Device> {
type: 'add' | 'remove';
descriptor: Descriptor;
device?: Device;
}
export type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never
}[keyof T];
export type ExtractPromise<T> = T extends Promise<infer U> ? U : T;
export default abstract class LedgerTransport<Descriptor> {
/**
*
* @description Check if a transport is supported on the user's platform/browser.
* @static
* @returns {Promise<boolean>}
* @memberof LedgerTransport
*/
public static isSupported(): Promise<boolean>;
/**
*
* @description List once all available descriptors. For a better granularity, checkout listen().
* @static
* @template Descriptor
* @returns {Promise<Descriptor[]>}
* @memberof LedgerTransport
*/
public static list<Descriptor>(): Promise<Descriptor[]>;
/**
*
* @description Listen all device events for a given Transport.
* The method takes an Observer of DescriptorEvent and returns a Subscription
* according to Observable paradigm https://github.com/tc39/proposal-observable
* a DescriptorEvent is a { descriptor, type } object.
* Type can be "add" or "remove" and descriptor is a value you can pass to open(descriptor).
* Each listen() call will first emit all potential device already connected and then will emit events can come over times,
* for instance if you plug a USB device after listen() or a bluetooth device become discoverable.
* @static
* @template Descriptor
* @template Device
* @template Err
* @param {Observer<DescriptorEvent<Descriptor, Device>, Err>} observer
* @returns {Subscription}
* @memberof LedgerTransport
*/
public static listen<Descriptor, Device = any, Err = any>(
observer: Observer<DescriptorEvent<Descriptor, Device>, Err>
): Subscription;
/**
*
* @description Attempt to create a Transport instance with potentially a descriptor.
* @static
* @template Descriptor
* @param {Descriptor} descriptor
* @param {number} [timeout]
* @returns {Promise<LedgerTransport<Descriptor>>}
* @memberof LedgerTransport
*/
public static open<Descriptor>(
descriptor: Descriptor,
timeout?: number
): Promise<LedgerTransport<Descriptor>>;
/**
*
* @description create() attempts open the first descriptor available or throw if:
* - there is no descriptor
* - if either timeout is reached
*
* This is a light alternative to using listen() and open() that you may need for any advanced usecases
* @static
* @template Descriptor
* @param {number} [openTimeout]
* @param {number} [listenTimeout]
* @returns {Promise<LedgerTransport<Descriptor>>}
* @memberof LedgerTransport
*/
public static create<Descriptor>(
openTimeout?: number,
listenTimeout?: number
): Promise<LedgerTransport<Descriptor>>;
/**
*
* @description Low level api to communicate with the device.
* This method is for implementations to implement but should not be directly called.
* Instead, the recommended way is to use send() method
* @param {Buffer} apdu
* @returns {Promise<Buffer>}
* @memberof LedgerTransport
*/
public abstract exchange(apdu: Buffer): Promise<Buffer>;
/**
*
* @description Set the "scramble key" for the next exchange with the device.
* Each App can have a different scramble key and they internally will set it at instantiation.
* @param {string} scrambleKey
* @memberof LedgerTransport
*/
public setScrambleKey(scrambleKey: string): void;
/**
*
* @description Close the exchange with the device.
* @returns {Promise<void>}
* @memberof LedgerTransport
*/
public close(): Promise<void>;
/**
*
* @description Listen to an event on an instance of transport.
* Transport implementation can have specific events. Here are the common events:
* - "disconnect" : triggered if Transport is disconnected
* @param {string} eventName
* @param {Listener} cb
* @memberof LedgerTransport
*/
public on(eventName: string | 'listen', cb: (...args: any[]) => any): void;
/**
*
* @description Stop listening to an event on an instance of transport.
* @param {string} eventName
* @param {Listener} cb
* @memberof LedgerTransport
*/
public off(eventName: string, cb: (...args: any[]) => any): void;
/**
*
* @description Toggle logs of binary exchange
* @param {boolean} debug
* @memberof LedgerTransport
*/
public setDebugMode(debug: boolean): void;
/**
* @description Set a timeout (in milliseconds) for the exchange call.
* Only some transport might implement it. (e.g. U2F)
* @param {number} exchangeTimeout
* @memberof LedgerTransport
*/
public setExchangeTimeout?(exchangeTimeout: number): void;
/**
* @description Used to decorate all callable public methods of an app so that they
* are mutually exclusive. Scramble key is application specific, e.g hw-app-eth will set
* its own scramblekey
* @param self
* @param methods
* @param scrambleKey
*/
public decorateAppAPIMethods<T>(
self: T,
methods: FunctionPropertyNames<T>[],
scrambleKey: string
): void;
/**
* @description Decorates a function so that it uses a global mutex, if an
* exchange is already in process, then calling the function will throw an
* error about being locked
* @param methodName
* @param functionToDecorate
* @param thisContext
* @param scrambleKey
*/
public decorateAppAPIMethod<T, FArgs = any, FRet = any>(
methodName: FunctionPropertyNames<T>,
functionToDecorate: (...args: FArgs[]) => FRet,
thisContext: T,
scrambleKey: string
): (...args: FArgs[]) => Promise<ExtractPromise<FRet>>; // make sure we dont wrap promises twice
}
}

View File

@ -64,10 +64,12 @@
"@types/classnames": "2.2.3",
"@types/enzyme": "3.1.8",
"@types/enzyme-adapter-react-16": "1.0.1",
"@types/events": "1.2.0",
"@types/history": "4.6.2",
"@types/jest": "22.2.3",
"@types/lodash": "4.14.107",
"@types/moment-timezone": "0.5.4",
"@types/node-hid": "0.7.0",
"@types/qrcode": "0.8.0",
"@types/qrcode.react": "0.6.3",
"@types/query-string": "5.1.0",
@ -131,6 +133,7 @@
"tslint-react": "3.5.1",
"types-rlp": "0.0.1",
"typescript": "2.8.1",
"u2f-api": "1.0.6",
"uglifyjs-webpack-plugin": "1.2.4",
"url-search-params-polyfill": "3.0.0",
"webapp-webpack-plugin": "2.0.1",

View File

@ -88,6 +88,10 @@
"@types/cheerio" "*"
"@types/react" "*"
"@types/events@1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86"
"@types/history@*", "@types/history@4.6.2":
version "4.6.2"
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.6.2.tgz#12cfaba693ba20f114ed5765467ff25fdf67ddb0"
@ -106,6 +110,10 @@
dependencies:
moment ">=2.14.0"
"@types/node-hid@0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@types/node-hid/-/node-hid-0.7.0.tgz#f696f39c528059116236e41df90c8fcba077d711"
"@types/node@*", "@types/node@^9.6.2":
version "9.6.5"
resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.5.tgz#ee700810fdf49ac1c399fc5980b7559b3e5a381d"
@ -11159,6 +11167,10 @@ u2f-api@0.2.7:
version "0.2.7"
resolved "https://registry.yarnpkg.com/u2f-api/-/u2f-api-0.2.7.tgz#17bf196b242f6bf72353d9858e6a7566cc192720"
u2f-api@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/u2f-api/-/u2f-api-1.0.6.tgz#fdde2a0788fcf7d8d273aa8688b217625961f866"
ua-parser-js@^0.7.9:
version "0.7.17"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac"