diff options
Diffstat (limited to 'app')
35 files changed, 2435 insertions, 339 deletions
diff --git a/app/_locales/cs/messages.json b/app/_locales/cs/messages.json new file mode 100644 index 000000000..6a4ebc8a5 --- /dev/null +++ b/app/_locales/cs/messages.json @@ -0,0 +1,912 @@ +{ + "accept": { + "message": "Přijmout" + }, + "account": { + "message": "Účet" + }, + "accountDetails": { + "message": "Detaily účtu" + }, + "accountName": { + "message": "Název účtu" + }, + "address": { + "message": "Adresa" + }, + "addCustomToken": { + "message": "Přidat vlastní token" + }, + "addToken": { + "message": "Přidat token" + }, + "addTokens": { + "message": "Přidat tokeny" + }, + "amount": { + "message": "Částka" + }, + "amountPlusGas": { + "message": "Částka + palivo" + }, + "appDescription": { + "message": "Ethereum rozšíření prohlížeče", + "description": "The description of the application" + }, + "appName": { + "message": "MetaMask", + "description": "The name of the application" + }, + "approved": { + "message": "Schváleno" + }, + "attemptingConnect": { + "message": "Pokouším se připojit k blockchainu." + }, + "attributions": { + "message": "Zásluhy" + }, + "available": { + "message": "Dostupné" + }, + "back": { + "message": "Zpět" + }, + "balance": { + "message": "Zůstatek:" + }, + "balances": { + "message": "Zůstatek tokenu" + }, + "balanceIsInsufficientGas": { + "message": "Nedostatek prostředků pro aktuální množství paliva" + }, + "beta": { + "message": "BETA" + }, + "betweenMinAndMax": { + "message": "musí být větší nebo roven $1 a menší nebo roven $2.", + "description": "helper for inputting hex as decimal input" + }, + "blockiesIdenticon": { + "message": "Použít Blockies Identicon" + }, + "borrowDharma": { + "message": "Pújčit si přes Dharma (Beta)" + }, + "builtInCalifornia": { + "message": "MetaMask je navržen a vytvořen v Kalifornii." + }, + "buy": { + "message": "Koupit" + }, + "buyCoinbase": { + "message": "Nákup na Coinbase" + }, + "buyCoinbaseExplainer": { + "message": "Coinbase je světově nejoblíbenější místo k nákupu a prodeji bitcoinu, etherea nebo litecoinu." + }, + "ok": { + "message": "Ok" + }, + "cancel": { + "message": "Zrušit" + }, + "classicInterface": { + "message": "Použít klasické rozhraní" + }, + "clickCopy": { + "message": "Kliknutím zkopírovat" + }, + "confirm": { + "message": "Potvrdit" + }, + "confirmed": { + "message": "Potvrzeno" + }, + "confirmContract": { + "message": "Potvrdit kontrakt" + }, + "confirmPassword": { + "message": "Potvrdit heslo" + }, + "confirmTransaction": { + "message": "Potvrdit transakci" + }, + "continue": { + "message": "Pokračovat" + }, + "continueToCoinbase": { + "message": "Přejít na Coinbase" + }, + "contractDeployment": { + "message": "Nasazení kontraktu" + }, + "conversionProgress": { + "message": "Provádí se převod" + }, + "copiedButton": { + "message": "Zkopírováno" + }, + "copiedClipboard": { + "message": "Zkopírováno do schránky" + }, + "copiedExclamation": { + "message": "Zkopírováno!" + }, + "copiedSafe": { + "message": "Zkopíroval jsem to na bezpečné místo" + }, + "copy": { + "message": "Kopírovat" + }, + "copyToClipboard": { + "message": "Kopírovat do schránky" + }, + "copyButton": { + "message": " Kopírovat " + }, + "copyPrivateKey": { + "message": "Toto je váš privátní klíč (kliknutím zkopírujte)" + }, + "create": { + "message": "Vytvořit" + }, + "createAccount": { + "message": "Vytvořit účet" + }, + "createDen": { + "message": "Vytvořit" + }, + "crypto": { + "message": "Krypto", + "description": "Exchange type (cryptocurrencies)" + }, + "currentConversion": { + "message": "Aktuální převod" + }, + "currentNetwork": { + "message": "Aktuální síť" + }, + "customGas": { + "message": "Nastavit palivo" + }, + "customToken": { + "message": "Vlastní token" + }, + "customize": { + "message": "Nastavit" + }, + "customRPC": { + "message": "Vlastní RPC" + }, + "decimalsMustZerotoTen": { + "message": "Desetinných míst musí být od 0 do 36." + }, + "decimal": { + "message": "Počet desetinných míst přesnosti" + }, + "defaultNetwork": { + "message": "Výchozí síť pro Etherové transakce je Main Net." + }, + "denExplainer": { + "message": "Váš DEN je heslem šifrované uložiště v MetaMasku." + }, + "deposit": { + "message": "Vklad" + }, + "depositBTC": { + "message": "Vložte BTC na níže uvedenou adresu:" + }, + "depositCoin": { + "message": "Vložte $1 na níže uvedenou adresu", + "description": "Tells the user what coin they have selected to deposit with shapeshift" + }, + "depositEth": { + "message": "Vložit Eth" + }, + "depositEther": { + "message": "Vložit Ether" + }, + "depositFiat": { + "message": "Vklad s fiat měnou" + }, + "depositFromAccount": { + "message": "Vložte z jiného účtu" + }, + "depositShapeShift": { + "message": "Vklad přes ShapeShift" + }, + "depositShapeShiftExplainer": { + "message": "Pokud vlastníte jiné kryptoměny, můžete je směnit Ether a vložit ho přímo do peněženky MetaMask. Bez založení účtu." + }, + "details": { + "message": "Podrobnosti" + }, + "directDeposit": { + "message": "Přímý vklad" + }, + "directDepositEther": { + "message": "Vložit Ether přímo" + }, + "directDepositEtherExplainer": { + "message": "Pokud už vlastníte nějaký Ether, nejrychleji ho dostanete do peněženky přímým vkladem." + }, + "done": { + "message": "Hotovo" + }, + "downloadStateLogs": { + "message": "Stáhnout stavové protokoly" + }, + "dropped": { + "message": "Zrušeno" + }, + "edit": { + "message": "Upravit" + }, + "editAccountName": { + "message": "Upravit název účtu" + }, + "emailUs": { + "message": "Napište nám e-mail!" + }, + "encryptNewDen": { + "message": "Zašifrujte svůj nový DEN" + }, + "enterPassword": { + "message": "Zadejte heslo" + }, + "enterPasswordConfirm": { + "message": "Zadejte heslo k potvrzení" + }, + "passwordNotLongEnough": { + "message": "Heslo není dost dlouhé" + }, + "passwordsDontMatch": { + "message": "Hesla nejsou stejná" + }, + "etherscanView": { + "message": "Prohlédněte si účet na Etherscan" + }, + "exchangeRate": { + "message": "Směnný kurz" + }, + "exportPrivateKey": { + "message": "Exportovat privátní klíč" + }, + "exportPrivateKeyWarning": { + "message": "Exportujte privátní klíč na vlastní riziko." + }, + "failed": { + "message": "Neúspěšné" + }, + "fiat": { + "message": "FIAT", + "description": "Exchange type" + }, + "fileImportFail": { + "message": "Import souboru nefunguje? Klikněte sem!", + "description": "Helps user import their account from a JSON file" + }, + "followTwitter": { + "message": "Sledujte nás na Twitteru" + }, + "from": { + "message": "Od" + }, + "fromToSame": { + "message": "Adresy odesílatele a příjemce nemohou být stejné" + }, + "fromShapeShift": { + "message": "Z ShapeShift" + }, + "gas": { + "message": "Palivo", + "description": "Short indication of gas cost" + }, + "gasFee": { + "message": "Poplatek za palivo" + }, + "gasLimit": { + "message": "Limit paliva" + }, + "gasLimitCalculation": { + "message": "Počítáme doporučený limit paliva na základě úspěšnosti v síti." + }, + "gasLimitRequired": { + "message": "Limit paliva je povinný" + }, + "gasLimitTooLow": { + "message": "Limit paliva musí být alespoň 21000" + }, + "generatingSeed": { + "message": "Generuji klíčovou frázi..." + }, + "gasPrice": { + "message": "Cena paliva (GWEI)" + }, + "gasPriceCalculation": { + "message": "Počítáme doporučenou cenu paliva na základě úspěšnosti v síti." + }, + "gasPriceRequired": { + "message": "Cena paliva je povinná" + }, + "getEther": { + "message": "Získejte Ether" + }, + "getEtherFromFaucet": { + "message": "Získejte Ether z faucetu za $1.", + "description": "Displays network name for Ether faucet" + }, + "greaterThanMin": { + "message": "musí být větší nebo roven $1.", + "description": "helper for inputting hex as decimal input" + }, + "here": { + "message": "zde", + "description": "as in -click here- for more information (goes with troubleTokenBalances)" + }, + "hereList": { + "message": "Tady je seznam!!!!" + }, + "hide": { + "message": "Skrýt" + }, + "hideToken": { + "message": "Skrýt token" + }, + "hideTokenPrompt": { + "message": "Skrýt token?" + }, + "howToDeposit": { + "message": "Jakým způsobem chcete vložit Ether?" + }, + "holdEther": { + "message": "Dovoluje vám držet ether a tokeny a slouží jako most k decentralizovaným aplikacím." + }, + "import": { + "message": "Import", + "description": "Button to import an account from a selected file" + }, + "importAccount": { + "message": "Import účtu" + }, + "importAccountMsg": { + "message":"Importované účty nebudou spojeny s vaší původní MetaMaskovou klíčovou frází. Zjistěte více o importovaných účtech " + }, + "importAnAccount": { + "message": "Import účtu" + }, + "importDen": { + "message": "Import existujícího DEN" + }, + "imported": { + "message": "Importováno", + "description": "status showing that an account has been fully loaded into the keyring" + }, + "infoHelp": { + "message": "Informace a nápověda" + }, + "insufficientFunds": { + "message": "Nedostatek finančních prostředků." + }, + "insufficientTokens": { + "message": "Nedostatek tokenů." + }, + "invalidAddress": { + "message": "Neplatná adresa" + }, + "invalidAddressRecipient": { + "message": "Adresa příjemce je neplatná" + }, + "invalidGasParams": { + "message": "Neplatná parametry paliva" + }, + "invalidInput": { + "message": "Neplatný vstup." + }, + "invalidRequest": { + "message": "Neplatný požadavek" + }, + "invalidRPC": { + "message": "Neplatné RPC URI" + }, + "jsonFail": { + "message": "Něco se pokazilo. Prosím, ujistěte se, že váš JSON soubor má správný formát." + }, + "jsonFile": { + "message": "JSON soubor", + "description": "format for importing an account" + }, + "keepTrackTokens": { + "message": "Udržujte si záznamy o tokenech, které jste koupili s účtem v MetaMasku." + }, + "kovan": { + "message": "Kovan Test Network" + }, + "knowledgeDataBase": { + "message": "Navštivte naši Knowledge Base" + }, + "max": { + "message": "Max" + }, + "learnMore": { + "message": "Zjistěte více." + }, + "lessThanMax": { + "message": "musí být menší nebo roven $1.", + "description": "helper for inputting hex as decimal input" + }, + "likeToAddTokens": { + "message": "Chcete přidat tyto tokeny?" + }, + "links": { + "message": "Odkazy" + }, + "limit": { + "message": "Limit" + }, + "loading": { + "message": "Načítám..." + }, + "loadingTokens": { + "message": "Načítám tokeny..." + }, + "localhost": { + "message": "Localhost 8545" + }, + "login": { + "message": "Přihlásit" + }, + "logout": { + "message": "Odhlásit" + }, + "loose": { + "message": "Nevázané" + }, + "loweCaseWords": { + "message": "slova klíčové fráze mají pouze malá písmena" + }, + "mainnet": { + "message": "Main Ethereum Network" + }, + "message": { + "message": "Zpráva" + }, + "metamaskDescription": { + "message": "MetaMask je bezpečný osobní trezor pro Ethereum." + }, + "min": { + "message": "Minimum" + }, + "myAccounts": { + "message": "Moje účty" + }, + "mustSelectOne": { + "message": "Musíte zvolit aspoň 1 token." + }, + "needEtherInWallet": { + "message": "Potřebujete Ether v peněžence, abyste mohli pomocí MetaMasku interagovat s decentralizovanými aplikacemi." + }, + "needImportFile": { + "message": "Musíte zvolit soubor k importu.", + "description": "User is important an account and needs to add a file to continue" + }, + "needImportPassword": { + "message": "Musíte zadat heslo pro zvolený soubor.", + "description": "Password and file needed to import an account" + }, + "negativeETH": { + "message": "Nelze odeslat zápornou částku ETH." + }, + "networks": { + "message": "Sítě" + }, + "newAccount": { + "message": "Nový účet" + }, + "newAccountNumberName": { + "message": "Účet $1", + "description": "Default name of next account to be created on create account screen" + }, + "newContract": { + "message": "Nový kontrakt" + }, + "newPassword": { + "message": "Nové heslo (min 8 znaků)" + }, + "newRecipient": { + "message": "Nový příjemce" + }, + "newRPC": { + "message": "Nová RPC URL" + }, + "next": { + "message": "Další" + }, + "noAddressForName": { + "message": "Pro toto jméno nebyla nastavena žádná adresa." + }, + "noDeposits": { + "message": "Žádný vklad" + }, + "noTransactionHistory": { + "message": "Žádná historie transakcí." + }, + "noTransactions": { + "message": "Žádné transakce" + }, + "notStarted": { + "message": "Nezačalo" + }, + "oldUI": { + "message": "Staré rozhraní" + }, + "oldUIMessage": { + "message": "Vrátili jste se ke starému rozhraní. Můžete přepnout na nové rozhraní v nastavení v pravém horním menu." + }, + "or": { + "message": "nebo", + "description": "choice between creating or importing a new account" + }, + "passwordCorrect": { + "message": "Ujistěte se, že je vaše heslo správně." + }, + "passwordMismatch": { + "message": "hesla nesouhlasí", + "description": "in password creation process, the two new password fields did not match" + }, + "passwordShort": { + "message": "heslo je krátké", + "description": "in password creation process, the password is not long enough to be secure" + }, + "pastePrivateKey": { + "message": "Vložte zde svůj privátní klíč:", + "description": "For importing an account from a private key" + }, + "pasteSeed": { + "message": "Svou klíčovou frázi vložte zde!" + }, + "personalAddressDetected": { + "message": "Detekována osobní adresa. Zadejte adresu kontraktu tokenu." + }, + "pleaseReviewTransaction": { + "message": "Zkontrolujte si transakci." + }, + "popularTokens": { + "message": "Oblíbené tokeny" + }, + "privacyMsg": { + "message": "Zásady ochrany osobních údajů" + }, + "privateKey": { + "message": "Privátní klíč", + "description": "select this type of file to use to import an account" + }, + "privateKeyWarning": { + "message": "Upozornění: Nikdy nezveřejněte tento klíč. Kdokoli může s vaším privátním klíčem odcizit vaše aktiva z účtu." + }, + "privateNetwork": { + "message": "Soukromá síť" + }, + "qrCode": { + "message": "Ukázat QR kód" + }, + "readdToken": { + "message": "Tento token můžete v budoucnu přidat zpět s „Přidat token“ v nastavení účtu." + }, + "readMore": { + "message": "Přečtěte si více zde." + }, + "readMore2": { + "message": "Přečtěte si více." + }, + "receive": { + "message": "Obrdžet" + }, + "recipientAddress": { + "message": "Adresa příjemce" + }, + "refundAddress": { + "message": "Adresa pro vrácení peněz" + }, + "rejected": { + "message": "Odmítnuto" + }, + "resetAccount": { + "message": "Resetovat účet" + }, + "restoreFromSeed": { + "message": "Obnovit z seed fráze" + }, + "restoreVault": { + "message": "Obnovit trezor" + }, + "required": { + "message": "Povinné" + }, + "retryWithMoreGas": { + "message": "Opakujte s vyšší cenou paliva" + }, + "walletSeed": { + "message": "Klíčová fráze peněženky" + }, + "revealSeedWords": { + "message": "Zobrazit slova klíčové fráze" + }, + "revealSeedWordsWarning": { + "message": "Nebnovujte slova klíčové fráze na veřejnosti! Tato slova mohou být použita k odcizení veškerých vyašich účtů." + }, + "revert": { + "message": "Zvrátit" + }, + "rinkeby": { + "message": "Rinkeby Test Network" + }, + "ropsten": { + "message": "Ropsten Test Network" + }, + "currentRpc": { + "message": "Současné RPC" + }, + "connectingToMainnet": { + "message": "Připojuji se k Main Ethereum Network" + }, + "connectingToRopsten": { + "message": "Připojuji se k Ropsten Test Network" + }, + "connectingToKovan": { + "message": "Připojuji se k Kovan Test Network" + }, + "connectingToRinkeby": { + "message": "Připojuji se k Rinkeby Test Network" + }, + "connectingToUnknown": { + "message": "Připojuji se k neznámé síti" + }, + "sampleAccountName": { + "message": "Např. můj nový účet", + "description": "Help user understand concept of adding a human-readable name to their account" + }, + "save": { + "message": "Uložit" + }, + "reprice_title": { + "message": "Změnit cenu transakce" + }, + "reprice_subtitle": { + "message": "Navyšte cenu paliva ve snaze k přepsání a urychlení vyší transakce" + }, + "saveAsFile": { + "message": "Uložit do souboru", + "description": "Account export process" + }, + "saveSeedAsFile": { + "message": "Uložit slova klíčové fráze do souboru" + }, + "search": { + "message": "Hledat" + }, + "secretPhrase": { + "message": "Zadejte svých 12 slov tajné fráze k obnovení trezoru." + }, + "newPassword8Chars": { + "message": "Nové heslo (min 8 znaků)" + }, + "seedPhraseReq": { + "message": "klíčové fráze mají 12 slov" + }, + "select": { + "message": "Vybrat" + }, + "selectCurrency": { + "message": "Vybrat měnu" + }, + "selectService": { + "message": "Vybrat službu" + }, + "selectType": { + "message": "Vybrat typ" + }, + "send": { + "message": "Odeslat" + }, + "sendETH": { + "message": "Odeslat ETH" + }, + "sendTokens": { + "message": "Odeslat tokeny" + }, + "onlySendToEtherAddress": { + "message": "Posílejte jen ETH na Ethereum adresu." + }, + "searchTokens": { + "message": "Hledat tokeny" + }, + "sendTokensAnywhere": { + "message": "Posílejte tokeny komukoli s Ethereum účtem" + }, + "settings": { + "message": "Nastavení" + }, + "info": { + "message": "Informace" + }, + "shapeshiftBuy": { + "message": "Nakoupit na ShapeShift" + }, + "showPrivateKeys": { + "message": "Zobrazit privátní klíče" + }, + "showQRCode": { + "message": "Zobrazit QR kód" + }, + "sign": { + "message": "Podepsat" + }, + "signed": { + "message": "Podepsáno" + }, + "signMessage": { + "message": "Podepsat zprávu" + }, + "signNotice": { + "message": "Podepsání zprávy může mít \nnebezpečný vedlejší učinek. Podepisujte zprávy pouze ze \nstránek, kterým plně důvěřujete celým svým účtem.\n Tato nebezpečná metoda bude odebrána v budoucí verzi. " + }, + "sigRequest": { + "message": "Požadavek podpisu" + }, + "sigRequested": { + "message": "Požádáno o podpis" + }, + "spaceBetween": { + "message": "mezi slovy může být pouze mezera" + }, + "status": { + "message": "Stav" + }, + "stateLogs": { + "message": "Stavové protokoly" + }, + "stateLogsDescription": { + "message": "Stavové protokoly obsahují vaše veřejné adresy účtů a odeslané transakce." + }, + "stateLogError": { + "message": "Chyba během získávání stavových protokolů." + }, + "submit": { + "message": "Odeslat" + }, + "submitted": { + "message": "Odesláno" + }, + "supportCenter": { + "message": "Navštivte naše centrum podpory" + }, + "symbolBetweenZeroTen": { + "message": "Symbol musí být mezi 0 a 10 znaky." + }, + "takesTooLong": { + "message": "Trvá to dlouho?" + }, + "terms": { + "message": "Podmínky použití" + }, + "testFaucet": { + "message": "Testovací faucet" + }, + "to": { + "message": "Komu: " + }, + "toETHviaShapeShift": { + "message": "$1 na ETH přes ShapeShift", + "description": "system will fill in deposit type in start of message" + }, + "tokenAddress": { + "message": "Adresa tokenu" + }, + "tokenAlreadyAdded": { + "message": "Token byl už přidán." + }, + "tokenBalance": { + "message": "Váš zůstatek tokenu je:" + }, + "tokenSelection": { + "message": "Vyhledejte token nebo je vyberte z našeho seznamu oblíbených tokenů." + }, + "tokenSymbol": { + "message": "Symbol tokenu" + }, + "tokenWarning1": { + "message": "Mějte přehled o tokenech, které jste koupili s účtem MetaMasku. Pokud jste koupili tokeny s jiným účtem, tyto tokeny se zde nezobrazí." + }, + "total": { + "message": "Celkem" + }, + "transactions": { + "message": "transakce" + }, + "transactionError": { + "message": "Chyba transakce. Vyhozena výjimka v kódu kontraktu." + }, + "transactionMemo": { + "message": "Poznámka transakce (nepovinné)" + }, + "transactionNumber": { + "message": "Číslo transakce" + }, + "transfers": { + "message": "Převody" + }, + "troubleTokenBalances": { + "message": "Měli jsme problém s načtením vašich tokenových zůstatků. Můžete je vidět ", + "description": "Followed by a link (here) to view token balances" + }, + "twelveWords": { + "message": "Těchto 12 slov je jedinou možností, jak obnovit MetaMask účet. \nUložte je na bezpečné a neveřejné místo." + }, + "typePassword": { + "message": "Zadejte své heslo" + }, + "uiWelcome": { + "message": "Vítejte v novém rozhraní (Beta)" + }, + "uiWelcomeMessage": { + "message": "Používáte nyní nové rozhraní MetaMasku. Rozhlédněte se kolem, vyzkoušejte nové funkce, jako jsou zasílání tokenů, a dejte nám vědět, pokud narazíte na problém." + }, + "unapproved": { + "message": "Neschváleno" + }, + "unavailable": { + "message": "Nedostupné" + }, + "unknown": { + "message": "Neznámé" + }, + "unknownNetwork": { + "message": "Neznámá soukromá síť" + }, + "unknownNetworkId": { + "message": "Neznámé ID sítě" + }, + "uriErrorMsg": { + "message": "URI vyžadují korektní HTTP/HTTPS prefix." + }, + "usaOnly": { + "message": "jen v USA", + "description": "Using this exchange is limited to people inside the USA" + }, + "usedByClients": { + "message": "Používána různými klienty" + }, + "useOldUI": { + "message": "Použijte staré rozhraní" + }, + "validFileImport": { + "message": "Musíte vybrat validní soubor k importu." + }, + "vaultCreated": { + "message": "Trezor vytvořen" + }, + "viewAccount": { + "message": "Zobrazit účet" + }, + "visitWebSite": { + "message": "Navštivte naši stránku" + }, + "warning": { + "message": "Varování" + }, + "welcomeBeta": { + "message": "Vítejte v MetaMask Beta" + }, + "whatsThis": { + "message": "Co to je?" + }, + "yourSigRequested": { + "message": "Je vyžadován váš podpis" + }, + "youSign": { + "message": "Podepisujete" + } +} diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index b372326ee..3b20ab49a 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -908,5 +908,8 @@ }, "youSign": { "message": "You are signing" + }, + "generatingTransaction": { + "message": "Generating transaction" } } diff --git a/app/_locales/hn/messages.json b/app/_locales/hn/messages.json index 323f4b4b3..b869560e5 100644 --- a/app/_locales/hn/messages.json +++ b/app/_locales/hn/messages.json @@ -9,7 +9,7 @@ "message": "खाता विवरण" }, "accountName": { - "message": "खाता का नाम" + "message": "खाते का नाम" }, "address": { "message": "खाते का पता" @@ -21,7 +21,7 @@ "message": "टोकन जोड़ें" }, "addTokens": { - "message": "टोकनो को जोड़ें" + "message": "टोकनों को जोड़ें" }, "amount": { "message": "राशि" @@ -30,7 +30,7 @@ "message": "राशि + गैस" }, "appDescription": { - "message": "एथरेम ब्राउज़र एक्सटेंशन", + "message": "इथीरियम ब्राउज़र एक्सटेंशन", "description": "आवेदन का विवरण" }, "appName": { @@ -53,7 +53,7 @@ "message": "उपलब्ध बैलेंस।" }, "balances": { - "message": "ापके उपलब्ध बैलेंस" + "message": "आपके उपलब्ध बैलेंस" }, "balanceIsInsufficientGas": { "message": "वर्तमान गैस कुल के लिए अपर्याप्त शेष" @@ -78,10 +78,10 @@ "message": "खरीदें" }, "buyCoinbase": { - "message": "कॉनबेस पर खरीदें" + "message": "कॉइनबेस पर खरीदें" }, "buyCoinbaseExplainer": { - "message": "बिल्टकोइन, एथरेम और लाइटकोइन खरीदने और बेचने के लिए दुनिया का सबसे लोकप्रिय तरीका Coinbase है।" + "message": "बिल्टकोइन, इथीरियम और लाइटकोइन खरीदने और बेचने के लिए दुनिया का सबसे लोकप्रिय तरीका कॉइनबेस है।" }, "cancel": { "message": "रद्द करें" @@ -108,7 +108,7 @@ "message": "जारी रखें" }, "continueToCoinbase": { - "message": "कॉ्ोनबेस को ब्हेजना जारी रखें" + "message": "कॉइनबेस को ब्हेजना जारी रखें" }, "contractDeployment": { "message": "अनुबंध परिनियोजन व तैनाती" @@ -435,13 +435,13 @@ "message": "बीज शब्द में केवल लोअरकेस वर्ण होते हैं" }, "mainnet": { - "message": "मुख्य ईथरम नेटवर्क" + "message": "मुख्य इथीरियम नेटवर्क" }, "message": { "message": "संदेश" }, "metamaskDescription": { - "message": "मेटामास्क एथर्मम के लिए एक सुरक्षित पहचान वॉल्ट है।" + "message": "मेटामास्क इथीरियम के लिए एक सुरक्षित पहचान वॉल्ट है।" }, "min": { "message": "न्यूनतम" @@ -649,7 +649,7 @@ "message": "भेजें टोकन" }, "sendTokensAnywhere": { - "message": "इटोरम खाते वाले किसी को भी टोकन भेजें" + "message": "इथीरियम खाते वाले किसी को भी टोकन भेजें" }, "settings": { "message": "सेटिंग्स" diff --git a/app/_locales/index.json b/app/_locales/index.json index 727504bda..7717502b7 100644 --- a/app/_locales/index.json +++ b/app/_locales/index.json @@ -1,4 +1,5 @@ [ + { "code": "cs", "name": "Czech" }, { "code": "de", "name": "German" }, { "code": "en", "name": "English" }, { "code": "es", "name": "Spanish" }, @@ -13,6 +14,7 @@ { "code": "ru", "name": "Russian" }, { "code": "sl", "name": "Slovenian" }, { "code": "th", "name": "Thai" }, + { "code": "tml", "name": "Tamil" }, { "code": "tr", "name": "Turkish" }, { "code": "vi", "name": "Vietnamese" }, { "code": "zh_CN", "name": "Mandarin" }, diff --git a/app/_locales/tml/messages.json b/app/_locales/tml/messages.json new file mode 100644 index 000000000..fcc418bac --- /dev/null +++ b/app/_locales/tml/messages.json @@ -0,0 +1,912 @@ +{ + "accept": { + "message": "ஏற்கவும்" + }, + "account": { + "message": "கணக்கு" + }, + "accountDetails": { + "message": "கணக்கு விவரங்கள்" + }, + "accountName": { + "message": "கணக்கின் பெயர்" + }, + "address": { + "message": "முகவரி" + }, + "addCustomToken": { + "message": "தனிப்பயன் டோக்கனைச் சேர்க்கவும்" + }, + "addToken": { + "message": "டோக்கனைச் சேர்" + }, + "addTokens": { + "message": "டோக்கன்களைச் சேர்" + }, + "amount": { + "message": "தொகை" + }, + "amountPlusGas": { + "message": "தொகை + எரிவாயு" + }, + "appDescription": { + "message": "எதெரியும் பிரௌசர் நீட்டிப்பு", + "description": "பயன்பாட்டின் விளக்கம்" + }, + "appName": { + "message": "மேடமஸ்க் ", + "description": "பயன்பாட்டின் பெயர்" + }, + "approved": { + "message": "அங்கீகரிக்கப்பட்ட" + }, + "attemptingConnect": { + "message": "இணைக்க முயற்சி செய்க ப்ளாக்சைன்" + }, + "attributions": { + "message": "பண்புகளும்" + }, + "available": { + "message": "கிடைக்கும்" + }, + "back": { + "message": "மீண்டும்" + }, + "balance": { + "message": "இருப்பு:" + }, + "balances": { + "message": "உங்கள் இருப்பு" + }, + "balanceIsInsufficientGas": { + "message": "நடப்பு வாயு மொத்தம் போதுமான சமநிலை" + }, + "beta": { + "message": "பீட்டா" + }, + "betweenMinAndMax": { + "message": "$ 1 க்கும் அதிகமாகவும் அல்லது $ 2 க்கு சமமாகவும் இருக்க வேண்டும்.", + "description": "ஹெக்ஸ் உள்ளீடு தசம உள்ளீடு என உதவி" + }, + "blockiesIdenticon": { + "message": "ப்ளாக்கிஸ் ஐடென்டிகோன் பயன்பாட்டு" + }, + "borrowDharma": { + "message": "தர்மத்துடன் கடன் வாங்குங்கள் (பீட்டா)" + }, + "builtInCalifornia": { + "message": "மேடமஸ்க் வடிவமைக்கப்பட்டு கலிபோர்னியாவில் கட்டப்பட்டுள்ளது." + }, + "buy": { + "message": "வாங்க" + }, + "buyCoinbase": { + "message": "கோஇன்பசே வாங்கவும்" + }, + "buyCoinbaseExplainer": { + "message": "கோஇன்பசே பிறகாய்ன் , எதெரியும் மற்றும் ளிட்டசோன் வாங்க மற்றும் விற்க உலகின் மிகவும் பிரபலமான வழி" + }, + "ok": { + "message": "சரி" + }, + "cancel": { + "message": "ரத்து" + }, + "classicInterface": { + "message": "கிளாசிக் இடைமுகத்தைப் பயன்படுத்தவும்" + }, + "clickCopy": { + "message": "நகலெடுக்க கிளிக் செய்யவும்" + }, + "confirm": { + "message": "உறுதிப்படுத்தவும்" + }, + "confirmed": { + "message": "உறுதி" + }, + "confirmContract": { + "message": "ஒப்பந்தத்தை உறுதிப்படுத்துக" + }, + "confirmPassword": { + "message": "கடவுச்சொல்லை உறுதிப்படுத்துக" + }, + "confirmTransaction": { + "message": "பரிவர்த்தனை உறுதிபடுத்தவும்" + }, + "continue": { + "message": "தொடர்ந்து" + }, + "continueToCoinbase": { + "message": "கோஇன்பசே ஐத் தொடரவும்" + }, + "contractDeployment": { + "message": "ஒப்பந்த வரிசைப்படுத்தல்" + }, + "conversionProgress": { + "message": "மாற்றம் முன்னேற்றம்" + }, + "copiedButton": { + "message": "நகலெடுக்கப்பட்டன" + }, + "copiedClipboard": { + "message": "கிளிப்போர்டுக்கு நகலெடுக்கப்பட்டது" + }, + "copiedExclamation": { + "message": "நகலெடுக்கப்பட்டன!" + }, + "copiedSafe": { + "message": "நான் எங்காவது பாதுகாப்பாக நகலெடுத்திருக்கிறேன்" + }, + "copy": { + "message": "நகல்" + }, + "copyToClipboard": { + "message": "கிளிப்போர்டுக்கு நகலெடுக்கப்பட்டது" + }, + "copyButton": { + "message": " நகல் " + }, + "copyPrivateKey": { + "message": "இது உங்கள் தனிப்பட்ட விசை (நகலெடுக்க கிளிக் செய்யவும்)" + }, + "create": { + "message": "உருவாக்கவும்" + }, + "createAccount": { + "message": "உங்கள் கணக்கை துவங்குங்கள்" + }, + "createDen": { + "message": "உருவாக்கவும்" + }, + "crypto": { + "message": "கிரிப்டோ", + "description": "பரிமாற்ற வகை (கிரிப்டோசுர்ரென்சிஸ்)" + }, + "currentConversion": { + "message": "தற்போதைய மாற்றம்" + }, + "currentNetwork": { + "message": "தற்போதைய நெட்வொர்க்" + }, + "customGas": { + "message": "எரிவாயுவைத் தனிப்பயனாக்குங்கள்" + }, + "customToken": { + "message": "தனிப்பயன் டோக்கன்" + }, + "customize": { + "message": "தனிப்பயனாக்கலாம்" + }, + "customRPC": { + "message": "விருப்ப RPC ஐ" + }, + "decimalsMustZerotoTen": { + "message": "தசமங்கள் குறைந்தபட்சம் 0, மற்றும் 36 க்கு மேல் இருக்க வேண்டும்." + }, + "decimal": { + "message": "துல்லியத்தின் முடிவு" + }, + "defaultNetwork": { + "message": "எதிர் பரிவர்த்தனைகளுக்கான முன்னிருப்பு வலையமைப்பு முதன்மை நிகரமாகும்." + }, + "denExplainer": { + "message": "உங்கள் DEN என்பது உங்கள் கடவுச்சொல்-மறைகுறியாக்கப்பட்ட சேமிப்பகம் மெட்டாமாஸ்க்கிற்குள்." + }, + "deposit": { + "message": "வைப்புத்தொகை" + }, + "depositBTC": { + "message": "கீழே உங்கள் முகவரிக்கு உங்கள் BTC வைப்போம்:" + }, + "depositCoin": { + "message": "உங்கள் முகவரிக்கு $ 1 ஐ கீழே உள்ளிடவும்", + "description": "சேபஷிபிட் உடன் வைப்புக்குத் தேர்ந்தெடுக்கப்பட்ட நாணயத்தை பயனரிடம் கூறுகிறார்" + }, + "depositEth": { + "message": "வைப்புத்தொகை எது " + }, + "depositEther": { + "message": "வைப்புத்தொகை எதிர் " + }, + "depositFiat": { + "message": "ஃபியட் உடன் வைப்பு" + }, + "depositFromAccount": { + "message": "மற்றொரு கணக்கிலிருந்து வைப்பு" + }, + "depositShapeShift": { + "message": "ShapeShift உடன் வைப்பு" + }, + "depositShapeShiftExplainer": { + "message": "நீங்கள் மற்ற கிரிப்டோகிராரன்கள் சொந்தமாக வைத்திருந்தால், உங்கள் மெட்டாமாஸ்க் பணப்பையில் நேரடியாக ஈதரை வர்த்தகம் செய்யலாம் மற்றும் வைப்பு செய்யலாம். கணக்கு தேவையில்லை." + }, + "details": { + "message": "விவரங்கள்" + }, + "directDeposit": { + "message": "நேரடி வைப்பு" + }, + "directDepositEther": { + "message": "நேரடியாக வைப்புத்தொகை" + }, + "directDepositEtherExplainer": { + "message": "நீங்கள் ஏற்கனவே ஏதெர் இருந்தால், நேரடி வைப்பு மூலம் உங்கள் புதிய பணப்பையில் ஈத்தர் பெற விரைவான வழி." + }, + "done": { + "message": "முடிந்தது" + }, + "downloadStateLogs": { + "message": "மாநில பதிவுகள் பதிவிறக்க" + }, + "dropped": { + "message": "நீக்கப்பட்டார்" + }, + "edit": { + "message": "தொகு" + }, + "editAccountName": { + "message": "கணக்கு பெயரை மாற்றுக" + }, + "emailUs": { + "message": "எங்களுக்கு மின்னஞ்சல்!" + }, + "encryptNewDen": { + "message": "உங்கள் புதிய DEN ஐ குறியாக்குக" + }, + "enterPassword": { + "message": "கடவுச்சொல்லை உள்ளிடவும்" + }, + "enterPasswordConfirm": { + "message": "உறுதிப்படுத்த உங்கள் கடவுச்சொல்லை உள்ளிடவும்" + }, + "passwordNotLongEnough": { + "message": "கடவுச்சொல் போதாது" + }, + "passwordsDontMatch": { + "message": "கடவுச்சொற்கள் பொருந்தாதே" + }, + "etherscanView": { + "message": "Etherscan கணக்கைப் பார்க்கவும்" + }, + "exchangeRate": { + "message": "மாற்று விகிதம்" + }, + "exportPrivateKey": { + "message": "தனியார் விசை ஐ ஏற்றுமதி செய்க" + }, + "exportPrivateKeyWarning": { + "message": "தனிப்பட்ட விசைகளை உங்கள் சொந்த ஆபத்தில் ஏற்றுமதி செய்யுங்கள்." + }, + "failed": { + "message": "தோல்வி" + }, + "fiat": { + "message": "FIAT", + "description": "பரிமாற்ற வகை" + }, + "fileImportFail": { + "message": "கோப்பு இறக்குமதி வேலை செய்யவில்லையா? இங்கே கிளிக் செய்யவும்!", + "description": "JSON கோப்பில் பயனர் கணக்கை தங்கள் கணக்கை இறக்குமதி செய்ய உதவுகிறது" + }, + "followTwitter": { + "message": "Twitter இல் எங்களைப் பின்தொடரவும்" + }, + "from": { + "message": "இருந்து" + }, + "fromToSame": { + "message": "இருந்து மற்றும் முகவரி அதே இருக்க முடியாது" + }, + "fromShapeShift": { + "message": "ShapeShift இலிருந்து" + }, + "gas": { + "message": "எரிவாயு", + "description": "எரிவாயு விலை குறையும்" + }, + "gasFee": { + "message": "எரிவாயு கட்டணம்" + }, + "gasLimit": { + "message": "எரிவாயு வரம்பு" + }, + "gasLimitCalculation": { + "message": "நெட்வொர்க் வெற்றி விகிதங்களின் அடிப்படையில் பரிந்துரைக்கப்பட்ட எரிவாயு வரம்பை நாங்கள் கணக்கிடுகிறோம்." + }, + "gasLimitRequired": { + "message": "எரிவாயு வரம்பு தேவை" + }, + "gasLimitTooLow": { + "message": "எரிவாயு வரம்பு குறைந்தது 21000 ஆக இருக்க வேண்டும்" + }, + "generatingSeed": { + "message": "விதை உருவாக்குகிறது ..." + }, + "gasPrice": { + "message": "எரிவாயு விலை (GWEI)" + }, + "gasPriceCalculation": { + "message": "நெட்வொர்க் வெற்றி விகிதங்களின் அடிப்படையில் பரிந்துரைக்கப்பட்ட எரிவாயு விலைகளை நாங்கள் கணக்கிடுகிறோம்." + }, + "gasPriceRequired": { + "message": "எரிவாயு விலை தேவைப்படுகிறது" + }, + "getEther": { + "message": "ஈத்தர் கிடைக்கும்" + }, + "getEtherFromFaucet": { + "message": "$ 1 க்கு ஒரு குழாய் இருந்து ஈதர் கிடைக்கும்$1", + "description": "ஈத்தர் குழாய் ஐந்து பிணைய பெயர் காட்டுகிறது" + }, + "greaterThanMin": { + "message": "$ 1 க்கும் அதிகமாகவோ அல்லது சமமாகவோ இருக்க வேண்டும்", + "description": "ஹெக்ஸ் உள்ளீடு தசம உள்ளீடு என உதவி" + }, + "here": { + "message": "இங்கே", + "description": "இங்கே-கிளிக் செய்யவும்- மேலும் தகவலுக்கு (troubleTokenBalances செல்கிறது)" + }, + "hereList": { + "message": "இங்கே ஒரு பட்டியல் !!!!" + }, + "hide": { + "message": "மறை" + }, + "hideToken": { + "message": "டோக்கனை மறை" + }, + "hideTokenPrompt": { + "message": "டோக்கனை மறை?" + }, + "howToDeposit": { + "message": "எப்படி ஈத்தர் வைப்பது?" + }, + "holdEther": { + "message": "இது நீங்கள் ஈத்தர் மற்றும் டோக்கன்களை வைத்திருக்க உதவுகிறது, மற்றும் பரவலாக்கப்பட்ட பயன்பாடுகளுக்கு உங்கள் பாலமாக செயல்படுகிறது." + }, + "import": { + "message": "இறக்குமதி", + "description": "தேர்ந்தெடுக்கப்பட்ட கோப்பிலிருந்து ஒரு கணக்கை இறக்குமதி செய்ய பொத்தானை அழுத்தவும்" + }, + "importAccount": { + "message": "கணக்கை இறக்குமதி செய்க" + }, + "importAccountMsg": { + "message":" இறக்குமதி செய்யப்பட்ட கணக்கு உங்கள் முதலில் உருவாக்கப்பட்ட மெட்டாமாஸ்க் கணக்கு விதை மூலம் தொடர்புடையதாக இருக்காது. இறக்குமதி செய்யப்பட்ட கணக்குகள் பற்றி மேலும் அறிக " + }, + "importAnAccount": { + "message": "ஒரு கணக்கை இறக்குமதி செய்க" + }, + "importDen": { + "message": "இறக்குமதி DEN இறக்குமதி" + }, + "imported": { + "message": "இறக்குமதி", + "description": "ஒரு கணக்கு முழுமையாக விசைப்பலகையில் ஏற்றப்பட்டதைக் காட்டுகிறது" + }, + "infoHelp": { + "message": "தகவல் மற்றும் உதவி" + }, + "insufficientFunds": { + "message": "போதுமான பணம் இல்லை." + }, + "insufficientTokens": { + "message": "போதுமான டோக்கன்கள்." + }, + "invalidAddress": { + "message": "தவறான முகவரி" + }, + "invalidAddressRecipient": { + "message": "பெறுநர் முகவரி தவறானது" + }, + "invalidGasParams": { + "message": "தவறான எரிவாயு அளவுருக்கள்" + }, + "invalidInput": { + "message": "தவறான உள்ளீடு.." + }, + "invalidRequest": { + "message": "தவறான கோரிக்கை" + }, + "invalidRPC": { + "message": "தவறான RPC URI" + }, + "jsonFail": { + "message": "ஏதோ தவறு நடந்துவிட்டது. உங்கள் JSON கோப்பு ஒழுங்காக வடிவமைக்கப்பட்டுள்ளது என்பதை உறுதிப்படுத்தவும்" + }, + "jsonFile": { + "message": "JSON கோப்பு", + "description": "ஒரு கணக்கை இறக்குமதி செய்ய வடிவமைக்கப்பட்டுள்ளது" + }, + "keepTrackTokens": { + "message": "உங்கள் மேடமஸ்க் கணக்குடன் நீங்கள் வாங்கிய டோக்கன்களை கண்காணியுங்கள்." + }, + "kovan": { + "message": "கோவன் டெஸ்ட் நெட்வொர்க்" + }, + "knowledgeDataBase": { + "message": "எங்கள் அறிவுத் தளத்தைப் பார்வையிடவும்" + }, + "max": { + "message": "மேக்ஸ்" + }, + "learnMore": { + "message": "மேலும் அறிக" + }, + "lessThanMax": { + "message": "$ 1 க்கும் குறைவாகவோ அல்லது சமமாகவோ இருக்க வேண்டும்.", + "description": "ஹெக்ஸ் உள்ளீடு தசம உள்ளீடு என உதவி" + }, + "likeToAddTokens": { + "message": "இந்த டோக்கன்களைச் சேர்க்க விரும்புகிறீர்களா?" + }, + "links": { + "message": "இணைப்புகள்" + }, + "limit": { + "message": "அளவு" + }, + "loading": { + "message": "ஏற்றுதல் ..." + }, + "loadingTokens": { + "message": "டோக்கன்களை ஏற்றுகிறது ..." + }, + "localhost": { + "message": "லோக்கல் ஹோஸ்ட் 8545" + }, + "login": { + "message": "உள் நுழை" + }, + "logout": { + "message": "வெளியேறு" + }, + "loose": { + "message": "லூஸ்" + }, + "loweCaseWords": { + "message": "விதை வார்த்தைகள் ஸ்மால் எழுத்துகள் மட்டுமே" + }, + "mainnet": { + "message": "முதன்மை எதெரியும் நெட்வொர்க்" + }, + "message": { + "message": "செய்தி" + }, + "metamaskDescription": { + "message": "மேடமஸ்க் என்பது ஒரு பாதுகாப்பான அடையாள வால்ட் எதெரியும்" + }, + "min": { + "message": "குறைந்தபட்ச" + }, + "myAccounts": { + "message": "எனது கணக்குகள்" + }, + "mustSelectOne": { + "message": "குறைந்தது 1 டோக்கனை தேர்ந்தெடுக்க வேண்டும்." + }, + "needEtherInWallet": { + "message": "மேடமஸ்க் ஐ பயன்படுத்தி பரவலாக்கப்பட்ட பயன்பாடுகளுடன் தொடர்பு கொள்ள, உங்கள் பணப்பரிமாற்றத்தில் ஈதர் தேவை." + }, + "needImportFile": { + "message": "இறக்குமதி செய்ய ஒரு கோப்பை நீங்கள் தேர்ந்தெடுக்க வேண்டும்.", + "description": "பயனர் ஒரு கணக்கு முக்கியம் மற்றும் தொடர ஒரு கோப்பு சேர்க்க வேண்டும்" + }, + "needImportPassword": { + "message": "நீங்கள் தேர்ந்தெடுத்த கோப்புக்கு ஒரு கடவுச்சொல்லை உள்ளிட வேண்டும்.", + "description": "ஒரு கணக்கை இறக்குமதி செய்ய கடவுச்சொல் மற்றும் கோப்பு தேவை" + }, + "negativeETH": { + "message": "ETH எதிர்மறை அளவுகளை அனுப்ப முடியாது." + }, + "networks": { + "message": "நெட்வொர்க்ஸ்" + }, + "newAccount": { + "message": "புதிய கணக்கு" + }, + "newAccountNumberName": { + "message": "கணக்கு $ 1", + "description": "கணக்கு கணக்கை உருவாக்குவதற்கு அடுத்த கணக்கின் இயல்புநிலை பெயர் உருவாக்கப்படும்" + }, + "newContract": { + "message": "புதிய ஒப்பந்தம்" + }, + "newPassword": { + "message": "புதிய கடவுச்சொல் (min 8 எழுத்துகள்)" + }, + "newRecipient": { + "message": "புதிய பெறுநர்" + }, + "newRPC": { + "message": "புதிய RPC URL" + }, + "next": { + "message": "அடுத்த" + }, + "noAddressForName": { + "message": "இந்த பெயருக்கான முகவரி அமைக்கப்படவில்லை." + }, + "noDeposits": { + "message": "எந்த வைப்புகளும் கிடைக்கவில்லை" + }, + "noTransactionHistory": { + "message": "பரிவர்த்தனை வரலாறு இல்லை." + }, + "noTransactions": { + "message": "பரிவர்த்தனைகள் இல்லை" + }, + "notStarted": { + "message": "துவங்கவில்லை" + }, + "oldUI": { + "message": "பழைய UI" + }, + "oldUIMessage": { + "message": "நீங்கள் பழைய UI க்கு திரும்பியுள்ளீர்கள். மேல் வலது கீழ்தோன்றும் மெனுவில் உள்ள விருப்பத்தின் மூலம் புதிய UI ஐ மீண்டும் மாறலாம்." + }, + "or": { + "message": "அல்லது", + "description": "ஒரு புதிய கணக்கை உருவாக்க அல்லது இறக்குமதி செய்வதற்கு இடையே தேர்வு" + }, + "passwordCorrect": { + "message": "தயவுசெய்து உங்கள் கடவுச்சொல் சரியானதா என உறுதிப்படுத்தவும்." + }, + "passwordMismatch": { + "message": "கடவுச்சொற்கள் பொருந்தவில்லை", + "description": "கடவுச்சொல் உருவாக்கத்தில், இரண்டு புதிய கடவுச்சொல் புலங்கள் பொருந்தவில்லை" + }, + "passwordShort": { + "message": "கடவுச்சொல் நீண்ட காலமாக இல்லை", + "description": "கடவுச்சொல் உருவாக்கத்தில், பாதுகாப்பானதாக இருக்கும் கடவுச்சொல் போதும்" + }, + "pastePrivateKey": { + "message": "இங்கே உங்கள் தனிப்பட்ட விசை சரத்தை ஒட்டுக:", + "description": "ஒரு தனிப்பட்ட விசை ஒரு கணக்கை இறக்குமதி செய்ய" + }, + "pasteSeed": { + "message": "இங்கே உங்கள் விதை சொற்றொடரை ஒட்டவும்!" + }, + "personalAddressDetected": { + "message": "தனிப்பட்ட முகவரி கண்டறியப்பட்டது. டோக்கன் ஒப்பந்த முகவரியை உள்ளிடவும்." + }, + "pleaseReviewTransaction": { + "message": "உங்கள் பரிவர்த்தனை மதிப்பாய்வு செய்யவும்." + }, + "popularTokens": { + "message": "பிரபலமான டோக்கன்கள்" + }, + "privacyMsg": { + "message": "தனியுரிமை கொள்கை" + }, + "privateKey": { + "message": "தனிப்பட்ட விசை", + "description": "ஒரு கணக்கை இறக்குமதி செய்ய பயன்படுத்த இந்த வகை கோப்பை தேர்ந்தெடுக்கவும்" + }, + "privateKeyWarning": { + "message": "எச்சரிக்கை: இந்த விசையை எப்போதும் வெளியிட வேண்டாம். உங்கள் தனிப்பட்ட விசைகளைக் கொண்ட எவரும் உங்கள் கணக்கில் உள்ள எந்த சொத்துக்களையும் திருடலாம்." + }, + "privateNetwork": { + "message": "தனியார் நெட்வொர்க்" + }, + "qrCode": { + "message": "QR குறியீட்டைக் காட்டு" + }, + "readdToken": { + "message": "உங்கள் கணக்கு விருப்பங்கள் மெனுவில் \"டோக்கனைச் சேர்\" என்பதன் மூலம் நீங்கள் எதிர்காலத்தில் இந்த டோக்கனை மீண்டும் சேர்க்கலாம்." + }, + "readMore": { + "message": "மேலும் வாசிக்க இங்கே." + }, + "readMore2": { + "message": "மேலும் வாசிக்க." + }, + "receive": { + "message": "பெறுக" + }, + "recipientAddress": { + "message": "பெறுநர் முகவரி" + }, + "refundAddress": { + "message": "உங்கள் பணத்தை திருப்பி அனுப்பும் முகவரி" + }, + "rejected": { + "message": "நிராகரிக்கப்பட்டது" + }, + "resetAccount": { + "message": "கணக்கை மீட்டமை" + }, + "restoreFromSeed": { + "message": "விதை வாக்கியத்திலிருந்து மீட்கவும்" + }, + "restoreVault": { + "message": "வால்ட் மீட்கவும்" + }, + "required": { + "message": "தேவையான" + }, + "retryWithMoreGas": { + "message": "இங்கே அதிக எரிவாயு விலை மீண்டும் முயற்சிக்கவும்" + }, + "walletSeed": { + "message": "வால்ட் விதை" + }, + "revealSeedWords": { + "message": "விதை வார்த்தைகள் வெளிப்படுத்த" + }, + "revealSeedWordsWarning": { + "message": "உங்கள் விதை வார்த்தைகள் ஒரு பொது இடத்தில் மீட்க வேண்டாம்! உங்கள் எல்லா கணக்குகளையும் திருட இந்த வார்த்தைகள் பயன்படுத்தப்படலாம்." + }, + "revert": { + "message": "மாற்றியமை" + }, + "rinkeby": { + "message": "ரிங்கெப்ய டெஸ்ட் நெட்வொர்க்" + }, + "ropsten": { + "message": "ரொப்ஸ்டென் டெஸ்ட் நெட்வொர்க்" + }, + "currentRpc": { + "message": "தற்போதைய RPC" + }, + "connectingToMainnet": { + "message": "முக்கிய எதெரியும் நெட்வொர்க் இணைக்கும்" + }, + "connectingToRopsten": { + "message": "ரொப்ஸ்டென் டெஸ்ட் நெட்வொர்க்குடன் இணைக்கிறது" + }, + "connectingToKovan": { + "message": "கோவன் டெஸ்ட் நெட்வொர்க்குடன் இணைத்தல்" + }, + "connectingToRinkeby": { + "message": "ரிங்கெப்ய டெஸ்ட் நெட்வொர்க்குடன் இணைக்கிறது" + }, + "connectingToUnknown": { + "message": "தெரியாத நெட்வொர்க்குடன் இணைக்கிறது" + }, + "sampleAccountName": { + "message": "உதாரணமாக எனது புதிய கணக்கு", + "description": "தங்கள் கணக்கில் மனிதர் படிக்கக்கூடிய பெயரைச் சேர்க்கும் கருத்தை பயனர் புரிந்து கொள்ள உதவுங்கள்" + }, + "save": { + "message": "சேமி" + }, + "reprice_title": { + "message": "ரெப்ரிஸ் பரிவர்த்தனை" + }, + "reprice_subtitle": { + "message": "உங்கள் பரிவர்த்தனைகளை மேலெழுதும் முயற்சியை அதிகரிக்க உங்கள் எரிவாயு விலையை அதிகரிக்கவும்" + }, + "saveAsFile": { + "message": "கோப்பாக சேமிக்கவும்", + "description": "கணக்கு ஏற்றுமதி செயல்முறை" + }, + "saveSeedAsFile": { + "message": "கோப்பு என விதை வார்த்தைகள் சேமிக்கவும்" + }, + "search": { + "message": "தேடல்" + }, + "secretPhrase": { + "message": "உங்கள் பெட்டகத்தை மீட்டெடுப்பதற்காக இங்கே உங்கள் ரகசிய பன்னிரண்டு வார்த்தை சொற்றொடரை உள்ளிடவும்." + }, + "newPassword8Chars": { + "message": "புதிய கடவுச்சொல் (குறைந்தபட்சம் 8 எழுத்துகள்)" + }, + "seedPhraseReq": { + "message": "விதை வாக்கியங்கள் 12 வார்த்தைகள் நீண்டவை" + }, + "select": { + "message": "தேர்வு" + }, + "selectCurrency": { + "message": "நாணயத்தைத் தேர்ந்தெடு" + }, + "selectService": { + "message": "சேவை தேர்ந்தெடுக்கவும்" + }, + "selectType": { + "message": "வகை தேர்ந்தெடு" + }, + "send": { + "message": "அனுப்பு" + }, + "sendETH": { + "message": "ETH ஐ அனுப்பு" + }, + "sendTokens": { + "message": "டோக்கன்களை அனுப்பவும்" + }, + "onlySendToEtherAddress": { + "message": "ETH ஐ ஒரு எதரியும் முகவரிக்கு மட்டும் அனுப்பவும்." + }, + "searchTokens": { + "message": "தேடல் டோக்கன்ஸ்" + }, + "sendTokensAnywhere": { + "message": "யாருடனும் டோக்கன்களை அனுப்பவும் எதெரியும் கணக்கு" + }, + "settings": { + "message": "அமைப்புகள்" + }, + "info": { + "message": "தகவல்" + }, + "shapeshiftBuy": { + "message": "Shapeshift உடன் வாங்கவும்" + }, + "showPrivateKeys": { + "message": "தனிப்பட்ட விசைகளைக் காண்பி" + }, + "showQRCode": { + "message": "QR குறியீட்டைக் காட்டு" + }, + "sign": { + "message": "உள்நுழை" + }, + "signed": { + "message": "கையொப்பமிடப்பட்ட" + }, + "signMessage": { + "message": "செய்தியை பதிவு செய்க" + }, + "signNotice": { + "message": "இந்த செய்தியில் கையொப்பமிடலாம் \nஆபத்தான பக்க விளைவுகள் இருக்கலாம். \n உங்கள் மொத்த கணக்கில் முழுமையாக நம்பக்கூடிய தளங்களில் செய்திகளை மட்டுமே கையொப்பமிடுங்கள். \n இந்த ஆபத்தான முறை எதிர்கால பதிப்பில் அகற்றப்படும்." + }, + "sigRequest": { + "message": "கையொப்பம் கோரிக்கை" + }, + "sigRequested": { + "message": "கையொப்பம் கோரப்பட்டது" + }, + "spaceBetween": { + "message": "வார்த்தைகள் இடையே இடைவெளி மட்டுமே இருக்க முடியும்" + }, + "status": { + "message": "நிலைமை" + }, + "stateLogs": { + "message": "மாநில பதிவுகள்" + }, + "stateLogsDescription": { + "message": "மாநில பதிவுகள் உங்கள் பொது கணக்கு முகவரிகள் மற்றும் பரிமாற்றங்களை அனுப்பியுள்ளன." + }, + "stateLogError": { + "message": "மாநில பதிவுகளை மீட்டெடுப்பதில் பிழை." + }, + "submit": { + "message": "சமர்ப்பி" + }, + "submitted": { + "message": "சமர்ப்பிக்கப்பட்டது" + }, + "supportCenter": { + "message": "எங்கள் ஆதரவு மையத்தைப் பார்வையிடவும்" + }, + "symbolBetweenZeroTen": { + "message": "குறியீடு 0 மற்றும் 10 எழுத்துகளுக்கு இடையில் இருக்க வேண்டும்." + }, + "takesTooLong": { + "message": "நீண்ட நேரம் எடுத்துக்கொள்கிறது?" + }, + "terms": { + "message": "பயன்பாட்டு விதிமுறைகளை" + }, + "testFaucet": { + "message": "சோதனை குழாய்" + }, + "to": { + "message": "பெறுநர்: " + }, + "toETHviaShapeShift": { + "message": "$ 1 முதல் ETH வரை வடிவம்", + "description": "செய்தி தொடக்கத்தில் வைப்பு வகைகளில் நிரப்பப்படும்" + }, + "tokenAddress": { + "message": "டோக்கன் முகவரி" + }, + "tokenAlreadyAdded": { + "message": "டோக்கன் ஏற்கனவே சேர்க்கப்பட்டது." + }, + "tokenBalance": { + "message": "உங்கள் டோக்கன் இருப்பு:" + }, + "tokenSelection": { + "message": "டோக்கன்களைத் தேடு அல்லது பிரபல டோக்கன்களின் பட்டியலிலிருந்து தேர்ந்தெடுக்கவும்." + }, + "tokenSymbol": { + "message": "டோக்கன் சின்னம்" + }, + "tokenWarning1": { + "message": "உங்கள் மேடமஸ்க் கணக்குடன் நீங்கள் வாங்கிய டோக்கன்களை கண்காணியுங்கள். வேறு கணக்கைப் பயன்படுத்தி டோக்கன்களை வாங்கிவிட்டால், அந்த டோக்கன்கள் இங்கே தோன்றாது." + }, + "total": { + "message": "மொத்த" + }, + "transactions": { + "message": "பரிவர்த்தனைகள்" + }, + "transactionError": { + "message": "பரிவர்த்தனை பிழை. விதிமுறை ஒப்பந்தத்தில் விதிவிலக்கு." + }, + "transactionMemo": { + "message": "பரிவர்த்தனை குறிப்பு (விருப்பம்)" + }, + "transactionNumber": { + "message": "பரிவர்த்தனை எண்" + }, + "transfers": { + "message": "இடமாற்றங்கள்" + }, + "troubleTokenBalances": { + "message": "உங்கள் டோக்கன் நிலுவைகளை ஏற்றுவதில் சிக்கல் ஏற்பட்டது. நீங்கள் அவர்களை பார்க்க முடியும்.", + "description": "டோக்கன் நிலுவைகளை காண ஒரு இணைப்பு (இங்கே) தொடர்ந்து" + }, + "twelveWords": { + "message": "இந்த 12 வார்த்தைகள் உங்கள் மெட்டாமாஸ்க் கணக்கை மீட்க ஒரே வழி. \n அவற்றை எங்காவது பாதுகாப்பாகவும் ரகசியமாகவும் சேமிக்கவும்." + }, + "typePassword": { + "message": "உங்கள் கடவுச்சொல்லை தட்டச்சு செய்யவும்" + }, + "uiWelcome": { + "message": "புதிய UI (பீட்டா) க்கு வரவேற்கிறோம்" + }, + "uiWelcomeMessage": { + "message": "இப்போது நீங்கள் புதிய மெட்டாமாஸ்க்கு UI ஐ பயன்படுத்துகிறீர்கள். சுற்றி பாருங்கள், டோக்கன்களை அனுப்பும் புதிய அம்சங்களை முயற்சிக்கவும், உங்களிடம் ஏதேனும் சிக்கல் இருந்தால் எங்களுக்குத் தெரியப்படுத்தவும்." + }, + "unapproved": { + "message": "அங்கீகரிக்கப்படாத" + }, + "unavailable": { + "message": "கிடைக்கவில்லை" + }, + "unknown": { + "message": "தெரியாத" + }, + "unknownNetwork": { + "message": "அறியப்படாத தனியார் நெட்வொர்க்" + }, + "unknownNetworkId": { + "message": "தெரியாத நெட்வொர்க் ஐடி" + }, + "uriErrorMsg": { + "message": "URI கள் சரியான HTTP / HTTPS முன்னொட்டு தேவை." + }, + "usaOnly": { + "message": "அமெரிக்கா மட்டும்", + "description": "இந்த பரிமாற்றத்தைப் பயன்படுத்தி அமெரிக்காவில் உள்ளவர்களுக்கு மட்டுமே இது வரையறுக்கப்படுகிறது" + }, + "usedByClients": { + "message": "பல்வேறு வாடிக்கையாளர்கள் பல்வேறு பயன்படுத்திய" + }, + "useOldUI": { + "message": "உஸ் ஓல்ட் உய் " + }, + "validFileImport": { + "message": "இறக்குமதி செய்ய சரியான கோப்பு தேர்ந்தெடுக்க வேண்டும்." + }, + "vaultCreated": { + "message": "வால்ட் உருவாக்கப்பட்டது" + }, + "viewAccount": { + "message": "கணக்கைக் காட்டு" + }, + "visitWebSite": { + "message": "எங்கள் வலைத்தளத்தைப் பார்வையிடவும்" + }, + "warning": { + "message": "எச்சரிக்கை" + }, + "welcomeBeta": { + "message": "மெட்டாமாஸ்க் பீட்டாவுக்கு வருக" + }, + "whatsThis": { + "message": "இது என்ன?" + }, + "yourSigRequested": { + "message": "உங்கள் கையொப்பம் கோரப்படுகிறது" + }, + "youSign": { + "message": "நீங்கள் கையெழுத்திடுகிறீர்கள்" + } +} diff --git a/app/scripts/background.js b/app/scripts/background.js index 5878cd2e8..6550e8944 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -21,12 +21,16 @@ const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics') const EdgeEncryptor = require('./edge-encryptor') const getFirstPreferredLangCode = require('./lib/get-first-preferred-lang-code') const getObjStructure = require('./lib/getObjStructure') +const { + ENVIRONMENT_TYPE_POPUP, + ENVIRONMENT_TYPE_NOTIFICATION, + ENVIRONMENT_TYPE_FULLSCREEN, +} = require('./lib/enums') const STORAGE_KEY = 'metamask-config' const METAMASK_DEBUG = process.env.METAMASK_DEBUG -window.log = log -log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn') +log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn') const platform = new ExtensionPlatform() const notificationManager = new NotificationManager() @@ -44,7 +48,7 @@ const isEdge = !isIE && !!window.StyleMedia let popupIsOpen = false let notificationIsOpen = false -let openMetamaskTabsIDs = {} +const openMetamaskTabsIDs = {} // state persistence const diskStore = new LocalStorageStore({ storageKey: STORAGE_KEY }) @@ -173,7 +177,7 @@ function setupController (initState, initLangCode) { return versionedData } - function persistData(state) { + function persistData (state) { if (!state) { throw new Error('MetaMask - updated state is missing', state) } @@ -192,30 +196,53 @@ function setupController (initState, initLangCode) { // // connect to other contexts // - extension.runtime.onConnect.addListener(connectRemote) + + const metamaskInternalProcessHash = { + [ENVIRONMENT_TYPE_POPUP]: true, + [ENVIRONMENT_TYPE_NOTIFICATION]: true, + [ENVIRONMENT_TYPE_FULLSCREEN]: true, + } + + const isClientOpenStatus = () => { + return popupIsOpen || Boolean(Object.keys(openMetamaskTabsIDs).length) || notificationIsOpen + } + function connectRemote (remotePort) { - const isMetaMaskInternalProcess = remotePort.name === 'popup' || remotePort.name === 'notification' + const processName = remotePort.name + const isMetaMaskInternalProcess = metamaskInternalProcessHash[processName] const portStream = new PortStream(remotePort) + if (isMetaMaskInternalProcess) { // communication with popup - popupIsOpen = popupIsOpen || (remotePort.name === 'popup') + controller.isClientOpen = true controller.setupTrustedCommunication(portStream, 'MetaMask') - // record popup as closed - if (remotePort.sender.url.match(/home.html$/)) { - openMetamaskTabsIDs[remotePort.sender.tab.id] = true - } - if (remotePort.name === 'popup') { + + if (processName === ENVIRONMENT_TYPE_POPUP) { + popupIsOpen = true + endOfStream(portStream, () => { popupIsOpen = false - if (remotePort.sender.url.match(/home.html$/)) { - openMetamaskTabsIDs[remotePort.sender.tab.id] = false - } + controller.isClientOpen = isClientOpenStatus() }) } - if (remotePort.name === 'notification') { + + if (processName === ENVIRONMENT_TYPE_NOTIFICATION) { + notificationIsOpen = true + endOfStream(portStream, () => { notificationIsOpen = false + controller.isClientOpen = isClientOpenStatus() + }) + } + + if (processName === ENVIRONMENT_TYPE_FULLSCREEN) { + const tabId = remotePort.sender.tab.id + openMetamaskTabsIDs[tabId] = true + + endOfStream(portStream, () => { + delete openMetamaskTabsIDs[tabId] + controller.isClientOpen = isClientOpenStatus() }) } } else { @@ -258,10 +285,11 @@ function setupController (initState, initLangCode) { // popup trigger function triggerUi () { - extension.tabs.query({ active: true }, (tabs) => { - const currentlyActiveMetamaskTab = tabs.find(tab => openMetamaskTabsIDs[tab.id]) - if (!popupIsOpen && !currentlyActiveMetamaskTab && !notificationIsOpen) notificationManager.showPopup() - notificationIsOpen = true + extension.tabs.query({ active: true }, tabs => { + const currentlyActiveMetamaskTab = Boolean(tabs.find(tab => openMetamaskTabsIDs[tab.id])) + if (!popupIsOpen && !currentlyActiveMetamaskTab && !notificationIsOpen) { + notificationManager.showPopup() + } }) } diff --git a/app/scripts/controllers/currency.js b/app/scripts/controllers/currency.js index c23c7f616..480c08b1c 100644 --- a/app/scripts/controllers/currency.js +++ b/app/scripts/controllers/currency.js @@ -1,17 +1,18 @@ const ObservableStore = require('obs-store') const extend = require('xtend') +const log = require('loglevel') // every ten minutes const POLLING_INTERVAL = 600000 class CurrencyController { - /** - * Controller responsible for managing data associated with the currently selected currency. - * + /** + * Controller responsible for managing data associated with the currently selected currency. + * * @typedef {Object} CurrencyController - * @param {object} opts Overrides the defaults for the initial state of this.store - * @property {array} opts.initState initializes the the state of the CurrencyController. Can contain an + * @param {object} opts Overrides the defaults for the initial state of this.store + * @property {array} opts.initState initializes the the state of the CurrencyController. Can contain an * currentCurrency, conversionRate and conversionDate properties * @property {string} currentCurrency A 2-4 character shorthand that describes a specific currency, currently * selected by the user @@ -20,8 +21,8 @@ class CurrencyController { * since midnight of January 1, 1970 * @property {number} conversionInterval The id of the interval created by the scheduleConversionInterval method. * Used to clear an existing interval on subsequent calls of that method. - * - */ + * + */ constructor (opts = {}) { const initState = extend({ currentCurrency: 'usd', @@ -35,22 +36,22 @@ class CurrencyController { // PUBLIC METHODS // - /** - * A getter for the currentCurrency property - * - * @returns {string} A 2-4 character shorthand that describes a specific currency, currently selected by the user - * - */ + /** + * A getter for the currentCurrency property + * + * @returns {string} A 2-4 character shorthand that describes a specific currency, currently selected by the user + * + */ getCurrentCurrency () { return this.store.getState().currentCurrency } - /** - * A setter for the currentCurrency property - * - * @param {string} currentCurrency The new currency to set as the currentCurrency in the store - * - */ + /** + * A setter for the currentCurrency property + * + * @param {string} currentCurrency The new currency to set as the currentCurrency in the store + * + */ setCurrentCurrency (currentCurrency) { this.store.updateState({ currentCurrency }) } @@ -117,12 +118,12 @@ class CurrencyController { } } - /** - * Creates a new poll, using setInterval, to periodically call updateConversionRate. The id of the interval is + /** + * Creates a new poll, using setInterval, to periodically call updateConversionRate. The id of the interval is * stored at the controller's conversionInterval property. If it is called and such an id already exists, the * previous interval is clear and a new one is created. - * - */ + * + */ scheduleConversionInterval () { if (this.conversionInterval) { clearInterval(this.conversionInterval) diff --git a/app/scripts/controllers/infura.js b/app/scripts/controllers/infura.js index c6b4c9de2..8f6dd837e 100644 --- a/app/scripts/controllers/infura.js +++ b/app/scripts/controllers/infura.js @@ -1,5 +1,6 @@ const ObservableStore = require('obs-store') const extend = require('xtend') +const log = require('loglevel') // every ten minutes const POLLING_INTERVAL = 10 * 60 * 1000 diff --git a/app/scripts/controllers/network.js b/app/scripts/controllers/network.js index 617456cd7..45574e673 100644 --- a/app/scripts/controllers/network.js +++ b/app/scripts/controllers/network.js @@ -9,6 +9,7 @@ const extend = require('xtend') const EthQuery = require('eth-query') const createEventEmitterProxy = require('../lib/events-proxy.js') const networkConfig = require('../config.js') +const log = require('loglevel') const { OLD_UI_NETWORK_TYPE, DEFAULT_RPC } = networkConfig.enums const INFURA_PROVIDER_TYPES = ['ropsten', 'rinkeby', 'kovan', 'mainnet'] diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index d54efb889..bdedde2fb 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -4,8 +4,8 @@ const extend = require('xtend') class PreferencesController { - /** - * + /** + * * @typedef {Object} PreferencesController * @param {object} opts Overrides the defaults for the initial state of this.store * @property {object} store The stored object containing a users preferences, stored in local storage @@ -18,7 +18,7 @@ class PreferencesController { * @property {string} store.currentLocale The preferred language locale key * @property {string} store.selectedAddress A hex string that matches the currently selected address in the app * - */ + */ constructor (opts = {}) { const initState = extend({ frequentRpcList: [], @@ -32,43 +32,43 @@ class PreferencesController { } // PUBLIC METHODS - /** - * Setter for the `useBlockie` property - * - * @param {boolean} val Whether or not the user prefers blockie indicators - * - */ + /** + * Setter for the `useBlockie` property + * + * @param {boolean} val Whether or not the user prefers blockie indicators + * + */ setUseBlockie (val) { this.store.updateState({ useBlockie: val }) } - /** - * Getter for the `useBlockie` property - * - * @returns {boolean} this.store.useBlockie - * - */ + /** + * Getter for the `useBlockie` property + * + * @returns {boolean} this.store.useBlockie + * + */ getUseBlockie () { return this.store.getState().useBlockie } - /** - * Setter for the `currentLocale` property + /** + * Setter for the `currentLocale` property * * @param {string} key he preferred language locale key - * - */ + * + */ setCurrentLocale (key) { this.store.updateState({ currentLocale: key }) } - /** - * Setter for the `selectedAddress` property - * - * @param {string} _address A new hex address for an account - * @returns {Promise<void>} Promise resolves with undefined - * - */ + /** + * Setter for the `selectedAddress` property + * + * @param {string} _address A new hex address for an account + * @returns {Promise<void>} Promise resolves with undefined + * + */ setSelectedAddress (_address) { return new Promise((resolve, reject) => { const address = normalizeAddress(_address) @@ -129,13 +129,13 @@ class PreferencesController { return Promise.resolve(tokens) } - /** - * Removes a specified token from the tokens array. - * - * @param {string} rawAddress Hex address of the token contract to remove. - * @returns {Promise<array> The new array of AddedToken objects - * - */ + /** + * Removes a specified token from the tokens array. + * + * @param {string} rawAddress Hex address of the token contract to remove. + * @returns {Promise<array>} The new array of AddedToken objects + * + */ removeToken (rawAddress) { const tokens = this.store.getState().tokens @@ -145,23 +145,23 @@ class PreferencesController { return Promise.resolve(updatedTokens) } - /** - * A getter for the `tokens` property - * - * @returns {array} The current array of AddedToken objects - * - */ + /** + * A getter for the `tokens` property + * + * @returns {array} The current array of AddedToken objects + * + */ getTokens () { return this.store.getState().tokens } - /** - * Gets an updated rpc list from this.addToFrequentRpcList() and sets the `frequentRpcList` to this update list. - * - * @param {string} _url The the new rpc url to add to the updated list - * @returns {Promise<void>} Promise resolves with undefined - * - */ + /** + * Gets an updated rpc list from this.addToFrequentRpcList() and sets the `frequentRpcList` to this update list. + * + * @param {string} _url The the new rpc url to add to the updated list + * @returns {Promise<void>} Promise resolves with undefined + * + */ updateFrequentRpcList (_url) { return this.addToFrequentRpcList(_url) .then((rpcList) => { @@ -170,13 +170,13 @@ class PreferencesController { }) } - /** - * Setter for the `currentAccountTab` property - * - * @param {string} currentAccountTab Specifies the new tab to be marked as current - * @returns {Promise<void>} Promise resolves with undefined - * - */ + /** + * Setter for the `currentAccountTab` property + * + * @param {string} currentAccountTab Specifies the new tab to be marked as current + * @returns {Promise<void>} Promise resolves with undefined + * + */ setCurrentAccountTab (currentAccountTab) { return new Promise((resolve, reject) => { this.store.updateState({ currentAccountTab }) @@ -184,15 +184,15 @@ class PreferencesController { }) } - /** - * Returns an updated rpcList based on the passed url and the current list. + /** + * Returns an updated rpcList based on the passed url and the current list. * The returned list will have a max length of 2. If the _url currently exists it the list, it will be moved to the * end of the list. The current list is modified and returned as a promise. - * - * @param {string} _url The rpc url to add to the frequentRpcList. - * @returns {Promise<array>} The updated frequentRpcList. - * - */ + * + * @param {string} _url The rpc url to add to the frequentRpcList. + * @returns {Promise<array>} The updated frequentRpcList. + * + */ addToFrequentRpcList (_url) { const rpcList = this.getFrequentRpcList() const index = rpcList.findIndex((element) => { return element === _url }) @@ -208,24 +208,24 @@ class PreferencesController { return Promise.resolve(rpcList) } - /** - * Getter for the `frequentRpcList` property. - * - * @returns {array<string>} An array of one or two rpc urls. - * - */ + /** + * Getter for the `frequentRpcList` property. + * + * @returns {array<string>} An array of one or two rpc urls. + * + */ getFrequentRpcList () { return this.store.getState().frequentRpcList } - /** - * Updates the `featureFlags` property, which is an object. One property within that object will be set to a boolean. - * - * @param {string} feature A key that corresponds to a UI feature. + /** + * Updates the `featureFlags` property, which is an object. One property within that object will be set to a boolean. + * + * @param {string} feature A key that corresponds to a UI feature. * @param {boolean} activated Indicates whether or not the UI feature should be displayed - * @returns {Promise<object>} Promises a new object; the updated featureFlags object. - * - */ + * @returns {Promise<object>} Promises a new object; the updated featureFlags object. + * + */ setFeatureFlag (feature, activated) { const currentFeatureFlags = this.store.getState().featureFlags const updatedFeatureFlags = { @@ -238,13 +238,13 @@ class PreferencesController { return Promise.resolve(updatedFeatureFlags) } - /** - * A getter for the `featureFlags` property - * - * @returns {object} A key-boolean map, where keys refer to features and booleans to whether the + /** + * A getter for the `featureFlags` property + * + * @returns {object} A key-boolean map, where keys refer to features and booleans to whether the * user wishes to see that feature - * - */ + * + */ getFeatureFlags () { return this.store.getState().featureFlags } diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js index ddcaa7220..1377c1ba9 100644 --- a/app/scripts/controllers/recent-blocks.js +++ b/app/scripts/controllers/recent-blocks.js @@ -2,6 +2,7 @@ const ObservableStore = require('obs-store') const extend = require('xtend') const BN = require('ethereumjs-util').BN const EthQuery = require('eth-query') +const log = require('loglevel') class RecentBlocksController { diff --git a/app/scripts/controllers/shapeshift.js b/app/scripts/controllers/shapeshift.js index 17994d2db..b2a1462c2 100644 --- a/app/scripts/controllers/shapeshift.js +++ b/app/scripts/controllers/shapeshift.js @@ -1,5 +1,6 @@ const ObservableStore = require('obs-store') const extend = require('xtend') +const log = require('loglevel') // every three seconds when an incomplete tx is waiting const POLLING_INTERVAL = 3000 @@ -31,7 +32,7 @@ class ShapeshiftController { * @property {string} depositAddress - An address at which to send a crypto deposit, so that eth can be sent to the * user's Metamask account * @property {string} depositType - An abbreviation of the type of crypto currency to be deposited. - * @constant {string} key - The 'shapeshift' key differentiates this from other types of txs in Metamask + * @property {string} key - The 'shapeshift' key differentiates this from other types of txs in Metamask * @property {number} time - The time at which the tx was created * @property {object} response - Initiated as an empty object, which will be replaced by a Response object. @see {@link * https://developer.mozilla.org/en-US/docs/Web/API/Response} @@ -41,38 +42,38 @@ class ShapeshiftController { // PUBLIC METHODS // - /** - * A getter for the shapeShiftTxList property - * - * @returns {array<ShapeShiftTx>} - * - */ + /** + * A getter for the shapeShiftTxList property + * + * @returns {array<ShapeShiftTx>} + * + */ getShapeShiftTxList () { const shapeShiftTxList = this.store.getState().shapeShiftTxList return shapeShiftTxList } - /** - * A getter for all ShapeShiftTx in the shapeShiftTxList that have not successfully completed a deposit. - * - * @returns {array<ShapeShiftTx>} Only includes ShapeShiftTx which has a response property with a status !== complete - * - */ + /** + * A getter for all ShapeShiftTx in the shapeShiftTxList that have not successfully completed a deposit. + * + * @returns {array<ShapeShiftTx>} Only includes ShapeShiftTx which has a response property with a status !== complete + * + */ getPendingTxs () { const txs = this.getShapeShiftTxList() const pending = txs.filter(tx => tx.response && tx.response.status !== 'complete') return pending } - /** - * A poll that exists as long as there are pending transactions. Each call attempts to update the data of any + /** + * A poll that exists as long as there are pending transactions. Each call attempts to update the data of any * pendingTxs, and then calls itself again. If there are no pending txs, the recursive call is not made and * the polling stops. * * this.updateTx is used to attempt the update to the pendingTxs in the ShapeShiftTxList, and that updated data * is saved with saveTx. - * - */ + * + */ pollForUpdates () { const pendingTxs = this.getPendingTxs() @@ -113,13 +114,13 @@ class ShapeshiftController { } } - /** - * Saves an updated to a ShapeShiftTx in the shapeShiftTxList. If the passed ShapeShiftTx is not in the + /** + * Saves an updated to a ShapeShiftTx in the shapeShiftTxList. If the passed ShapeShiftTx is not in the * shapeShiftTxList, nothing happens. - * - * @param {ShapeShiftTx} tx The updated tx to save, if it exists in the current shapeShiftTxList - * - */ + * + * @param {ShapeShiftTx} tx The updated tx to save, if it exists in the current shapeShiftTxList + * + */ saveTx (tx) { const { shapeShiftTxList } = this.store.getState() const index = shapeShiftTxList.indexOf(tx) @@ -129,12 +130,12 @@ class ShapeshiftController { } } - /** - * Removes a ShapeShiftTx from the shapeShiftTxList - * - * @param {ShapeShiftTx} tx The tx to remove - * - */ + /** + * Removes a ShapeShiftTx from the shapeShiftTxList + * + * @param {ShapeShiftTx} tx The tx to remove + * + */ removeShapeShiftTx (tx) { const { shapeShiftTxList } = this.store.getState() const index = shapeShiftTxList.indexOf(index) @@ -144,14 +145,14 @@ class ShapeshiftController { this.updateState({ shapeShiftTxList }) } - /** - * Creates a new ShapeShiftTx, adds it to the shapeShiftTxList, and initiates a new poll for updates of pending txs - * + /** + * Creates a new ShapeShiftTx, adds it to the shapeShiftTxList, and initiates a new poll for updates of pending txs + * * @param {string} depositAddress - An address at which to send a crypto deposit, so that eth can be sent to the * user's Metamask account * @param {string} depositType - An abbreviation of the type of crypto currency to be deposited. - * - */ + * + */ createShapeShiftTx (depositAddress, depositType) { const state = this.store.getState() let { shapeShiftTxList } = state diff --git a/app/scripts/controllers/token-rates.js b/app/scripts/controllers/token-rates.js new file mode 100644 index 000000000..22e3e8154 --- /dev/null +++ b/app/scripts/controllers/token-rates.js @@ -0,0 +1,77 @@ +const ObservableStore = require('obs-store') + +// By default, poll every 3 minutes +const DEFAULT_INTERVAL = 180 * 1000 + +/** + * A controller that polls for token exchange + * rates based on a user's current token list + */ +class TokenRatesController { + /** + * Creates a TokenRatesController + * + * @param {Object} [config] - Options to configure controller + */ + constructor ({ interval = DEFAULT_INTERVAL, preferences } = {}) { + this.store = new ObservableStore() + this.preferences = preferences + this.interval = interval + } + + /** + * Updates exchange rates for all tokens + */ + async updateExchangeRates () { + if (!this.isActive) { return } + const contractExchangeRates = {} + for (const i in this._tokens) { + const address = this._tokens[i].address + contractExchangeRates[address] = await this.fetchExchangeRate(address) + } + this.store.putState({ contractExchangeRates }) + } + + /** + * Fetches a token exchange rate by address + * + * @param {String} address - Token contract address + */ + async fetchExchangeRate (address) { + try { + const response = await fetch(`https://exchanges.balanc3.net/prices?from=${address}&to=ETH&autoConversion=false&summaryOnly=true`) + const json = await response.json() + return json && json.length ? json[0].averagePrice : 0 + } catch (error) { } + } + + /** + * @type {Number} - Interval used to poll for exchange rates + */ + set interval (interval) { + this._handle && clearInterval(this._handle) + if (!interval) { return } + this._handle = setInterval(() => { this.updateExchangeRates() }, interval) + } + + /** + * @type {Object} - Preferences controller instance + */ + set preferences (preferences) { + this._preferences && this._preferences.unsubscribe() + if (!preferences) { return } + this._preferences = preferences + this.tokens = preferences.getState().tokens + preferences.subscribe(({ tokens = [] }) => { this.tokens = tokens }) + } + + /** + * @type {Array} - Array of token objects with contract addresses + */ + set tokens (tokens) { + this._tokens = tokens + this.updateExchangeRates() + } +} + +module.exports = TokenRatesController diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 336b0d8f7..c8211ebd7 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -7,6 +7,7 @@ const TransactionStateManager = require('../lib/tx-state-manager') const TxGasUtil = require('../lib/tx-gas-utils') const PendingTransactionTracker = require('../lib/pending-tx-tracker') const NonceTracker = require('../lib/nonce-tracker') +const log = require('loglevel') /* Transaction Controller is an aggregate of sub-controllers and trackers diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index ec99bfc35..92c732813 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -3,16 +3,11 @@ cleanContextForImports() require('web3/dist/web3.min.js') const log = require('loglevel') const LocalMessageDuplexStream = require('post-message-stream') -// const PingStream = require('ping-pong-stream/ping') -// const endOfStream = require('end-of-stream') const setupDappAutoReload = require('./lib/auto-reload.js') const MetamaskInpageProvider = require('./lib/inpage-provider.js') restoreContextAfterImports() -const METAMASK_DEBUG = process.env.METAMASK_DEBUG -window.log = log -log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn') - +log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn') // // setup plugin communication diff --git a/app/scripts/lib/ComposableObservableStore.js b/app/scripts/lib/ComposableObservableStore.js new file mode 100644 index 000000000..d5ee708a1 --- /dev/null +++ b/app/scripts/lib/ComposableObservableStore.js @@ -0,0 +1,49 @@ +const ObservableStore = require('obs-store') + +/** + * An ObservableStore that can composes a flat + * structure of child stores based on configuration + */ +class ComposableObservableStore extends ObservableStore { + /** + * Create a new store + * + * @param {Object} [initState] - The initial store state + * @param {Object} [config] - Map of internal state keys to child stores + */ + constructor (initState, config) { + super(initState) + this.updateStructure(config) + } + + /** + * Composes a new internal store subscription structure + * + * @param {Object} [config] - Map of internal state keys to child stores + */ + updateStructure (config) { + this.config = config + this.removeAllListeners() + for (const key in config) { + config[key].subscribe((state) => { + this.updateState({ [key]: state }) + }) + } + } + + /** + * Merges all child store state into a single object rather than + * returning an object keyed by child store class name + * + * @returns {Object} - Object containing merged child store state + */ + getFlatState () { + let flatState = {} + for (const key in this.config) { + flatState = { ...flatState, ...this.config[key].getState() } + } + return flatState + } +} + +module.exports = ComposableObservableStore diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index 34b603b96..63d27c40e 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -102,7 +102,6 @@ ConfigManager.prototype.setShowSeedWords = function (should) { this.setData(data) } - ConfigManager.prototype.getShouldShowSeedWords = function () { var data = this.getData() return data.showSeedWords @@ -118,6 +117,27 @@ ConfigManager.prototype.getSeedWords = function () { var data = this.getData() return data.seedWords } + +/** + * Called to set the isRevealingSeedWords flag. This happens only when the user chooses to reveal + * the seed words and not during the first time flow. + * @param {boolean} reveal - Value to set the isRevealingSeedWords flag. + */ +ConfigManager.prototype.setIsRevealingSeedWords = function (reveal = false) { + const data = this.getData() + data.isRevealingSeedWords = reveal + this.setData(data) +} + +/** + * Returns the isRevealingSeedWords flag. + * @returns {boolean|undefined} + */ +ConfigManager.prototype.getIsRevealingSeedWords = function () { + const data = this.getData() + return data.isRevealingSeedWords +} + ConfigManager.prototype.setRpcTarget = function (rpcUrl) { var config = this.getConfig() config.provider = { diff --git a/app/scripts/lib/createLoggerMiddleware.js b/app/scripts/lib/createLoggerMiddleware.js index 2707cbd9e..996c3477c 100644 --- a/app/scripts/lib/createLoggerMiddleware.js +++ b/app/scripts/lib/createLoggerMiddleware.js @@ -1,14 +1,20 @@ -// log rpc activity +const log = require('loglevel') + module.exports = createLoggerMiddleware -function createLoggerMiddleware ({ origin }) { - return function loggerMiddleware (req, res, next, end) { - next((cb) => { +/** + * Returns a middleware that logs RPC activity + * @param {{ origin: string }} opts - The middleware options + * @returns {Function} + */ +function createLoggerMiddleware (opts) { + return function loggerMiddleware (/** @type {any} */ req, /** @type {any} */ res, /** @type {Function} */ next) { + next((/** @type {Function} */ cb) => { if (res.error) { log.error('Error in RPC response:\n', res) } if (req.isMetamaskInternal) return - log.info(`RPC (${origin}):`, req, '->', res) + log.info(`RPC (${opts.origin}):`, req, '->', res) cb() }) } diff --git a/app/scripts/lib/createOriginMiddleware.js b/app/scripts/lib/createOriginMiddleware.js index f8bdb2dc2..98bb0e3b3 100644 --- a/app/scripts/lib/createOriginMiddleware.js +++ b/app/scripts/lib/createOriginMiddleware.js @@ -1,9 +1,13 @@ -// append dapp origin domain to request module.exports = createOriginMiddleware -function createOriginMiddleware ({ origin }) { - return function originMiddleware (req, res, next, end) { - req.origin = origin +/** + * Returns a middleware that appends the DApp origin to request + * @param {{ origin: string }} opts - The middleware options + * @returns {Function} + */ +function createOriginMiddleware (opts) { + return function originMiddleware (/** @type {any} */ req, /** @type {any} */ _, /** @type {Function} */ next) { + req.origin = opts.origin next() } } diff --git a/app/scripts/lib/enums.js b/app/scripts/lib/enums.js new file mode 100644 index 000000000..0a3afca47 --- /dev/null +++ b/app/scripts/lib/enums.js @@ -0,0 +1,9 @@ +const ENVIRONMENT_TYPE_POPUP = 'popup' +const ENVIRONMENT_TYPE_NOTIFICATION = 'notification' +const ENVIRONMENT_TYPE_FULLSCREEN = 'fullscreen' + +module.exports = { + ENVIRONMENT_TYPE_POPUP, + ENVIRONMENT_TYPE_NOTIFICATION, + ENVIRONMENT_TYPE_FULLSCREEN, +} diff --git a/app/scripts/lib/environment-type.js b/app/scripts/lib/environment-type.js deleted file mode 100644 index f13a1574d..000000000 --- a/app/scripts/lib/environment-type.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Used to determine the window type through which the app is being viewed. - * - 'popup' refers to the extension opened through the browser app icon (in top right corner in chrome and firefox) - * - 'responsive' refers to the main browser window - * - 'notification' refers to the popup that appears in its own window when taking action outside of metamask - * - * @returns {string} A single word label that represents the type of window through which the app is being viewed - * - */ -module.exports = function environmentType () { - const url = window.location.href - if (url.match(/popup.html$/)) { - return 'popup' - } else if (url.match(/home.html$/)) { - return 'responsive' - } else { - return 'notification' - } -} diff --git a/app/scripts/lib/events-proxy.js b/app/scripts/lib/events-proxy.js index c0a490b05..f83773ccc 100644 --- a/app/scripts/lib/events-proxy.js +++ b/app/scripts/lib/events-proxy.js @@ -1,26 +1,37 @@ +/** + * Returns an EventEmitter that proxies events from the given event emitter + * @param {any} eventEmitter + * @param {object} listeners - The listeners to proxy to + * @returns {any} + */ module.exports = function createEventEmitterProxy (eventEmitter, listeners) { let target = eventEmitter const eventHandlers = listeners || {} - const proxy = new Proxy({}, { - get: (obj, name) => { + const proxy = /** @type {any} */ (new Proxy({}, { + get: (_, name) => { // intercept listeners if (name === 'on') return addListener if (name === 'setTarget') return setTarget if (name === 'proxyEventHandlers') return eventHandlers - return target[name] + return (/** @type {any} */ (target))[name] }, - set: (obj, name, value) => { + set: (_, name, value) => { target[name] = value return true }, - }) - function setTarget (eventEmitter) { + })) + function setTarget (/** @type {EventEmitter} */ eventEmitter) { target = eventEmitter // migrate listeners Object.keys(eventHandlers).forEach((name) => { - eventHandlers[name].forEach((handler) => target.on(name, handler)) + /** @type {Array<Function>} */ (eventHandlers[name]).forEach((handler) => target.on(name, handler)) }) } + /** + * Attaches a function to be called whenever the specified event is emitted + * @param {string} name + * @param {Function} handler + */ function addListener (name, handler) { if (!eventHandlers[name]) eventHandlers[name] = [] eventHandlers[name].push(handler) diff --git a/app/scripts/lib/hex-to-bn.js b/app/scripts/lib/hex-to-bn.js index 184217279..b28746920 100644 --- a/app/scripts/lib/hex-to-bn.js +++ b/app/scripts/lib/hex-to-bn.js @@ -1,6 +1,11 @@ -const ethUtil = require('ethereumjs-util') +const ethUtil = (/** @type {object} */ (require('ethereumjs-util'))) const BN = ethUtil.BN +/** + * Returns a [BinaryNumber]{@link BN} representation of the given hex value + * @param {string} hex + * @return {any} + */ module.exports = function hexToBn (hex) { return new BN(ethUtil.stripHexPrefix(hex), 16) } diff --git a/app/scripts/lib/is-popup-or-notification.js b/app/scripts/lib/is-popup-or-notification.js deleted file mode 100644 index 894564def..000000000 --- a/app/scripts/lib/is-popup-or-notification.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Indicates whether the user is viewing the app through an extension like window or through a notification. - * Used to make some style decisions on the frontend, and when deciding whether to close the popup in the backend. - * - * @returns {string} Returns 'popup' if the user is viewing through the browser ('home.html') or popup extension - * ('popup.html'). Otherwise it returns 'notification'. - * - */ -module.exports = function isPopupOrNotification () { - const url = window.location.href - - if (url.match(/popup.html(?:\?.+)*$/) || - url.match(/home.html(?:\?.+)*$/) || url.match(/home.html(?:#.*)*$/)) { - return 'popup' - } else { - return 'notification' - } -} diff --git a/app/scripts/lib/local-store.js b/app/scripts/lib/local-store.js index 5b47985f6..139ff86bd 100644 --- a/app/scripts/lib/local-store.js +++ b/app/scripts/lib/local-store.js @@ -1,10 +1,13 @@ -// We should not rely on local storage in an extension! -// We should use this instead! -// https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/storage/local - const extension = require('extensionizer') +const log = require('loglevel') +/** + * A wrapper around the extension's storage local API + */ module.exports = class ExtensionStore { + /** + * @constructor + */ constructor() { this.isSupported = !!(extension.storage.local) if (!this.isSupported) { @@ -12,6 +15,10 @@ module.exports = class ExtensionStore { } } + /** + * Returns all of the keys currently saved + * @return {Promise<*>} + */ async get() { if (!this.isSupported) return undefined const result = await this._get() @@ -24,14 +31,24 @@ module.exports = class ExtensionStore { } } + /** + * Sets the key in local state + * @param {object} state - The state to set + * @return {Promise<void>} + */ async set(state) { return this._set(state) } + /** + * Returns all of the keys currently saved + * @private + * @return {object} the key-value map from local storage + */ _get() { const local = extension.storage.local return new Promise((resolve, reject) => { - local.get(null, (result) => { + local.get(null, (/** @type {any} */ result) => { const err = extension.runtime.lastError if (err) { reject(err) @@ -42,6 +59,12 @@ module.exports = class ExtensionStore { }) } + /** + * Sets the key in local state + * @param {object} obj - The key to set + * @return {Promise<void>} + * @private + */ _set(obj) { const local = extension.storage.local return new Promise((resolve, reject) => { @@ -57,6 +80,11 @@ module.exports = class ExtensionStore { } } +/** + * Returns whether or not the given object contains no keys + * @param {object} obj - The object to check + * @returns {boolean} + */ function isEmpty(obj) { return Object.keys(obj).length === 0 } diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js index 85c2717ea..345ca8001 100644 --- a/app/scripts/lib/migrator/index.js +++ b/app/scripts/lib/migrator/index.js @@ -1,7 +1,23 @@ const EventEmitter = require('events') +/** + * @typedef {object} Migration + * @property {number} version - The migration version + * @property {Function} migrate - Returns a promise of the migrated data + */ + +/** + * @typedef {object} MigratorOptions + * @property {Array<Migration>} [migrations] - The list of migrations to apply + * @property {number} [defaultVersion] - The version to use in the initial state + */ + class Migrator extends EventEmitter { + /** + * @constructor + * @param {MigratorOptions} opts + */ constructor (opts = {}) { super() const migrations = opts.migrations || [] @@ -42,19 +58,30 @@ class Migrator extends EventEmitter { return versionedData - // migration is "pending" if it has a higher - // version number than currentVersion + /** + * Returns whether or not the migration is pending + * + * A migration is considered "pending" if it has a higher + * version number than the current version. + * @param {Migration} migration + * @returns {boolean} + */ function migrationIsPending (migration) { return migration.version > versionedData.meta.version } } - generateInitialState (initState) { + /** + * Returns the initial state for the migrator + * @param {object} [data] - The data for the initial state + * @returns {{meta: {version: number}, data: any}} + */ + generateInitialState (data) { return { meta: { version: this.defaultVersion, }, - data: initState, + data, } } diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js index 3c502329c..4f19876db 100644 --- a/app/scripts/lib/personal-message-manager.js +++ b/app/scripts/lib/personal-message-manager.js @@ -3,6 +3,7 @@ const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') const createId = require('./random-id') const hexRe = /^[0-9A-Fa-f]+$/g +const log = require('loglevel') /** * Represents, and contains data about, an 'personal_sign' type signature request. These are created when a diff --git a/app/scripts/lib/seed-phrase-verifier.js b/app/scripts/lib/seed-phrase-verifier.js index 826e73306..3b5afb800 100644 --- a/app/scripts/lib/seed-phrase-verifier.js +++ b/app/scripts/lib/seed-phrase-verifier.js @@ -1,4 +1,5 @@ const KeyringController = require('eth-keyring-controller') +const log = require('loglevel') const seedPhraseVerifier = { diff --git a/app/scripts/lib/stream-utils.js b/app/scripts/lib/stream-utils.js index 8bb0b4f3c..3dbc064b5 100644 --- a/app/scripts/lib/stream-utils.js +++ b/app/scripts/lib/stream-utils.js @@ -8,20 +8,34 @@ module.exports = { setupMultiplex: setupMultiplex, } +/** + * Returns a stream transform that parses JSON strings passing through + * @return {stream.Transform} + */ function jsonParseStream () { - return Through.obj(function (serialized, encoding, cb) { + return Through.obj(function (serialized, _, cb) { this.push(JSON.parse(serialized)) cb() }) } +/** + * Returns a stream transform that calls {@code JSON.stringify} + * on objects passing through + * @return {stream.Transform} the stream transform + */ function jsonStringifyStream () { - return Through.obj(function (obj, encoding, cb) { + return Through.obj(function (obj, _, cb) { this.push(JSON.stringify(obj)) cb() }) } +/** + * Sets up stream multiplexing for the given stream + * @param {any} connectionStream - the stream to mux + * @return {stream.Stream} the multiplexed stream + */ function setupMultiplex (connectionStream) { const mux = new ObjectMultiplex() pump( diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js index 8716ebf9a..367c6ecb9 100644 --- a/app/scripts/lib/typed-message-manager.js +++ b/app/scripts/lib/typed-message-manager.js @@ -3,6 +3,7 @@ const ObservableStore = require('obs-store') const createId = require('./random-id') const assert = require('assert') const sigUtil = require('eth-sig-util') +const log = require('loglevel') /** * Represents, and contains data about, an 'eth_signTypedData' type signature request. These are created when a diff --git a/app/scripts/lib/util.js b/app/scripts/lib/util.js index cb0d7e5c1..431d1e59c 100644 --- a/app/scripts/lib/util.js +++ b/app/scripts/lib/util.js @@ -1,14 +1,11 @@ const ethUtil = require('ethereumjs-util') const assert = require('assert') const BN = require('bn.js') - -module.exports = { - getStack, - sufficientBalance, - hexToBn, - bnToHex, - BnMultiplyByFraction, -} +const { + ENVIRONMENT_TYPE_POPUP, + ENVIRONMENT_TYPE_NOTIFICATION, + ENVIRONMENT_TYPE_FULLSCREEN, +} = require('./enums') /** * Generates an example stack trace @@ -22,6 +19,25 @@ function getStack () { } /** + * Used to determine the window type through which the app is being viewed. + * - 'popup' refers to the extension opened through the browser app icon (in top right corner in chrome and firefox) + * - 'responsive' refers to the main browser window + * - 'notification' refers to the popup that appears in its own window when taking action outside of metamask + * + * @returns {string} A single word label that represents the type of window through which the app is being viewed + * + */ +const getEnvironmentType = (url = window.location.href) => { + if (url.match(/popup.html(?:\?.+)*$/)) { + return ENVIRONMENT_TYPE_POPUP + } else if (url.match(/home.html(?:\?.+)*$/) || url.match(/home.html(?:#.*)*$/)) { + return ENVIRONMENT_TYPE_FULLSCREEN + } else { + return ENVIRONMENT_TYPE_NOTIFICATION + } +} + +/** * Checks whether a given balance of ETH, represented as a hex string, is sufficient to pay a value plus a gas fee * * @param {object} txParams Contains data about a transaction @@ -82,3 +98,12 @@ function BnMultiplyByFraction (targetBN, numerator, denominator) { const denomBN = new BN(denominator) return targetBN.mul(numBN).div(denomBN) } + +module.exports = { + getStack, + getEnvironmentType, + sufficientBalance, + hexToBn, + bnToHex, + BnMultiplyByFraction, +} diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b96acc9da..782bc50ac 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -5,10 +5,10 @@ */ const EventEmitter = require('events') -const extend = require('xtend') const pump = require('pump') const Dnode = require('dnode') const ObservableStore = require('obs-store') +const ComposableObservableStore = require('./lib/ComposableObservableStore') const asStream = require('obs-store/lib/asStream') const AccountTracker = require('./lib/account-tracker') const RpcEngine = require('json-rpc-engine') @@ -34,6 +34,7 @@ const PersonalMessageManager = require('./lib/personal-message-manager') const TypedMessageManager = require('./lib/typed-message-manager') const TransactionController = require('./controllers/transactions') const BalancesController = require('./controllers/computed-balances') +const TokenRatesController = require('./controllers/token-rates') const ConfigManager = require('./lib/config-manager') const nodeify = require('./lib/nodeify') const accountImporter = require('./account-import-strategies') @@ -44,6 +45,7 @@ const BN = require('ethereumjs-util').BN const GWEI_BN = new BN('1000000000') const percentile = require('percentile') const seedPhraseVerifier = require('./lib/seed-phrase-verifier') +const log = require('loglevel') module.exports = class MetamaskController extends EventEmitter { @@ -65,7 +67,7 @@ module.exports = class MetamaskController extends EventEmitter { this.platform = opts.platform // observable state store - this.store = new ObservableStore(initState) + this.store = new ComposableObservableStore(initState) // lock to ensure only one vault created at once this.createVaultMutex = new Mutex() @@ -104,6 +106,11 @@ module.exports = class MetamaskController extends EventEmitter { this.provider = this.initializeProvider() this.blockTracker = this.provider._blockTracker + // token exchange rate tracker + this.tokenRatesController = new TokenRatesController({ + preferences: this.preferencesController.store, + }) + this.recentBlocksController = new RecentBlocksController({ blockTracker: this.blockTracker, provider: this.provider, @@ -184,53 +191,37 @@ module.exports = class MetamaskController extends EventEmitter { this.typedMessageManager = new TypedMessageManager() this.publicConfigStore = this.initPublicConfigStore() - // manual disk state subscriptions - this.txController.store.subscribe((state) => { - this.store.updateState({ TransactionController: state }) - }) - this.keyringController.store.subscribe((state) => { - this.store.updateState({ KeyringController: state }) - }) - this.preferencesController.store.subscribe((state) => { - this.store.updateState({ PreferencesController: state }) - }) - this.addressBookController.store.subscribe((state) => { - this.store.updateState({ AddressBookController: state }) - }) - this.currencyController.store.subscribe((state) => { - this.store.updateState({ CurrencyController: state }) - }) - this.noticeController.store.subscribe((state) => { - this.store.updateState({ NoticeController: state }) - }) - this.shapeshiftController.store.subscribe((state) => { - this.store.updateState({ ShapeShiftController: state }) - }) - this.networkController.store.subscribe((state) => { - this.store.updateState({ NetworkController: state }) + this.store.updateStructure({ + TransactionController: this.txController.store, + KeyringController: this.keyringController.store, + PreferencesController: this.preferencesController.store, + AddressBookController: this.addressBookController.store, + CurrencyController: this.currencyController.store, + NoticeController: this.noticeController.store, + ShapeShiftController: this.shapeshiftController.store, + NetworkController: this.networkController.store, + InfuraController: this.infuraController.store, }) - this.infuraController.store.subscribe((state) => { - this.store.updateState({ InfuraController: state }) + this.memStore = new ComposableObservableStore(null, { + NetworkController: this.networkController.store, + AccountTracker: this.accountTracker.store, + TxController: this.txController.memStore, + BalancesController: this.balancesController.store, + TokenRatesController: this.tokenRatesController.store, + MessageManager: this.messageManager.memStore, + PersonalMessageManager: this.personalMessageManager.memStore, + TypesMessageManager: this.typedMessageManager.memStore, + KeyringController: this.keyringController.memStore, + PreferencesController: this.preferencesController.store, + RecentBlocksController: this.recentBlocksController.store, + AddressBookController: this.addressBookController.store, + CurrencyController: this.currencyController.store, + NoticeController: this.noticeController.memStore, + ShapeshiftController: this.shapeshiftController.store, + InfuraController: this.infuraController.store, }) - - // manual mem state subscriptions - const sendUpdate = this.sendUpdate.bind(this) - this.networkController.store.subscribe(sendUpdate) - this.accountTracker.store.subscribe(sendUpdate) - this.txController.memStore.subscribe(sendUpdate) - this.balancesController.store.subscribe(sendUpdate) - this.messageManager.memStore.subscribe(sendUpdate) - this.personalMessageManager.memStore.subscribe(sendUpdate) - this.typedMessageManager.memStore.subscribe(sendUpdate) - this.keyringController.memStore.subscribe(sendUpdate) - this.preferencesController.store.subscribe(sendUpdate) - this.recentBlocksController.store.subscribe(sendUpdate) - this.addressBookController.store.subscribe(sendUpdate) - this.currencyController.store.subscribe(sendUpdate) - this.noticeController.memStore.subscribe(sendUpdate) - this.shapeshiftController.store.subscribe(sendUpdate) - this.infuraController.store.subscribe(sendUpdate) + this.memStore.subscribe(this.sendUpdate.bind(this)) } /** @@ -279,6 +270,7 @@ module.exports = class MetamaskController extends EventEmitter { // memStore -> transform -> publicConfigStore this.on('update', (memState) => { + this.isClientOpenAndUnlocked = memState.isUnlocked && this._isClientOpen const publicState = selectPublicState(memState) publicConfigStore.putState(publicState) }) @@ -308,33 +300,17 @@ module.exports = class MetamaskController extends EventEmitter { const vault = this.keyringController.store.getState().vault const isInitialized = (!!wallet || !!vault) - return extend( - { - isInitialized, - }, - this.networkController.store.getState(), - this.accountTracker.store.getState(), - this.txController.memStore.getState(), - this.messageManager.memStore.getState(), - this.personalMessageManager.memStore.getState(), - this.typedMessageManager.memStore.getState(), - this.keyringController.memStore.getState(), - this.balancesController.store.getState(), - this.preferencesController.store.getState(), - this.addressBookController.store.getState(), - this.currencyController.store.getState(), - this.noticeController.memStore.getState(), - this.infuraController.store.getState(), - this.recentBlocksController.store.getState(), - // config manager - this.configManager.getConfig(), - this.shapeshiftController.store.getState(), - { + return { + ...{ isInitialized }, + ...this.memStore.getFlatState(), + ...this.configManager.getConfig(), + ...{ lostAccounts: this.configManager.getLostAccounts(), seedWords: this.configManager.getSeedWords(), forgottenPassword: this.configManager.getPasswordForgotten(), - } - ) + isRevealingSeedWords: Boolean(this.configManager.getIsRevealingSeedWords()), + }, + } } /** @@ -372,6 +348,7 @@ module.exports = class MetamaskController extends EventEmitter { clearSeedWordCache: this.clearSeedWordCache.bind(this), resetAccount: nodeify(this.resetAccount, this), importAccountWithStrategy: this.importAccountWithStrategy.bind(this), + setIsRevealingSeedWords: this.configManager.setIsRevealingSeedWords.bind(this.configManager), // vault management submitPassword: nodeify(keyringController.submitPassword, keyringController), @@ -1057,4 +1034,12 @@ module.exports = class MetamaskController extends EventEmitter { } } + set isClientOpen (open) { + this._isClientOpen = open + this.isClientOpenAndUnlocked = this.getState().isUnlocked && open + } + + set isClientOpenAndUnlocked (active) { + this.tokenRatesController.isActive = active + } } diff --git a/app/scripts/platforms/sw.js b/app/scripts/platforms/sw.js index 007d8dc5b..56c5f2774 100644 --- a/app/scripts/platforms/sw.js +++ b/app/scripts/platforms/sw.js @@ -1,20 +1,25 @@ - class SwPlatform { - - // - // Public - // - + /** + * Reloads the platform + */ reload () { - // you cant actually do this - global.location.reload() + // TODO: you can't actually do this + /** @type {any} */ (global).location.reload() } - openWindow ({ url }) { - // this doesnt actually work - global.open(url, '_blank') + /** + * Opens a window + * @param {{url: string}} opts - The window options + */ + openWindow (opts) { + // TODO: this doesn't actually work + /** @type {any} */ (global).open(opts.url, '_blank') } + /** + * Returns the platform version + * @returns {string} + */ getVersion () { return '<unable to read version>' } diff --git a/app/scripts/platforms/window.js b/app/scripts/platforms/window.js index 1527c008b..943b2a703 100644 --- a/app/scripts/platforms/window.js +++ b/app/scripts/platforms/window.js @@ -1,18 +1,23 @@ - class WindowPlatform { - - // - // Public - // - + /** + * Reload the platform + */ reload () { - global.location.reload() + /** @type {any} */ (global).location.reload() } - openWindow ({ url }) { - global.open(url, '_blank') + /** + * Opens a window + * @param {{url: string}} opts - The window options + */ + openWindow (opts) { + /** @type {any} */ (global).open(opts.url, '_blank') } + /** + * Returns the platform version + * @returns {string} + */ getVersion () { return '<unable to read version>' } diff --git a/app/scripts/ui.js b/app/scripts/ui.js index 13c7ac5ec..bdab29c1e 100644 --- a/app/scripts/ui.js +++ b/app/scripts/ui.js @@ -3,12 +3,14 @@ const OldMetaMaskUiCss = require('../../old-ui/css') const NewMetaMaskUiCss = require('../../ui/css') const startPopup = require('./popup-core') const PortStream = require('./lib/port-stream.js') -const isPopupOrNotification = require('./lib/is-popup-or-notification') +const { getEnvironmentType } = require('./lib/util') +const { ENVIRONMENT_TYPE_NOTIFICATION } = require('./lib/enums') const extension = require('extensionizer') const ExtensionPlatform = require('./platforms/extension') const NotificationManager = require('./lib/notification-manager') const notificationManager = new NotificationManager() const setupRaven = require('./lib/setupRaven') +const log = require('loglevel') start().catch(log.error) @@ -26,7 +28,7 @@ async function start() { // injectCss(css) // identify window type (popup, notification) - const windowType = isPopupOrNotification() + const windowType = getEnvironmentType(window.location.href) global.METAMASK_UI_TYPE = windowType closePopupIfOpen(windowType) @@ -68,7 +70,7 @@ async function start() { function closePopupIfOpen (windowType) { - if (windowType !== 'notification') { + if (windowType !== ENVIRONMENT_TYPE_NOTIFICATION) { // should close only chrome popup notificationManager.closePopup() } |