From 1713e3fa29d86f1a641d4d7114b3afb9b1fc1302 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Thu, 18 Jan 2018 00:16:47 -0500 Subject: [PATCH] Update trezor-connect.js to v4 (#856) * Update trezor connect. * Re-ignore trezor connect, fix typings. --- .../WalletDecrypt/components/Trezor.tsx | 6 +- common/libs/wallet/deterministic/trezor.ts | 20 +- common/vendor/trezor-connect.js | 1865 ++++++++--------- 3 files changed, 923 insertions(+), 968 deletions(-) diff --git a/common/components/WalletDecrypt/components/Trezor.tsx b/common/components/WalletDecrypt/components/Trezor.tsx index 1344711a..236acef9 100644 --- a/common/components/WalletDecrypt/components/Trezor.tsx +++ b/common/components/WalletDecrypt/components/Trezor.tsx @@ -1,5 +1,5 @@ import DPATHS from 'config/dpaths'; -import { TrezorWallet } from 'libs/wallet'; +import { TrezorWallet, TREZOR_MINIMUM_FIRMWARE } from 'libs/wallet'; import React, { Component } from 'react'; import translate, { translateRaw } from 'translations'; import TrezorConnect from 'vendor/trezor-connect'; @@ -97,7 +97,7 @@ export class TrezorDecrypt extends Component { error: null }); - TrezorConnect.getXPubKey( + (TrezorConnect as any).getXPubKey( dPath, res => { if (res.success) { @@ -114,7 +114,7 @@ export class TrezorDecrypt extends Component { }); } }, - '1.5.2' + TREZOR_MINIMUM_FIRMWARE ); }; diff --git a/common/libs/wallet/deterministic/trezor.ts b/common/libs/wallet/deterministic/trezor.ts index aa6960e1..dd08c16b 100644 --- a/common/libs/wallet/deterministic/trezor.ts +++ b/common/libs/wallet/deterministic/trezor.ts @@ -10,6 +10,8 @@ import mapValues from 'lodash/mapValues'; import { IFullWallet } from '../IWallet'; import { translateRaw } from 'translations'; +export const TREZOR_MINIMUM_FIRMWARE = '1.5.2'; + export class TrezorWallet extends DeterministicWallet implements IFullWallet { public signRawTransaction(tx: EthTx): Promise { return new Promise((resolve, reject) => { @@ -17,7 +19,7 @@ export class TrezorWallet extends DeterministicWallet implements IFullWallet { // stripHexPrefixAndLower identical to ethFuncs.getNakedAddress const cleanedTx = mapValues(mapValues(strTx, stripHexPrefixAndLower), padLeftEven); - TrezorConnect.ethereumSignTx( + (TrezorConnect as any).ethereumSignTx( // Args this.getPath(), cleanedTx.nonce, @@ -51,7 +53,6 @@ export class TrezorWallet extends DeterministicWallet implements IFullWallet { public signMessage = () => Promise.reject(new Error('Signing via Trezor not yet supported.')); - // trezor-connect.js doesn't provide the promise return type public displayAddress = (dPath?: string, index?: number): Promise => { if (!dPath) { dPath = this.dPath; @@ -59,7 +60,20 @@ export class TrezorWallet extends DeterministicWallet implements IFullWallet { if (!index) { index = this.index; } - return TrezorConnect.ethereumGetAddress(dPath + '/' + index); + + return new Promise((resolve, reject) => { + (TrezorConnect as any).ethereumGetAddress( + dPath + '/' + index, + res => { + if (res.error) { + reject(res.error); + } else { + resolve(res); + } + }, + TREZOR_MINIMUM_FIRMWARE + ); + }); }; public getWalletType(): string { diff --git a/common/vendor/trezor-connect.js b/common/vendor/trezor-connect.js index 42d65378..d1f9c51f 100644 --- a/common/vendor/trezor-connect.js +++ b/common/vendor/trezor-connect.js @@ -1,30 +1,31 @@ -/* prettier-ignore */ /* eslint-ignore */ +/* prettier-ignore */ /** * (C) 2017 SatoshiLabs * * GPLv3 */ -var VERSION = 3; +var VERSION = 4; if (!Array.isArray) { - Array.isArray = function(arg) { - return Object.prototype.toString.call(arg) === '[object Array]'; - }; + Array.isArray = function(arg) { + return Object.prototype.toString.call(arg) === '[object Array]'; + }; } var HD_HARDENED = 0x80000000; // react sometimes adds some other parameters that should not be there function _fwStrFix(obj, fw) { - if (typeof fw === 'string') { - obj.requiredFirmware = fw; - } - return obj; + if (typeof fw === 'string') { + obj.requiredFirmware = fw; + } + return obj; } -('use strict'); + +'use strict'; var chrome = window.chrome; var IS_CHROME_APP = chrome && chrome.app && chrome.app.window; @@ -37,687 +38,666 @@ var ERR_CHROME_NOT_CONNECTED = 'Internal Chrome popup is not responding.'; var DISABLE_LOGIN_BUTTONS = window.TREZOR_DISABLE_LOGIN_BUTTONS || false; var CHROME_URL = window.TREZOR_CHROME_URL || './chrome/wrapper.html'; -var POPUP_URL = - window.TREZOR_POPUP_URL || 'https://connect.trezor.io/' + VERSION + '/popup/popup.html'; -var POPUP_PATH = window.TREZOR_POPUP_PATH || 'https://connect.trezor.io/' + VERSION; var POPUP_ORIGIN = window.TREZOR_POPUP_ORIGIN || 'https://connect.trezor.io'; - -var INSIGHT_URLS = window.TREZOR_INSIGHT_URLS || [ - 'https://btc-bitcore1.trezor.io/api/', - 'https://btc-bitcore3.trezor.io/api/' -]; +var POPUP_PATH = window.TREZOR_POPUP_PATH || POPUP_ORIGIN + '/' + VERSION; +if (window.location.hostname === 'localhost' && !window.TREZOR_POPUP_ORIGIN) { + // development settings + //POPUP_ORIGIN = window.location.origin; + //POPUP_PATH = POPUP_ORIGIN; +} +var POPUP_URL = window.TREZOR_POPUP_URL || POPUP_PATH + '/popup/popup.html'; var POPUP_INIT_TIMEOUT = 15000; /** - * Public API. - */ + * Public API. + */ +function TrezorConnect() { -class TrezorConnect { - constructor() { - this.manager = new PopupManager(); - this.LOGIN_CSS = ''; - this.LOGIN_ONCLICK = - 'TrezorConnect.requestLogin(' + - "'@hosticon@','@challenge_hidden@','@challenge_visual@','@callback@'" + - ')'; - this.LOGIN_HTML = - '
' + - ' ' + - ' ' + - ' @text@' + - ' ' + - ' ' + - ' What is TREZOR?' + - ' ' + - '
'; - } + var manager = new PopupManager(); - /** - * Open the popup for further communication. All API functions open the - * popup automatically, but if you need to generate some parameters - * asynchronously, use `open` first to avoid popup blockers. - * @param {function(?Error)} callback - */ - open(callback) { - var onchannel = function(result) { - if (result instanceof Error) { - callback(result); - } else { - callback(); - } - }; - this.manager.waitForChannel(onchannel); - } + /** + * Popup errors. + */ + this.ERR_TIMED_OUT = ERR_TIMED_OUT; + this.ERR_WINDOW_CLOSED = ERR_WINDOW_CLOSED; + this.ERR_WINDOW_BLOCKED = ERR_WINDOW_BLOCKED; + this.ERR_ALREADY_WAITING = ERR_ALREADY_WAITING; + this.ERR_CHROME_NOT_CONNECTED = ERR_CHROME_NOT_CONNECTED; - /** - * Close the opened popup, if any. - */ - close() { - this.manager.close(); - } - - /** - * Enable or disable closing the opened popup after a successful call. - * @param {boolean} value - */ - closeAfterSuccess(value) { - this.manager.closeAfterSuccess = value; - } - - /** - * Enable or disable closing the opened popup after a failed call. - * @param {boolean} value - */ - closeAfterFailure(value) { - this.manager.closeAfterFailure = value; - } - - /** - * Set bitcore server - * @param {string|Array} value - */ - setBitcoreURLS(value) { - if (typeof value === 'string') { - this.manager.bitcoreURLS = [value]; - } else if (value instanceof Array) { - this.manager.bitcoreURLS = value; - } - } - - /** - * Set max. limit for account discovery - * @param {number} value - */ - setAccountDiscoveryLimit(value) { - if (!isNaN(value)) this.manager.accountDiscoveryLimit = value; - } - - /** - * Set max. gap for account discovery - * @param {number} value - */ - setAccountDiscoveryGapLength(value) { - if (!isNaN(value)) this.manager.accountDiscoveryGapLength = value; - } - - /** - * Set discovery BIP44 coin type - * @param {number} value - */ - setAccountDiscoveryBip44CoinType(value) { - if (!isNaN(value)) this.manager.accountDiscoveryBip44CoinType = value; - } - - /** - * @typedef XPubKeyResult - * @param {boolean} success - * @param {?string} error - * @param {?string} xpubkey serialized extended public key - * @param {?string} path BIP32 serializd path of the key - */ - - /** - * Load BIP32 extended public key by path. - * - * Path can be specified either in the string form ("m/44'/1/0") or as - * raw integer array. In case you omit the path, user is asked to select - * a BIP32 account to export, and the result contains m/44'/0'/x' node - * of the account. - * - * @param {?(string|array)} path - * @param {function(XPubKeyResult)} callback - * @param {?(string|array)} requiredFirmware - */ - getXPubKey(path, callback, requiredFirmware) { - if (typeof path === 'string') { - path = parseHDPath(path); - } - this.manager.sendWithChannel( - _fwStrFix( - { - type: 'xpubkey', - path: path - }, - requiredFirmware - ), - callback - ); - } - - getFreshAddress(callback, requiredFirmware) { - var wrapperCallback = function(result) { - if (result.success) { - callback({ success: true, address: result.freshAddress }); - } else { - callback(result); - } - }; - - this.manager.sendWithChannel( - _fwStrFix( - { - type: 'accountinfo' - }, - requiredFirmware - ), - wrapperCallback - ); - } - - getAccountInfo(input, callback, requiredFirmware) { - try { - var description = parseAccountInfoInput(input); - this.manager.sendWithChannel( - _fwStrFix( - { - type: 'accountinfo', - description: description - }, - requiredFirmware - ), - callback - ); - } catch (e) { - callback({ success: false, error: e }); - } - } - - getAllAccountsInfo(callback, requiredFirmware) { - try { - this.manager.sendWithChannel( - _fwStrFix( - { - type: 'allaccountsinfo', - description: 'all' - }, - requiredFirmware - ), - callback - ); - } catch (e) { - callback({ success: false, error: e }); - } - } - - getBalance(callback, requiredFirmware) { - this.manager.sendWithChannel( - _fwStrFix( - { - type: 'accountinfo' - }, - requiredFirmware - ), - callback - ); - } - - /** - * @typedef SignTxResult - * @param {boolean} success - * @param {?string} error - * @param {?string} serialized_tx serialized tx, in hex, including signatures - * @param {?array} signatures array of input signatures, in hex - */ - - /** - * Sign a transaction in the device and return both serialized - * transaction and the signatures. - * - * @param {array} inputs - * @param {array} outputs - * @param {function(SignTxResult)} callback - * @param {?(string|array)} requiredFirmware - * - * @see https://github.com/trezor/trezor-common/blob/master/protob/types.proto - */ - signTx(inputs, outputs, callback, requiredFirmware, coin) { - this.manager.sendWithChannel( - _fwStrFix( - { - type: 'signtx', - inputs: inputs, - outputs: outputs, - coin: coin - }, - requiredFirmware - ), - callback - ); - } - - // new implementation with ethereum at beginnig - ethereumSignTx() { - this.signEthereumTx.apply(this, arguments); - } - - // old fallback - signEthereumTx( - address_n, - nonce, - gas_price, - gas_limit, - to, - value, - data, - chain_id, - callback, - requiredFirmware - ) { - if (requiredFirmware == null) { - requiredFirmware = '1.4.0'; // first firmware that supports ethereum - } - if (typeof address_n === 'string') { - address_n = parseHDPath(address_n); - } - this.manager.sendWithChannel( - _fwStrFix( - { - type: 'signethtx', - address_n: address_n, - nonce: nonce, - gas_price: gas_price, - gas_limit: gas_limit, - to: to, - value: value, - data: data, - chain_id: chain_id - }, - requiredFirmware - ), - callback - ); - } - - /** - * @typedef TxRecipient - * @param {number} amount the amount to send, in satoshis - * @param {string} address the address of the recipient - */ - - /** - * Compose a transaction by doing BIP-0044 discovery, letting the user - * select an account, and picking UTXO by internal preferences. - * Transaction is then signed and returned in the same format as - * `signTx`. Only supports BIP-0044 accounts (single-signature). - * - * @param {array} recipients - * @param {function(SignTxResult)} callback - * @param {?(string|array)} requiredFirmware - */ - composeAndSignTx(recipients, callback, requiredFirmware) { - this.manager.sendWithChannel( - _fwStrFix( - { - type: 'composetx', - recipients: recipients - }, - requiredFirmware - ), - callback - ); - } - - /** - * @typedef RequestLoginResult - * @param {boolean} success - * @param {?string} error - * @param {?string} public_key public key used for signing, in hex - * @param {?string} signature signature, in hex - */ - - /** - * Sign a login challenge for active origin. - * - * @param {?string} hosticon - * @param {string} challenge_hidden - * @param {string} challenge_visual - * @param {string|function(RequestLoginResult)} callback - * @param {?(string|array)} requiredFirmware - * - * @see https://github.com/trezor/trezor-common/blob/master/protob/messages.proto - */ - requestLogin(hosticon, challenge_hidden, challenge_visual, callback, requiredFirmware) { - if (typeof callback === 'string') { - // special case for a login through button. - // `callback` is name of global var - callback = window[callback]; - } - if (!callback) { - throw new TypeError('TrezorConnect: login callback not found'); - } - this.manager.sendWithChannel( - _fwStrFix( - { - type: 'login', - icon: hosticon, - challenge_hidden: challenge_hidden, - challenge_visual: challenge_visual - }, - requiredFirmware - ), - callback - ); - } - - /** - * @typedef SignMessageResult - * @param {boolean} success - * @param {?string} error - * @param {?string} address address (in base58check) - * @param {?string} signature signature, in base64 - */ - - /** - * Sign a message - * - * @param {string|array} path - * @param {string} message to sign (ascii) - * @param {string|function(SignMessageResult)} callback - * @param {?string} opt_coin - (optional) name of coin (default Bitcoin) - * @param {?(string|array)} requiredFirmware - * - */ - signMessage(path, message, callback, opt_coin, requiredFirmware) { - if (typeof path === 'string') { - path = parseHDPath(path); - } - if (!opt_coin) { - opt_coin = 'Bitcoin'; - } - if (!callback) { - throw new TypeError('TrezorConnect: callback not found'); - } - this.manager.sendWithChannel( - _fwStrFix( - { - type: 'signmsg', - path: path, - message: message, - coin: { coin_name: opt_coin } - }, - requiredFirmware - ), - callback - ); - } - - /** - * Sign an Ethereum message - * - * @param {string|array} path - * @param {string} message to sign (ascii) - * @param {string|function(SignMessageResult)} callback - * @param {?(string|array)} requiredFirmware - * - */ - ethereumSignMessage(path, message, callback, requiredFirmware) { - if (typeof path === 'string') { - path = parseHDPath(path); - } - if (!callback) { - throw new TypeError('TrezorConnect: callback not found'); - } - this.manager.sendWithChannel( - _fwStrFix( - { - type: 'signethmsg', - path: path, - message: message - }, - requiredFirmware - ), - callback - ); - } - - /** - * Verify message - * - * @param {string} address - * @param {string} signature (base64) - * @param {string} message (string) - * @param {string|function()} callback - * @param {?string} opt_coin - (optional) name of coin (default Bitcoin) - * @param {?(string|array)} requiredFirmware - * - */ - verifyMessage(address, signature, message, callback, opt_coin, requiredFirmware) { - if (!opt_coin) { - opt_coin = 'Bitcoin'; - } - if (!callback) { - throw new TypeError('TrezorConnect: callback not found'); - } - this.manager.sendWithChannel( - _fwStrFix( - { - type: 'verifymsg', - address: address, - signature: signature, - message: message, - coin: { coin_name: opt_coin } - }, - requiredFirmware - ), - callback - ); - } - - /** - * Verify ethereum message - * - * @param {string} address - * @param {string} signature (base64) - * @param {string} message (string) - * @param {string|function()} callback - * @param {?(string|array)} requiredFirmware - * - */ - ethereumVerifyMessage(address, signature, message, callback, requiredFirmware) { - if (!callback) { - throw new TypeError('TrezorConnect: callback not found'); - } - this.manager.sendWithChannel( - _fwStrFix( - { - type: 'verifyethmsg', - address: address, - signature: signature, - message: message - }, - requiredFirmware - ), - callback - ); - } - - /** - * Symmetric key-value encryption - * - * @param {string|array} path - * @param {string} key to show on device display - * @param {string} value hexadecimal value, length a multiple of 16 bytes - * @param {boolean} encrypt / decrypt direction - * @param {boolean} ask_on_encrypt (should user confirm on encrypt?) - * @param {boolean} ask_on_decrypt (should user confirm on decrypt?) - * @param {string|function()} callback - * @param {?(string|array)} requiredFirmware - * - */ - cipherKeyValue( - path, - key, - value, - encrypt, - ask_on_encrypt, - ask_on_decrypt, - callback, - requiredFirmware - ) { - if (typeof path === 'string') { - path = parseHDPath(path); - } - if (typeof value !== 'string') { - throw new TypeError('TrezorConnect: Value must be a string'); - } - if (!/^[0-9A-Fa-f]*$/.test(value)) { - throw new TypeError('TrezorConnect: Value must be hexadecimal'); - } - if (value.length % 32 !== 0) { - // 1 byte == 2 hex strings - throw new TypeError('TrezorConnect: Value length must be multiple of 16 bytes'); - } - if (!callback) { - throw new TypeError('TrezorConnect: callback not found'); - } - this.manager.sendWithChannel( - _fwStrFix( - { - type: 'cipherkeyvalue', - path: path, - key: key, - value: value, - encrypt: !!encrypt, - ask_on_encrypt: !!ask_on_encrypt, - ask_on_decrypt: !!ask_on_decrypt - }, - requiredFirmware - ), - callback - ); - } - - pushTransaction(rawTx, callback) { - if (!/^[0-9A-Fa-f]*$/.test(rawTx)) { - throw new TypeError('TrezorConnect: Transaction must be hexadecimal'); - } - if (!callback) { - throw new TypeError('TrezorConnect: callback not found'); - } - - var tryUrl = function(i) { - var insight_url = INSIGHT_URLS[i]; - var xhr = new XMLHttpRequest(); - var method = 'POST'; - var url = insight_url + '/tx/send'; - var data = { - rawtx: rawTx - }; - - xhr.open(method, url, true); - xhr.setRequestHeader('Content-Type', 'application/json'); - xhr.onreadystatechange = function() { - if (xhr.readyState === XMLHttpRequest.DONE) { - if (xhr.status === 200) { - var txid = JSON.parse(xhr.responseText).txid; - callback({ success: true, txid: txid }); - } else { - if (i === INSIGHT_URLS.length - 1) { - callback({ error: new Error(xhr.responseText) }); + /** + * Open the popup for further communication. All API functions open the + * popup automatically, but if you need to generate some parameters + * asynchronously, use `open` first to avoid popup blockers. + * @param {function(?Error)} callback + */ + this.open = function (callback) { + var onchannel = function (result) { + if (result instanceof Error) { + callback(result); } else { - tryUrl(i + 1); + callback(); } - } - } - }; - xhr.send(JSON.stringify(data)); + }; + manager.waitForChannel(onchannel); }; - tryUrl(0); - } + /** + * Close the opened popup, if any. + */ + this.close = function () { manager.close(); }; - /** - * Display address on device - * - * @param {array} address - * @param {string} coin - * @param {boolean} segwit - * @param {?(string|array)} requiredFirmware - * - */ - getAddress(address, coin, segwit, callback, requiredFirmware) { - if (typeof address === 'string') { - address = parseHDPath(address); - } + /** + * Enable or disable closing the opened popup after a successful call. + * @param {boolean} value + */ + this.closeAfterSuccess = function (value) { manager.closeAfterSuccess = value; }; - this.manager.sendWithChannel( - _fwStrFix( - { - type: 'getaddress', - address_n: address, - coin: coin, - segwit: segwit - }, - requiredFirmware - ), - callback - ); - } + /** + * Enable or disable closing the opened popup after a failed call. + * @param {boolean} value + */ + this.closeAfterFailure = function (value) { manager.closeAfterFailure = value; }; - /** - * Display ethereum address on device - * - * @param {string|array} address - * @param {string|array} requiredFirmware - * @param {function()} callback - * - */ - ethereumGetAddress(address, requiredFirmware = undefined) { - if (typeof address === 'string') { - address = parseHDPath(address); - } - return new Promise((resolve, reject) => { - this.manager.sendWithChannel( - _fwStrFix( - { - type: 'ethgetaddress', - address_n: address - }, - requiredFirmware - ), - response => { - if (response.error) { - reject(response.error.message); - } else { - resolve(response); - } + /** + * Set bitcore server + * @param {string|Array} value + */ + this.setBitcoreURLS = function(value) { + if (typeof value === 'string') { + manager.bitcoreURLS = [ value ]; + }else if (value instanceof Array) { + manager.bitcoreURLS = value; } - ); - }); - } - - /** - * Find elements and replace them with login buttons. - * It's not required to use these special elements, feel free to call - * `TrezorConnect.requestLogin` directly. - */ - renderLoginButtons() { - var elements = document.getElementsByTagName('trezor:login'); - - for (var i = 0; i < elements.length; i++) { - var e = elements[i]; - var text = e.getAttribute('text') || 'Sign in with TREZOR'; - var callback = e.getAttribute('callback') || ''; - var hosticon = e.getAttribute('icon') || ''; - var challenge_hidden = e.getAttribute('challenge_hidden') || ''; - var challenge_visual = e.getAttribute('challenge_visual') || ''; - - // it's not valid to put markup into attributes, so let users - // supply a raw text and make TREZOR bold - text = text.replace('TREZOR', 'TREZOR'); - e.outerHTML = (this.LOGIN_CSS + this.LOGIN_HTML) - .replace('@text@', text) - .replace('@callback@', callback) - .replace('@hosticon@', hosticon) - .replace('@challenge_hidden@', challenge_hidden) - .replace('@challenge_visual@', challenge_visual) - .replace('@connect_path@', POPUP_PATH); } - } + + /** + * Set currency. Human readable coin name + * @param {string|Array} value + */ + this.setCurrency = function(value) { + if (typeof value === 'string') { + manager.currency = value; + } + } + + /** + * Set currency units (mBTC, BTC) + * @param {string|Array} value + */ + this.setCurrencyUnits = function(value) { + if (typeof value === 'string') { + manager.currencyUnits = value; + } + } + + /** + * Set coin info json url + * @param {string|Array} value + */ + this.setCoinInfoURL = function(value) { + if (typeof value === 'string') { + manager.coinInfoURL = value; + } + } + + /** + * Set max. limit for account discovery + * @param {number} value + */ + this.setAccountDiscoveryLimit = function(value) { + if(!isNaN(value)) + manager.accountDiscoveryLimit = value; + } + + /** + * Set max. gap for account discovery + * @param {number} value + */ + this.setAccountDiscoveryGapLength = function(value) { + if(!isNaN(value)) + manager.accountDiscoveryGapLength = value; + } + + /** + * Set discovery BIP44 coin type + * @param {number} value + */ + this.setAccountDiscoveryBip44CoinType = function(value) { + if(!isNaN(value)) + manager.accountDiscoveryBip44CoinType = value; + } + + /** + * @typedef XPubKeyResult + * @param {boolean} success + * @param {?string} error + * @param {?string} xpubkey serialized extended public key + * @param {?string} path BIP32 serializd path of the key + */ + + /** + * Load BIP32 extended public key by path. + * + * Path can be specified either in the string form ("m/44'/1/0") or as + * raw integer array. In case you omit the path, user is asked to select + * a BIP32 account to export, and the result contains m/44'/0'/x' node + * of the account. + * + * @param {?(string|array)} path + * @param {function(XPubKeyResult)} callback + * @param {?(string|array)} requiredFirmware + */ + this.getXPubKey = function (path, callback, requiredFirmware) { + if (typeof path === 'string') { + path = parseHDPath(path); + } + manager.sendWithChannel(_fwStrFix({ + type: 'xpubkey', + path: path + }, requiredFirmware), callback); + }; + + this.getFreshAddress = function (callback, requiredFirmware) { + var wrapperCallback = function (result) { + if (result.success) { + callback({success: true, address: result.freshAddress}); + } else { + callback(result); + } + } + + manager.sendWithChannel(_fwStrFix({ + type: 'accountinfo' + }, requiredFirmware), wrapperCallback); + } + + this.getAccountInfo = function (input, callback, requiredFirmware) { + try { + manager.sendWithChannel(_fwStrFix({ + type: 'accountinfo', + description: input + }, requiredFirmware), callback); + } catch(e) { + callback({success: false, error: e}); + } + } + + this.getAllAccountsInfo = function(callback, requiredFirmware){ + try { + manager.sendWithChannel(_fwStrFix({ + type: 'allaccountsinfo', + description: 'all' + }, requiredFirmware), callback); + } catch(e) { + callback({success: false, error: e}); + } + } + + this.getBalance = function (callback, requiredFirmware) { + manager.sendWithChannel(_fwStrFix({ + type: 'accountinfo' + }, requiredFirmware), callback) + } + + /** + * @typedef SignTxResult + * @param {boolean} success + * @param {?string} error + * @param {?string} serialized_tx serialized tx, in hex, including signatures + * @param {?array} signatures array of input signatures, in hex + */ + + /** + * Sign a transaction in the device and return both serialized + * transaction and the signatures. + * + * @param {array} inputs + * @param {array} outputs + * @param {function(SignTxResult)} callback + * @param {?(string|array)} requiredFirmware + * + * @see https://github.com/trezor/trezor-common/blob/master/protob/types.proto + */ + this.signTx = function (inputs, outputs, callback, requiredFirmware, coin) { + manager.sendWithChannel(_fwStrFix({ + type: 'signtx', + inputs: inputs, + outputs: outputs, + coin: coin + }, requiredFirmware), callback); + }; + + // new implementation with ethereum at beginnig + this.ethereumSignTx = function() { + this.signEthereumTx.apply(this, arguments); + } + + // old fallback + this.signEthereumTx = function ( + address_n, + nonce, + gas_price, + gas_limit, + to, + value, + data, + chain_id, + callback, + requiredFirmware + ) { + if (requiredFirmware == null) { + requiredFirmware = '1.4.0'; // first firmware that supports ethereum + } + if (typeof address_n === 'string') { + address_n = parseHDPath(address_n); + } + manager.sendWithChannel(_fwStrFix({ + type: 'signethtx', + address_n: address_n, + nonce: nonce, + gas_price: gas_price, + gas_limit: gas_limit, + to: to, + value: value, + data: data, + chain_id: chain_id, + }, requiredFirmware), callback); + }; + + /** + * @typedef TxRecipient + * @param {number} amount the amount to send, in satoshis + * @param {string} address the address of the recipient + */ + + /** + * Compose a transaction by doing BIP-0044 discovery, letting the user + * select an account, and picking UTXO by internal preferences. + * Transaction is then signed and returned in the same format as + * `signTx`. Only supports BIP-0044 accounts (single-signature). + * + * @param {array} recipients + * @param {function(SignTxResult)} callback + * @param {?(string|array)} requiredFirmware + */ + this.composeAndSignTx = function (recipients, callback, requiredFirmware) { + manager.sendWithChannel(_fwStrFix({ + type: 'composetx', + recipients: recipients + }, requiredFirmware), callback); + }; + + /** + * @typedef RequestLoginResult + * @param {boolean} success + * @param {?string} error + * @param {?string} public_key public key used for signing, in hex + * @param {?string} signature signature, in hex + */ + + /** + * Sign a login challenge for active origin. + * + * @param {?string} hosticon + * @param {string} challenge_hidden + * @param {string} challenge_visual + * @param {string|function(RequestLoginResult)} callback + * @param {?(string|array)} requiredFirmware + * + * @see https://github.com/trezor/trezor-common/blob/master/protob/messages.proto + */ + this.requestLogin = function ( + hosticon, + challenge_hidden, + challenge_visual, + callback, + requiredFirmware + ) { + if (typeof callback === 'string') { + // special case for a login through button. + // `callback` is name of global var + callback = window[callback]; + } + if (!callback) { + throw new TypeError('TrezorConnect: login callback not found'); + } + manager.sendWithChannel(_fwStrFix({ + type: 'login', + icon: hosticon, + challenge_hidden: challenge_hidden, + challenge_visual: challenge_visual + }, requiredFirmware), callback); + }; + + /** + * @typedef SignMessageResult + * @param {boolean} success + * @param {?string} error + * @param {?string} address address (in base58check) + * @param {?string} signature signature, in base64 + */ + + /** + * Sign a message + * + * @param {string|array} path + * @param {string} message to sign (ascii) + * @param {string|function(SignMessageResult)} callback + * @param {?string} opt_coin - (optional) name of coin (default Bitcoin) + * @param {?(string|array)} requiredFirmware + * + */ + this.signMessage = function ( + path, + message, + callback, + opt_coin, + requiredFirmware + ) { + if (typeof path === 'string') { + path = parseHDPath(path); + } + if (!opt_coin) { + opt_coin = 'Bitcoin'; + } + if (!callback) { + throw new TypeError('TrezorConnect: callback not found'); + } + manager.sendWithChannel(_fwStrFix({ + type: 'signmsg', + path: path, + message: message, + coin: opt_coin, + }, requiredFirmware), callback); + }; + + /** + * Sign an Ethereum message + * + * @param {string|array} path + * @param {string} message to sign (ascii) + * @param {string|function(SignMessageResult)} callback + * @param {?(string|array)} requiredFirmware + * + */ + this.ethereumSignMessage = function ( + path, + message, + callback, + requiredFirmware + ) { + if (typeof path === 'string') { + path = parseHDPath(path); + } + if (!callback) { + throw new TypeError('TrezorConnect: callback not found'); + } + manager.sendWithChannel(_fwStrFix({ + type: 'signethmsg', + path: path, + message: message, + }, requiredFirmware), callback); + }; + + /** + * Verify message + * + * @param {string} address + * @param {string} signature (base64) + * @param {string} message (string) + * @param {string|function()} callback + * @param {?string} opt_coin - (optional) name of coin (default Bitcoin) + * @param {?(string|array)} requiredFirmware + * + */ + this.verifyMessage = function ( + address, + signature, + message, + callback, + opt_coin, + requiredFirmware + ) { + if (!opt_coin) { + opt_coin = 'Bitcoin'; + } + if (!callback) { + throw new TypeError('TrezorConnect: callback not found'); + } + manager.sendWithChannel(_fwStrFix({ + type: 'verifymsg', + address: address, + signature: signature, + message: message, + coin: {coin_name: opt_coin}, + }, requiredFirmware), callback); + }; + + /** + * Verify ethereum message + * + * @param {string} address + * @param {string} signature (base64) + * @param {string} message (string) + * @param {string|function()} callback + * @param {?(string|array)} requiredFirmware + * + */ + this.ethereumVerifyMessage = function ( + address, + signature, + message, + callback, + requiredFirmware + ) { + if (!callback) { + throw new TypeError('TrezorConnect: callback not found'); + } + manager.sendWithChannel(_fwStrFix({ + type: 'verifyethmsg', + address: address, + signature: signature, + message: message, + }, requiredFirmware), callback); + }; + + /** + * Symmetric key-value encryption + * + * @param {string|array} path + * @param {string} key to show on device display + * @param {string} value hexadecimal value, length a multiple of 16 bytes + * @param {boolean} encrypt / decrypt direction + * @param {boolean} ask_on_encrypt (should user confirm on encrypt?) + * @param {boolean} ask_on_decrypt (should user confirm on decrypt?) + * @param {string|function()} callback + * @param {?(string|array)} requiredFirmware + * + */ + this.cipherKeyValue = function ( + path, + key, + value, + encrypt, + ask_on_encrypt, + ask_on_decrypt, + callback, + requiredFirmware + ) { + if (typeof path === 'string') { + path = parseHDPath(path); + } + if (typeof value !== 'string') { + throw new TypeError('TrezorConnect: Value must be a string'); + } + if (!(/^[0-9A-Fa-f]*$/.test(value))) { + throw new TypeError('TrezorConnect: Value must be hexadecimal'); + } + if (value.length % 32 !== 0) { + // 1 byte == 2 hex strings + throw new TypeError('TrezorConnect: Value length must be multiple of 16 bytes'); + } + if (!callback) { + throw new TypeError('TrezorConnect: callback not found'); + } + manager.sendWithChannel(_fwStrFix({ + type: 'cipherkeyvalue', + path: path, + key: key, + value: value, + encrypt: !!encrypt, + ask_on_encrypt: !!ask_on_encrypt, + ask_on_decrypt: !!ask_on_decrypt + }, requiredFirmware), callback); + }; + + this.nemGetAddress = function ( + address_n, + network, + callback, + requiredFirmware + ) { + if (requiredFirmware == null) { + requiredFirmware = '1.6.0'; // first firmware that supports NEM + } + if (typeof address_n === 'string') { + address_n = parseHDPath(address_n); + } + manager.sendWithChannel(_fwStrFix({ + type: 'nemGetAddress', + address_n: address_n, + network: network, + }, requiredFirmware), callback); + } + + this.nemSignTx = function ( + address_n, + transaction, + callback, + requiredFirmware + ) { + if (requiredFirmware == null) { + requiredFirmware = '1.6.0'; // first firmware that supports NEM + } + if (typeof address_n === 'string') { + address_n = parseHDPath(address_n); + } + manager.sendWithChannel(_fwStrFix({ + type: 'nemSignTx', + address_n: address_n, + transaction: transaction + }, requiredFirmware), callback); + } + + this.pushTransaction = function ( + rawTx, + callback + ) { + if (!(/^[0-9A-Fa-f]*$/.test(rawTx))) { + throw new TypeError('TrezorConnect: Transaction must be hexadecimal'); + } + if (!callback) { + throw new TypeError('TrezorConnect: callback not found'); + } + + manager.sendWithChannel({ + type: 'pushtx', + rawTx: rawTx, + }, callback); + } + + /** + * Display address on device + * + * @param {array} address + * @param {string} coin + * @param {boolean} segwit + * @param {?(string|array)} requiredFirmware + * + */ + this.getAddress = function (address, coin, segwit, callback, requiredFirmware) { + + if (typeof address === 'string') { + address = parseHDPath(address); + } + + manager.sendWithChannel(_fwStrFix({ + type: 'getaddress', + address_n: address, + coin: coin, + segwit: segwit + }, requiredFirmware), callback); + } + + /** + * Display ethereum address on device + * + * @param {array} address + * @param {?(string|array)} requiredFirmware + * + */ + this.ethereumGetAddress = function (address, callback, requiredFirmware) { + + if (typeof address === 'string') { + address = parseHDPath(address); + } + + manager.sendWithChannel(_fwStrFix({ + type: 'ethgetaddress', + address_n: address, + }, requiredFirmware), callback); + } + + var LOGIN_CSS = + ''; + + var LOGIN_ONCLICK = + 'TrezorConnect.requestLogin(' + + "'@hosticon@','@challenge_hidden@','@challenge_visual@','@callback@'" + + ')'; + + var LOGIN_HTML = + '
' + + ' ' + + ' ' + + ' @text@' + + ' ' + + ' ' + + ' What is TREZOR?' + + ' ' + + '
'; + + /** + * Find elements and replace them with login buttons. + * It's not required to use these special elements, feel free to call + * `TrezorConnect.requestLogin` directly. + */ + this.renderLoginButtons = function () { + var elements = document.getElementsByTagName('trezor:login'); + + for (var i = 0; i < elements.length; i++) { + var e = elements[i]; + var text = e.getAttribute('text') || 'Sign in with TREZOR'; + var callback = e.getAttribute('callback') || ''; + var hosticon = e.getAttribute('icon') || ''; + var challenge_hidden = e.getAttribute('challenge_hidden') || ''; + var challenge_visual = e.getAttribute('challenge_visual') || ''; + + // it's not valid to put markup into attributes, so let users + // supply a raw text and make TREZOR bold + text = text.replace('TREZOR', 'TREZOR'); + e.outerHTML = + (LOGIN_CSS + LOGIN_HTML) + .replace('@text@', text) + .replace('@callback@', callback) + .replace('@hosticon@', hosticon) + .replace('@challenge_hidden@', challenge_hidden) + .replace('@challenge_visual@', challenge_visual) + .replace('@connect_path@', POPUP_PATH); + } + }; } /* @@ -725,65 +705,25 @@ class TrezorConnect { */ function parseHDPath(string) { - return string - .toLowerCase() - .split('/') - .filter(function(p) { - return p !== 'm'; - }) - .map(function(p) { - var hardened = false; - if (p[p.length - 1] === "'") { - hardened = true; - p = p.substr(0, p.length - 1); - } - if (isNaN(p)) { - throw new Error('Not a valid path.'); - } - var n = parseInt(p); - if (hardened) { - // hardened index - n = (n | 0x80000000) >>> 0; - } - return n; - }); -} - -function getIdFromPath(path) { - if (path.length !== 3) { - throw new Error(); - } - if (path[0] >>> 0 !== (44 | HD_HARDENED) >>> 0) { - throw new Error(); - } - if (path[1] >>> 0 !== (0 | HD_HARDENED) >>> 0) { - throw new Error(); - } - return (path[2] & ~HD_HARDENED) >>> 0; -} - -// parses first argument from getAccountInfo -function parseAccountInfoInput(input) { - if (input == null) { - return null; - } - - if (typeof input === 'string') { - if (input.substr(0, 4) === 'xpub') { - return input; - } - if (isNaN(input)) { - var parsedPath = parseHDPath(input); - return getIdFromPath(parsedPath); - } else { - return parseInt(input); - } - } else if (Array.isArray(input)) { - return getIdFromPath(input); - } else if (typeof input === 'number') { - return input; - } - throw new Error('Unknown input format.'); + return string + .toLowerCase() + .split('/') + .filter(function (p) { return p !== 'm'; }) + .map(function (p) { + var hardened = false; + if (p[p.length - 1] === "'") { + hardened = true; + p = p.substr(0, p.length - 1); + } + if (isNaN(p)) { + throw new Error('Not a valid path.'); + } + var n = parseInt(p); + if (hardened) { // hardened index + n = (n | 0x80000000) >>> 0; + } + return n; + }); } /* @@ -791,283 +731,284 @@ function parseAccountInfoInput(input) { */ function ChromePopup(url, name, width, height) { - var left = (screen.width - width) / 2; - var top = (screen.height - height) / 2; - var opts = { - id: name, - innerBounds: { - width: width, - height: height, - left: left, - top: top - } - }; + var left = (screen.width - width) / 2; + var top = (screen.height - height) / 2; + var opts = { + id: name, + innerBounds: { + width: width, + height: height, + left: left, + top: top + } + }; - var closed = function() { - if (this.onclose) { - this.onclose(false); // never report as blocked - } - }.bind(this); + var closed = function () { + if (this.onclose) { + this.onclose(false); // never report as blocked + } + }.bind(this); - var opened = function(w) { - this.window = w; - this.window.onClosed.addListener(closed); - }.bind(this); + var opened = function (w) { + this.window = w; + this.window.onClosed.addListener(closed); + }.bind(this); - chrome.app.window.create(url, opts, opened); + chrome.app.window.create(url, opts, opened); - this.name = name; - this.window = null; - this.onclose = null; + this.name = name; + this.window = null; + this.onclose = null; } function ChromeChannel(popup, waiting) { - var port = null; + var port = null; - var respond = function(data) { - if (waiting) { - var w = waiting; - waiting = null; - w(data); - } - }; + var respond = function (data) { + if (waiting) { + var w = waiting; + waiting = null; + w(data); + } + }; - var setup = function(p) { - if (p.name === popup.name) { - port = p; - port.onMessage.addListener(respond); - chrome.runtime.onConnect.removeListener(setup); - } - }; + var setup = function (p) { + if (p.name === popup.name) { + port = p; + port.onMessage.addListener(respond); + chrome.runtime.onConnect.removeListener(setup); + } + }; - chrome.runtime.onConnect.addListener(setup); + chrome.runtime.onConnect.addListener(setup); - this.respond = respond; + this.respond = respond; - this.close = function() { - chrome.runtime.onConnect.removeListener(setup); - port.onMessage.removeListener(respond); - port.disconnect(); - port = null; - }; + this.close = function () { + chrome.runtime.onConnect.removeListener(setup); + port.onMessage.removeListener(respond); + port.disconnect(); + port = null; + }; - this.send = function(value, callback) { - if (waiting === null) { - waiting = callback; + this.send = function (value, callback) { + if (waiting === null) { + waiting = callback; - if (port) { - port.postMessage(value); - } else { - throw new Error(ERR_CHROME_NOT_CONNECTED); - } - } else { - throw new Error(ERR_ALREADY_WAITING); - } - }; + if (port) { + port.postMessage(value); + } else { + throw new Error(ERR_CHROME_NOT_CONNECTED); + } + } else { + throw new Error(ERR_ALREADY_WAITING); + } + }; } function Popup(url, origin, name, width, height) { - var left = (screen.width - width) / 2; - var top = (screen.height - height) / 2; - var opts = - 'width=' + - width + - ',height=' + - height + - ',left=' + - left + - ',top=' + - top + - ',menubar=no' + - ',toolbar=no' + - ',location=no' + - ',personalbar=no' + - ',status=no'; - var w = window.open(url, name, opts); + var left = (screen.width - width) / 2; + var top = (screen.height - height) / 2; + var opts = + 'width=' + width + + ',height=' + height + + ',left=' + left + + ',top=' + top + + ',menubar=no' + + ',toolbar=no' + + ',location=no' + + ',personalbar=no' + + ',status=no'; + var w = window.open(url, name, opts); - var interval; - var blocked = w.closed; - var iterate = function() { - if (w.closed) { - clearInterval(interval); - if (this.onclose) { - this.onclose(blocked); - } - } - }.bind(this); - interval = setInterval(iterate, 100); + var interval; + var blocked = w.closed; + var iterate = function () { + if (w.closed) { + clearInterval(interval); + if (this.onclose) { + this.onclose(blocked); + } + } + }.bind(this); + interval = setInterval(iterate, 100); - this.window = w; - this.origin = origin; - this.onclose = null; + this.window = w; + this.origin = origin; + this.onclose = null; } function Channel(popup, waiting) { - var respond = function(data) { - if (waiting) { - var w = waiting; - waiting = null; - w(data); - } - }; - var receive = function(event) { - if (event.source === popup.window && event.origin === popup.origin) { - respond(event.data); - } - }; + var respond = function (data) { + if (waiting) { + var w = waiting; + waiting = null; + w(data); + } + }; - window.addEventListener('message', receive); + var receive = function (event) { + var org1 = event.origin.match(/^.+\:\/\/[^\‌​/]+/)[0]; + var org2 = popup.origin.match(/^.+\:\/\/[^\‌​/]+/)[0]; + //if (event.source === popup.window && event.origin === popup.origin) { + if (event.source === popup.window && org1 === org2) { + respond(event.data); + } + }; - this.respond = respond; + window.addEventListener('message', receive); - this.close = function() { - window.removeEventListener('message', receive); - }; + this.respond = respond; - this.send = function(value, callback) { - if (waiting === null) { - waiting = callback; - popup.window.postMessage(value, popup.origin); - } else { - throw new Error(ERR_ALREADY_WAITING); - } - }; + this.close = function () { + window.removeEventListener('message', receive); + }; + + this.send = function (value, callback) { + if (waiting === null) { + waiting = callback; + popup.window.postMessage(value, popup.origin); + } else { + throw new Error(ERR_ALREADY_WAITING); + } + }; } function ConnectedChannel(p) { - var ready = function() { - clearTimeout(this.timeout); - this.popup.onclose = null; - this.ready = true; - this.onready(); - }.bind(this); - var closed = function(blocked) { - clearTimeout(this.timeout); - this.channel.close(); - if (blocked) { - this.onerror(new Error(ERR_WINDOW_BLOCKED)); + var ready = function () { + clearTimeout(this.timeout); + this.popup.onclose = null; + this.ready = true; + this.onready(); + }.bind(this); + + var closed = function (blocked) { + clearTimeout(this.timeout); + this.channel.close(); + if (blocked) { + this.onerror(new Error(ERR_WINDOW_BLOCKED)); + } else { + this.onerror(new Error(ERR_WINDOW_CLOSED)); + } + }.bind(this); + + var timedout = function () { + this.popup.onclose = null; + if (this.popup.window) { + this.popup.window.close(); + } + this.channel.close(); + this.onerror(new Error(ERR_TIMED_OUT)); + }.bind(this); + + if (IS_CHROME_APP) { + this.popup = new ChromePopup(p.chromeUrl, p.name, p.width, p.height); + this.channel = new ChromeChannel(this.popup, ready); } else { - this.onerror(new Error(ERR_WINDOW_CLOSED)); + this.popup = new Popup(p.url, p.origin, p.name, p.width, p.height); + this.channel = new Channel(this.popup, ready); } - }.bind(this); - var timedout = function() { - this.popup.onclose = null; - if (this.popup.window) { - this.popup.window.close(); - } - this.channel.close(); - this.onerror(new Error(ERR_TIMED_OUT)); - }.bind(this); + this.timeout = setTimeout(timedout, POPUP_INIT_TIMEOUT); - if (IS_CHROME_APP) { - this.popup = new ChromePopup(p.chromeUrl, p.name, p.width, p.height); - this.channel = new ChromeChannel(this.popup, ready); - } else { - this.popup = new Popup(p.url, p.origin, p.name, p.width, p.height); - this.channel = new Channel(this.popup, ready); - } + this.popup.onclose = closed; - this.timeout = setTimeout(timedout, POPUP_INIT_TIMEOUT); - - this.popup.onclose = closed; - - this.ready = false; - this.onready = null; - this.onerror = null; + this.ready = false; + this.onready = null; + this.onerror = null; } function PopupManager() { - var cc = null; + var cc = null; - var closed = function() { - cc.channel.respond(new Error(ERR_WINDOW_CLOSED)); - cc.channel.close(); - cc = null; - }; - - var open = function(callback) { - cc = new ConnectedChannel({ - name: 'trezor-connect', - width: 600, - height: 500, - origin: POPUP_ORIGIN, - path: POPUP_PATH, - url: POPUP_URL, - chromeUrl: CHROME_URL - }); - cc.onready = function() { - cc.popup.onclose = closed; - callback(cc.channel); + var closed = function () { + cc.channel.respond(new Error(ERR_WINDOW_CLOSED)); + cc.channel.close(); + cc = null; }; - cc.onerror = function(error) { - cc = null; - callback(error); - }; - }.bind(this); - this.closeAfterSuccess = true; - this.closeAfterFailure = true; - - this.close = function() { - if (cc && cc.popup.window) { - cc.popup.window.close(); - } - }; - - this.waitForChannel = function(callback) { - if (cc) { - if (cc.ready) { - callback(cc.channel); - } else { - callback(new Error(ERR_ALREADY_WAITING)); - } - } else { - try { - open(callback); - } catch (e) { - callback(new Error(ERR_WINDOW_BLOCKED)); - } - } - }; - - this.sendWithChannel = function(message, callback) { - message.bitcoreURLS = this.bitcoreURLS || null; - message.accountDiscoveryLimit = this.accountDiscoveryLimit || null; - message.accountDiscoveryGapLength = this.accountDiscoveryGapLength || null; - message.accountDiscoveryBip44CoinType = this.accountDiscoveryBip44CoinType || null; - - var respond = function(response) { - var succ = response.success && this.closeAfterSuccess; - var fail = !response.success && this.closeAfterFailure; - if (succ || fail) { - this.close(); - } - callback(response); + var open = function (callback) { + cc = new ConnectedChannel({ + name: 'trezor-connect', + width: 600, + height: 500, + origin: POPUP_ORIGIN, + path: POPUP_PATH, + url: POPUP_URL, + chromeUrl: CHROME_URL + }); + cc.onready = function () { + cc.popup.onclose = closed; + callback(cc.channel); + }; + cc.onerror = function (error) { + cc = null; + callback(error); + }; }.bind(this); - var onresponse = function(response) { - if (response instanceof Error) { - var error = response; - respond({ success: false, error: error.message }); - } else { - respond(response); - } + this.closeAfterSuccess = true; + this.closeAfterFailure = true; + + this.close = function () { + if (cc && cc.popup.window) { + cc.popup.window.close(); + } }; - var onchannel = function(channel) { - if (channel instanceof Error) { - var error = channel; - respond({ success: false, error: error.message }); - } else { - channel.send(message, onresponse); - } + this.waitForChannel = function (callback) { + if (cc) { + if (cc.ready) { + callback(cc.channel); + } else { + callback(new Error(ERR_ALREADY_WAITING)); + } + } else { + try { + open(callback); + } catch (e) { + callback(new Error(ERR_WINDOW_BLOCKED)); + } + } }; - this.waitForChannel(onchannel); - }; + this.sendWithChannel = function (message, callback) { + message.bitcoreURLS = this.bitcoreURLS || null; + message.accountDiscoveryLimit = this.accountDiscoveryLimit || null; + message.accountDiscoveryGapLength = this.accountDiscoveryGapLength || null; + message.accountDiscoveryBip44CoinType = this.accountDiscoveryBip44CoinType || null; + + var respond = function (response) { + var succ = response.success && this.closeAfterSuccess; + var fail = !response.success && this.closeAfterFailure; + if (succ || fail) { + this.close(); + } + callback(response); + }.bind(this); + + var onresponse = function (response) { + if (response instanceof Error) { + var error = response; + respond({ success: false, error: error.message }); + } else { + respond(response); + } + }; + + var onchannel = function (channel) { + if (channel instanceof Error) { + var error = channel; + respond({ success: false, error: error.message }); + } else { + channel.send(message, onresponse); + } + }; + + this.waitForChannel(onchannel); + }; } var connect = new TrezorConnect();