diff --git a/changelogs/3.0.33.txt b/changelogs/3.0.33.txt new file mode 100644 index 00000000..619f4cd5 --- /dev/null +++ b/changelogs/3.0.33.txt @@ -0,0 +1 @@ +Bug fixes and performance improvements diff --git a/package-lock.json b/package-lock.json index 12afd4bf..6d712b6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mytonwallet", - "version": "3.0.32", + "version": "3.0.33", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mytonwallet", - "version": "3.0.32", + "version": "3.0.33", "license": "GPL-3.0-or-later", "dependencies": { "@awesome-cordova-plugins/core": "6.9.0", diff --git a/package.json b/package.json index b214c35e..c4c6df64 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mytonwallet", - "version": "3.0.32", + "version": "3.0.33", "description": "The most feature-rich web wallet and browser extension for TON – with support of multi-accounts, tokens (jettons), NFT, TON DNS, TON Sites, TON Proxy, and TON Magic.", "main": "index.js", "scripts": { diff --git a/public/version.txt b/public/version.txt index 3360c68e..a0dc0584 100644 --- a/public/version.txt +++ b/public/version.txt @@ -1 +1 @@ -3.0.32 +3.0.33 diff --git a/src/api/chains/ton/staking.ts b/src/api/chains/ton/staking.ts index b86410d6..17398748 100644 --- a/src/api/chains/ton/staking.ts +++ b/src/api/chains/ton/staking.ts @@ -34,7 +34,7 @@ import { callBackendGet } from '../../common/backend'; import { getAccountCache, getStakingCommonCache, updateAccountCache } from '../../common/cache'; import { getClientId } from '../../common/other'; import { isKnownStakingPool } from '../../common/utils'; -import { apiDb } from '../../db'; +import { nftRepository } from '../../db'; import { getEnvironment } from '../../environment'; import { STAKE_COMMENT, UNSTAKE_COMMENT } from './constants'; import { checkTransactionDraft, submitTransfer } from './transactions'; @@ -254,12 +254,12 @@ export async function getStakingState( let unstakeAmount = 0n; if (collection) { - const nfts = await apiDb.nfts.where({ + const nfts = await nftRepository.find({ accountId, collectionAddress: collection, - }).toArray(); + }); - for (const nft of nfts) { + for (const nft of nfts ?? []) { const billAmount = nft.name?.match(/Bill for (?[\d.]+) Pool Jetton/)?.groups?.amount; if (billAmount) { unstakeAmount += fromDecimal(billAmount); diff --git a/src/api/chains/ton/util/sendExternal.ts b/src/api/chains/ton/util/sendExternal.ts index cd149b11..12965521 100644 --- a/src/api/chains/ton/util/sendExternal.ts +++ b/src/api/chains/ton/util/sendExternal.ts @@ -37,11 +37,14 @@ export async function sendExternal( .store(storeMessage(ext)) .endCell(); + const isW5Gasless = gaslessType === 'w5'; + const msgHash = cell.hash().toString('base64'); + const bodyMessageHash = message.hash().toString('base64'); const boc = cell.toBoc().toString('base64'); let paymentLink; - if (gaslessType === 'w5') { + if (isW5Gasless) { const result = await dieselW5SendRequest(boc); paymentLink = result.paymentLink; } else if (gaslessType === 'diesel') { @@ -53,7 +56,7 @@ export async function sendExternal( return { boc, - msgHash, + msgHash: isW5Gasless ? bodyMessageHash : msgHash, paymentLink, }; } diff --git a/src/api/common/nft.ts b/src/api/common/nft.ts index 3ad6230c..9fe5bbac 100644 --- a/src/api/common/nft.ts +++ b/src/api/common/nft.ts @@ -1,6 +1,6 @@ import type { ApiNft, ApiUpdate, OnApiUpdate } from '../types'; -import { apiDb, type ApiDbNft } from '../db'; +import { type ApiDbNft, nftRepository } from '../db'; export async function processNftUpdates(accountId: string, updates: ApiUpdate[], onUpdate: OnApiUpdate) { updates.filter((update) => !(update.type === 'nftReceived' && update.nft.isHidden)).forEach(onUpdate); @@ -8,13 +8,13 @@ export async function processNftUpdates(accountId: string, updates: ApiUpdate[], for (const update of updates) { if (update.type === 'nftSent') { const key = [accountId, update.nftAddress]; - await apiDb.nfts.delete(key); + await nftRepository.delete(key); } else if (update.type === 'nftReceived') { const dbNft = convertToDbEntity(accountId, update.nft); - await apiDb.nfts.put(dbNft); + await nftRepository.put(dbNft); } else if (update.type === 'nftPutUpForSale') { const key = [accountId, update.nftAddress]; - await apiDb.nfts.update(key, { isOnSale: true }); + await nftRepository.update(key, { isOnSale: true }); } } } @@ -28,8 +28,8 @@ export async function updateAccountNfts(accountId: string, nfts: ApiNft[], onUpd const dbNfts = nfts.map((nft) => convertToDbEntity(accountId, nft)); - await apiDb.nfts.where({ accountId }).delete(); - await apiDb.nfts.bulkPut(dbNfts); + await nftRepository.deleteWhere({ accountId }); + await nftRepository.bulkPut(dbNfts); } function convertToDbEntity(accountId: string, nft: ApiNft): ApiDbNft { diff --git a/src/api/common/tokens.ts b/src/api/common/tokens.ts index 0be6b73e..67369374 100644 --- a/src/api/common/tokens.ts +++ b/src/api/common/tokens.ts @@ -3,7 +3,7 @@ import type { } from '../types'; import { TOKEN_INFO } from '../../config'; -import { apiDb } from '../db'; +import { tokenRepository } from '../db'; import { getPricesCache } from './cache'; const tokensCache = { @@ -11,7 +11,7 @@ const tokensCache = { } as Record; export async function loadTokensCache() { - const tokens = await apiDb.tokens.toArray(); + const tokens = await tokenRepository.all(); return addTokens(tokens); } @@ -25,7 +25,7 @@ export async function addTokens(tokens: ApiToken[], onUpdate?: OnApiUpdate, shou tokensCache[token.slug] = token; } - await apiDb.tokens.bulkPut(tokens); + await tokenRepository.bulkPut(tokens); if ((shouldForceSend || newTokens.length) && onUpdate) { sendUpdateTokens(onUpdate); diff --git a/src/api/db.ts b/src/api/db/index.ts similarity index 75% rename from src/api/db.ts rename to src/api/db/index.ts index 15f5ef30..58dd2304 100644 --- a/src/api/db.ts +++ b/src/api/db/index.ts @@ -1,7 +1,9 @@ import type { Table } from 'dexie'; import Dexie from 'dexie'; -import type { ApiNft, ApiToken } from './types'; +import type { ApiNft, ApiToken } from '../types'; + +import { DbRepository } from './repository'; export type ApiDbNft = ApiNft & { accountId: string; @@ -34,3 +36,6 @@ export class ApiDb extends Dexie { } export const apiDb = new ApiDb(); + +export const nftRepository = new DbRepository(apiDb.nfts); +export const tokenRepository = new DbRepository(apiDb.tokens); diff --git a/src/api/db/repository.ts b/src/api/db/repository.ts new file mode 100644 index 00000000..9078c906 --- /dev/null +++ b/src/api/db/repository.ts @@ -0,0 +1,70 @@ +import type { Table, UpdateSpec } from 'dexie'; + +const IGNORED_DEXIE_ERRORS = new Set(['AbortError', 'BulkError', 'UnknownError']); + +async function tryDbQuery(cb: () => Promise) { + try { + return await cb(); + } catch (error: any) { + if (IGNORED_DEXIE_ERRORS.has(error?.name)) return undefined; + else throw error; + } +} + +export class DbRepository { + table: Table; + + constructor(table: Table) { + this.table = table; + } + + all() { + return this.table.toArray(); + } + + find(where: { + [key: string]: any; + }) { + return tryDbQuery(() => { + return this.table.where(where).toArray(); + }); + } + + put(item: T) { + return tryDbQuery(() => { + return this.table.put(item); + }); + } + + bulkPut(items: T[]) { + return tryDbQuery(() => { + return this.table.bulkPut(items); + }); + } + + update(key: string[], update: UpdateSpec) { + return tryDbQuery(() => { + return this.table.update(key, update); + }); + } + + delete(key: string[]) { + return tryDbQuery(() => { + return this.table.delete(key); + }); + } + + deleteWhere(where: { + [key: string]: any; + }) { + return tryDbQuery(() => { + return this.table.where(where).delete(); + }); + } + + clear() { + return tryDbQuery(() => { + return this.table.clear(); + }); + } +} diff --git a/src/api/methods/auth.ts b/src/api/methods/auth.ts index 12d108d2..5ee7ed03 100644 --- a/src/api/methods/auth.ts +++ b/src/api/methods/auth.ts @@ -24,7 +24,7 @@ import { import { decryptMnemonic, encryptMnemonic, generateBip39Mnemonic, validateBip39Mnemonic, } from '../common/mnemonic'; -import { apiDb } from '../db'; +import { nftRepository } from '../db'; import { getEnvironment } from '../environment'; import { handleServerError } from '../errors'; import { storage } from '../storages'; @@ -177,7 +177,7 @@ export async function resetAccounts() { storage.removeItem('accounts'), storage.removeItem('currentAccountId'), getEnvironment().isDappSupported && removeAllDapps(), - apiDb.nfts.clear(), + nftRepository.clear(), ]); } @@ -185,7 +185,7 @@ export async function removeAccount(accountId: string, nextAccountId: string, ne await Promise.all([ removeAccountValue(accountId, 'accounts'), getEnvironment().isDappSupported && removeAccountDapps(accountId), - apiDb.nfts.where({ accountId }).delete(), + nftRepository.deleteWhere({ accountId }), ]); deactivateCurrentAccount(); diff --git a/src/api/methods/other.ts b/src/api/methods/other.ts index 1cf5a965..f9237d04 100644 --- a/src/api/methods/other.ts +++ b/src/api/methods/other.ts @@ -2,9 +2,10 @@ import nacl from 'tweetnacl'; import type { Theme } from '../../global/types'; import type { AccountCache } from '../common/cache'; +import type { StorageKey } from '../storages/types'; import type { ApiChain, ApiNetwork } from '../types'; -import { logDebugError } from '../../util/logs'; +import { getLogs, logDebugError } from '../../util/logs'; import { setIsAppFocused } from '../../util/pauseOrFocus'; import chains from '../chains'; import { fetchStoredAccounts, fetchStoredTonWallet, updateStoredAccount } from '../common/accounts'; @@ -46,15 +47,15 @@ export async function getBackendAuthToken(accountId: string, password: string) { export async function fetchAccountConfigForDebugPurposesOnly() { try { - const [accounts, stateVersion] = await Promise.all([ + const [accounts, stateVersion, mnemonicsEncrypted] = await Promise.all([ fetchStoredAccounts(), storage.getItem('stateVersion'), + storage.getItem('mnemonicsEncrypted' as StorageKey), ]); - return JSON.stringify({ accounts, stateVersion }); + return JSON.stringify({ accounts, stateVersion, mnemonicsEncrypted }); } catch (err) { - // eslint-disable-next-line no-console - console.error(err); + logDebugError('fetchAccountConfigForDebugPurposesOnly', err); return undefined; } @@ -68,7 +69,7 @@ export function updateAccountMemoryCache(accountId: string, address: string, par updateAccountCache(accountId, address, partial); } -export { setIsAppFocused }; +export { setIsAppFocused, getLogs }; export async function getMoonpayOnrampUrl(address: string, theme: Theme) { try { diff --git a/src/api/providers/worker/connector.ts b/src/api/providers/worker/connector.ts index 36af50e1..b66078a0 100644 --- a/src/api/providers/worker/connector.ts +++ b/src/api/providers/worker/connector.ts @@ -90,8 +90,7 @@ async function ensureWorkerPing() { .then(() => (isResolved ? undefined : Promise.reject(new Error('HEALTH_CHECK_TIMEOUT')))), ]); } catch (err) { - // eslint-disable-next-line no-console - console.error(err); + logDebugError('ensureWorkerPing', err); if (Date.now() - startedAt >= HEALTH_CHECK_MIN_DELAY) { worker?.terminate(); diff --git a/src/components/main/sections/Card/Card.module.scss b/src/components/main/sections/Card/Card.module.scss index 9c2fdc77..202f6cbd 100644 --- a/src/components/main/sections/Card/Card.module.scss +++ b/src/components/main/sections/Card/Card.module.scss @@ -238,6 +238,10 @@ .icon { font-size: 1rem; vertical-align: -0.1875rem; + + &:global(.icon-ledger) { + margin-inline-end: 0.25rem; + } } .menuItem { @@ -367,13 +371,13 @@ } .tokenName { - display: block; + display: flex; margin-top: 0.25rem; font-size: 0.8125rem; font-weight: 500; - line-height: 1; + line-height: 0.9375rem; color: var(--color-card-second-text); } @@ -427,6 +431,7 @@ font-size: 0.625rem; font-weight: 700; + line-height: 1; color: var(--color-card-apy-text); background-color: var(--color-card-apy-background); diff --git a/src/components/main/sections/Card/CardAddress.tsx b/src/components/main/sections/Card/CardAddress.tsx index da58601d..84cc01ae 100644 --- a/src/components/main/sections/Card/CardAddress.tsx +++ b/src/components/main/sections/Card/CardAddress.tsx @@ -24,9 +24,10 @@ import styles from './Card.module.scss'; interface StateProps { addressByChain?: Account['addressByChain']; isTestnet?: boolean; + isHardwareAccount?: boolean; } -function CardAddress({ addressByChain, isTestnet }: StateProps) { +function CardAddress({ addressByChain, isTestnet, isHardwareAccount }: StateProps) { const { showNotification } = getActions(); const lang = useLang(); @@ -143,6 +144,7 @@ function CardAddress({ addressByChain, isTestnet }: StateProps) { aria-label={lang('Copy wallet address')} onClick={() => handleCopyAddress(addressByChain![chain])} > + {isHardwareAccount && } {shortenAddress(addressByChain![chain])} @@ -161,10 +163,11 @@ function CardAddress({ addressByChain, isTestnet }: StateProps) { } export default memo(withGlobal((global): StateProps => { - const { addressByChain } = selectAccount(global, global.currentAccountId!) || {}; + const { addressByChain, isHardware } = selectAccount(global, global.currentAccountId!) || {}; return { addressByChain, isTestnet: global.settings.isTestnet, + isHardwareAccount: isHardware, }; })(CardAddress)); diff --git a/src/components/settings/SettingsDeveloperOptions.tsx b/src/components/settings/SettingsDeveloperOptions.tsx index ec3d3b07..c8ccb342 100644 --- a/src/components/settings/SettingsDeveloperOptions.tsx +++ b/src/components/settings/SettingsDeveloperOptions.tsx @@ -4,6 +4,8 @@ import { getActions } from '../../global'; import type { ApiNetwork } from '../../api/types'; import buildClassName from '../../util/buildClassName'; +import { getLogs } from '../../util/logs'; +import { callApi } from '../../api'; import useLang from '../../hooks/useLang'; import useLastCallback from '../../hooks/useLastCallback'; @@ -39,6 +41,26 @@ function SettingsDeveloperOptions({ const lang = useLang(); const currentNetwork = NETWORK_OPTIONS[isTestnet ? 1 : 0].value; + const handleLogClick = useLastCallback(async () => { + const workerLogs = await callApi('getLogs') || []; + const uiLogs = getLogs(); + const logsString = JSON.stringify( + [...workerLogs, ...uiLogs].sort((a, b) => a.timestamp - b.timestamp), + undefined, + 2, + ); + + const blob = new Blob([logsString], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + + const link = document.createElement('a'); + link.href = url; + link.download = `mytonwallet_logs_${Date.now()}.json`; + link.click(); + + URL.revokeObjectURL(url); + }); + const handleNetworkChange = useLastCallback((newNetwork: string) => { if (currentNetwork === newNetwork) { return; @@ -84,6 +106,12 @@ function SettingsDeveloperOptions({ )} +
+
+ {lang('Download Logs')} +
+
+ )} + +

+ {lang('Reinstall biometrics in your device\'s system settings, or use a passcode.')} +

+ +
+ + +
+
); } diff --git a/src/components/ui/RichNumberField.tsx b/src/components/ui/RichNumberField.tsx index 7a728b4e..7fb72d7e 100644 --- a/src/components/ui/RichNumberField.tsx +++ b/src/components/ui/RichNumberField.tsx @@ -6,6 +6,7 @@ import React, { import { FRACTION_DIGITS } from '../../config'; import buildClassName from '../../util/buildClassName'; +import useFontScale from '../../hooks/useFontScale'; import useLastCallback from '../../hooks/useLastCallback'; import { buildContentHtml, getInputRegex } from './RichNumberInput'; @@ -27,7 +28,9 @@ type OwnProps = { children?: TeactNode; }; -function RichNumberInput({ +const MIN_LENGTH_FOR_SHRINK = 5; + +function RichNumberField({ id, labelText, value, @@ -44,6 +47,7 @@ function RichNumberInput({ // eslint-disable-next-line no-null/no-null const contentRef = useRef(null); const prevValueRef = useRef(''); + const { updateFontScale, isFontChangedRef } = useFontScale(contentRef, true); const renderValue = useLastCallback((inputValue = '', noFallbackToPrev = false) => { const contentEl = contentRef.current!; @@ -62,12 +66,17 @@ function RichNumberInput({ return; } - + const textContent = values?.[0] || ''; prevValueRef.current = inputValue; - contentEl.innerHTML = buildContentHtml({ + const content = buildContentHtml({ values, suffix, decimals, withRadix: true, }); + contentEl.innerHTML = content; + + if (textContent.length > MIN_LENGTH_FOR_SHRINK || isFontChangedRef.current) { + updateFontScale(content); + } }); useLayoutEffect(() => { @@ -120,4 +129,4 @@ function RichNumberInput({ ); } -export default memo(RichNumberInput); +export default memo(RichNumberField); diff --git a/src/components/ui/RichNumberInput.tsx b/src/components/ui/RichNumberInput.tsx index be23dd54..d4e95508 100644 --- a/src/components/ui/RichNumberInput.tsx +++ b/src/components/ui/RichNumberInput.tsx @@ -4,11 +4,12 @@ import React, { } from '../../lib/teact/teact'; import { FRACTION_DIGITS, TONCOIN, WHOLE_PART_DELIMITER } from '../../config'; -import { forceMeasure, requestMutation } from '../../lib/fasterdom/fasterdom'; +import { requestMutation } from '../../lib/fasterdom/fasterdom'; import buildClassName from '../../util/buildClassName'; import { saveCaretPosition } from '../../util/saveCaretPosition'; import useFlag from '../../hooks/useFlag'; +import useFontScale from '../../hooks/useFontScale'; import useLang from '../../hooks/useLang'; import useLastCallback from '../../hooks/useLastCallback'; @@ -37,8 +38,6 @@ type OwnProps = { }; const MIN_LENGTH_FOR_SHRINK = 5; -const MIN_SIZE_SCALE = 0.25; // 12px -const measureEl = document.createElement('div'); function RichNumberInput({ id, @@ -66,33 +65,7 @@ function RichNumberInput({ const lang = useLang(); const [hasFocus, markHasFocus, unmarkHasFocus] = useFlag(false); const [isContentEditable, setContentEditable] = useState(!disabled); - const isFontShrinkedRef = useRef(false); - - const updateFontScale = useLastCallback((content: string) => { - const input = inputRef.current!; - - forceMeasure(() => { - const { clientWidth: width } = input; - measureEl.className = buildClassName(input.className, 'measure-hidden'); - measureEl.style.width = `${width}px`; - measureEl.innerHTML = content; - document.body.appendChild(measureEl); - let delta = 1; - - while (delta > MIN_SIZE_SCALE) { - measureEl.style.setProperty('--base-font-size', delta.toString()); - if (measureEl.scrollWidth <= width) { - break; - } - delta -= 0.05; - } - - isFontShrinkedRef.current = delta < 1; - document.body.removeChild(measureEl); - measureEl.className = ''; - input.style.setProperty('--base-font-size', delta.toString()); - }); - }); + const { updateFontScale, isFontChangedRef } = useFontScale(inputRef); const handleLoadingHtml = useLastCallback((input: HTMLInputElement, parts?: RegExpMatchArray) => { const newHtml = parts ? buildContentHtml({ values: parts, suffix, decimals }) : ''; @@ -120,7 +93,7 @@ function RichNumberInput({ const content = isLoading ? handleLoadingHtml(input, parts) : handleNumberHtml(input, parts); const textContent = parts?.[0] || ''; - if (textContent.length > MIN_LENGTH_FOR_SHRINK || isFontShrinkedRef.current) { + if (textContent.length > MIN_LENGTH_FOR_SHRINK || isFontChangedRef.current) { updateFontScale(content); } if (content.length) { diff --git a/src/config.ts b/src/config.ts index c9145a0c..611e6a09 100644 --- a/src/config.ts +++ b/src/config.ts @@ -117,7 +117,7 @@ export const PROXY_HOSTS = process.env.PROXY_HOSTS; export const TINY_TRANSFER_MAX_COST = 0.01; -export const LANG_CACHE_NAME = 'mtw-lang-132'; +export const LANG_CACHE_NAME = 'mtw-lang-134'; export const LANG_LIST: LangItem[] = [{ langCode: 'en', diff --git a/src/electron/window.ts b/src/electron/window.ts index 279cf244..06a137b8 100644 --- a/src/electron/window.ts +++ b/src/electron/window.ts @@ -38,6 +38,7 @@ export function createWindow() { webPreferences: { preload: path.join(__dirname, 'preload.js'), devTools: !IS_PRODUCTION, + backgroundThrottling: false, }, titleBarStyle: 'hidden', ...(IS_MAC_OS && { diff --git a/src/global/actions/ui/initial.ts b/src/global/actions/ui/initial.ts index feea84d5..5dd2d9b0 100644 --- a/src/global/actions/ui/initial.ts +++ b/src/global/actions/ui/initial.ts @@ -39,6 +39,7 @@ import { selectNetworkAccounts, selectNetworkAccountsMemoized, selectNewestTxTimestamps, + selectSwapTokens, } from '../../selectors'; const ANIMATION_DELAY_MS = 320; @@ -154,7 +155,12 @@ addActionHandler('dismissDialog', (global) => { addActionHandler('selectToken', (global, actions, { slug } = {}) => { if (slug) { - if (slug === TONCOIN.slug) { + const isToncoin = slug === TONCOIN.slug; + const tokens = selectSwapTokens(global); + + if (!isToncoin && !tokens?.find((token) => token.slug === slug)) return undefined; + + if (isToncoin) { actions.setDefaultSwapParams({ tokenInSlug: DEFAULT_SWAP_SECOND_TOKEN_SLUG, tokenOutSlug: slug }); } else { actions.setDefaultSwapParams({ tokenOutSlug: slug }); diff --git a/src/hooks/useFontScale.ts b/src/hooks/useFontScale.ts new file mode 100644 index 00000000..73f75785 --- /dev/null +++ b/src/hooks/useFontScale.ts @@ -0,0 +1,46 @@ +import { useRef } from '../lib/teact/teact'; + +import { forceMeasure } from '../lib/fasterdom/stricterdom'; +import buildClassName from '../util/buildClassName'; + +const MIN_SIZE_SCALE = 0.25; // 12px + +function useFontScale(inputRef: React.RefObject, shouldGetParentWidth?: boolean) { + const isFontChangedRef = useRef(false); + const measureEl = useRef(document.createElement('div')); + + const updateFontScale = (content: string) => { + const input = inputRef.current; + if (!input) return; + + forceMeasure(() => { + let { clientWidth: width } = shouldGetParentWidth ? input.parentElement! : input; + if (shouldGetParentWidth) { + const { paddingLeft, paddingRight } = getComputedStyle(input); + width -= parseFloat(paddingLeft) + parseFloat(paddingRight); + } + measureEl.current.className = buildClassName(input.className, 'measure-hidden'); + measureEl.current.style.width = `${width}px`; + measureEl.current.innerHTML = content; + document.body.appendChild(measureEl.current); + + let delta = 1; + + while (delta > MIN_SIZE_SCALE) { + measureEl.current.style.setProperty('--base-font-size', delta.toString()); + + if (measureEl.current.scrollWidth <= width) break; + delta -= 0.05; + } + + isFontChangedRef.current = delta < 1; + document.body.removeChild(measureEl.current); + measureEl.current.className = ''; + input.style.setProperty('--base-font-size', delta.toString()); + }); + }; + + return { updateFontScale, isFontChangedRef }; +} + +export default useFontScale; diff --git a/src/i18n/de.yaml b/src/i18n/de.yaml index c4ea967e..f79b59c0 100644 --- a/src/i18n/de.yaml +++ b/src/i18n/de.yaml @@ -433,6 +433,7 @@ Enabling biometric confirmation will reset the password.: Das Aktivieren der bio Biometric Registration: Biometrische Registrierung Step 1 of 2. Registration: Schritt 1 von 2. Registrierung Step 2 of 2. Verification: Schritt 2 von 2. Überprüfung +Download Logs: Protokolle herunterladen Biometric setup failed.: Biometrische Einrichtung fehlgeschlagen. Biometric confirmation failed.: Biometrische Bestätigung fehlgeschlagen. Failed to disable biometrics.: Deaktivierung der Biometrie fehlgeschlagen. @@ -673,3 +674,15 @@ Enter Secret Words: Geheimwörter eingeben Turn this off so you can manually download updates and verify signatures.: Schalten Sie dies aus, damit Sie Updates manuell herunterladen und Signaturen überprüfen können. InvalidMnemonic: Ihre mnemonischen Wörter sind ungültig NFT Menu: NFT-Menü +Wallets will not be locked.: Wallets werden nicht gesperrt. +Wallets will be automatically locked after 30 seconds of inactivity.: Wallets werden nach 30 Sekunden Inaktivität automatisch gesperrt. +Wallets will be automatically locked after 3 minutes of inactivity.: Wallets werden nach 3 Minuten Inaktivität automatisch gesperrt. +Wallets will be automatically locked after 30 minutes of inactivity.: Wallets werden nach 30 Minuten Inaktivität automatisch gesperrt. +Auto-Lock: Automatische Sperre +Never: Niemals +30 seconds: 30 Sekunden +3 minutes: 3 Minuten +30 minutes: 30 Minuten +Unlock: Entsperren +Biometric authentification failed: Biometrische Authentifizierung fehlgeschlagen +Reset biometrics in security settings, or use a passcode.: Setzen Sie die Biometrie in den Sicherheitseinstellungen zurück oder verwenden Sie einen Zugangscode. diff --git a/src/i18n/en.yaml b/src/i18n/en.yaml index 0deac787..28bba5e6 100644 --- a/src/i18n/en.yaml +++ b/src/i18n/en.yaml @@ -432,6 +432,7 @@ Enabling biometric confirmation will reset the password.: Enabling biometric con Biometric Registration: Biometric Registration Step 1 of 2. Registration: Step 1 of 2. Registration Step 2 of 2. Verification: Step 2 of 2. Verification +Download Logs: Download Logs Biometric setup failed.: Biometric setup failed. Biometric confirmation failed.: Biometric confirmation failed. Failed to disable biometrics.: Failed to disable biometrics. @@ -673,3 +674,15 @@ Enter Secret Words: Enter Secret Words Turn this off so you can manually download updates and verify signatures.: Turn this off so you can manually download updates and verify signatures. InvalidMnemonic: Your mnemonic words are invalid NFT Menu: NFT Menu +Wallets will not be locked.: Wallets will not be locked. +Wallets will be automatically locked after 30 seconds of inactivity.: Wallets will be automatically locked after 30 seconds of inactivity. +Wallets will be automatically locked after 3 minutes of inactivity.: Wallets will be automatically locked after 3 minutes of inactivity. +Wallets will be automatically locked after 30 minutes of inactivity.: Wallets will be automatically locked after 30 minutes of inactivity. +Auto-Lock: Auto-Lock +Never: Never +30 seconds: 30 seconds +3 minutes: 3 minutes +30 minutes: 30 minutes +Unlock: Unlock +Biometric authentification failed: Biometric authentification failed +Reset biometrics in security settings, or use a passcode.: Reset biometrics in security settings, or use a passcode. diff --git a/src/i18n/es.yaml b/src/i18n/es.yaml index 73a2738e..fbcd09d5 100644 --- a/src/i18n/es.yaml +++ b/src/i18n/es.yaml @@ -433,6 +433,7 @@ Enabling biometric confirmation will reset the password.: Al habilitar la confir Biometric Registration: Registro Biométrico Step 1 of 2. Registration: Paso 1 de 2. Registro Step 2 of 2. Verification: Paso 2 de 2. Verificación +Download Logs: Descargar registros Biometric setup failed.: Error en la configuración biométrica. Biometric confirmation failed.: Error de confirmación biométrica. Failed to disable biometrics.: No se pudo desactivar la biometría. @@ -671,3 +672,15 @@ Enter Secret Words: Ingresar Palabras Secretas Turn this off so you can manually download updates and verify signatures.: Desactiva esto para poder descargar manualmente las actualizaciones y verificar las firmas. InvalidMnemonic: Tus palabras mnemónicas son inválidas NFT Menu: Menú NFT +Wallets will not be locked.: Las billeteras no se bloquearán. +Wallets will be automatically locked after 30 seconds of inactivity.: Las billeteras se bloquearán automáticamente después de 30 segundos de inactividad. +Wallets will be automatically locked after 3 minutes of inactivity.: Las billeteras se bloquearán automáticamente después de 3 minutos de inactividad. +Wallets will be automatically locked after 30 minutes of inactivity.: Las billeteras se bloquearán automáticamente después de 30 minutos de inactividad. +Auto-Lock: Bloqueo automático +Never: Nunca +30 seconds: 30 segundos +3 minutes: 3 minutos +30 minutes: 30 minutos +Unlock: Desbloquear +Biometric authentification failed: Fallo en la autenticación biométrica +Reset biometrics in security settings, or use a passcode.: Restablezca la biometría en la configuración de seguridad, o use un código de acceso. diff --git a/src/i18n/pl.yaml b/src/i18n/pl.yaml index 723d3124..24be3905 100644 --- a/src/i18n/pl.yaml +++ b/src/i18n/pl.yaml @@ -436,6 +436,7 @@ Enabling biometric confirmation will reset the password.: Włączenie potwierdze Biometric Registration: Rejestracja biometryczna Step 1 of 2. Registration: Krok 1 z 2. Rejestracja Step 2 of 2. Verification: Krok 2 z 2. Weryfikacja +Download Logs: Pobierz logi Biometric setup failed.: Konfiguracja biometryczna nie powiodła się. Biometric confirmation failed.: Potwierdzenie biometryczne nie powiodło się. Failed to disable biometrics.: Nie udało się wyłączyć biometrii. @@ -678,3 +679,15 @@ Enter Secret Words: Wprowadź Tajne Słowa Turn this off so you can manually download updates and verify signatures.: Wyłącz to, aby móc ręcznie pobierać aktualizacje i weryfikować podpisy. InvalidMnemonic: Twoje słowa mnemoniczne są nieprawidłowe NFT Menu: Menu NFT +Wallets will not be locked.: Portfele nie będą zablokowane. +Wallets will be automatically locked after 30 seconds of inactivity.: Portfele zostaną automatycznie zablokowane po 30 sekundach braku aktywności. +Wallets will be automatically locked after 3 minutes of inactivity.: Portfele zostaną automatycznie zablokowane po 3 minutach braku aktywności. +Wallets will be automatically locked after 30 minutes of inactivity.: Portfele zostaną automatycznie zablokowane po 30 minutach braku aktywności. +Auto-Lock: Automatyczna blokada +Never: Nigdy +30 seconds: 30 sekund +3 minutes: 3 minuty +30 minutes: 30 minut +Unlock: Odblokować +Biometric authentification failed: Autoryzacja biometryczna nie powiodła się +Reset biometrics in security settings, or use a passcode.: Zresetuj biometrię w ustawieniach zabezpieczeń lub użyj kodu dostępu. diff --git a/src/i18n/ru.yaml b/src/i18n/ru.yaml index ad7ac481..b6977d57 100644 --- a/src/i18n/ru.yaml +++ b/src/i18n/ru.yaml @@ -437,6 +437,7 @@ Enabling biometric confirmation will reset the password.: Включение б Biometric Registration: Подключение биометрии Step 1 of 2. Registration: Шаг 1 из 2. Регистрация Step 2 of 2. Verification: Шаг 2 из 2. Проверка +Download Logs: Загрузить журнал Biometric setup failed.: Настроить биометрию не удалось. Biometric confirmation failed.: Подтвердить биометрию не удалось. Failed to disable biometrics.: Не удалось отключить биометрию. @@ -672,3 +673,15 @@ Enter Secret Words: Введите секретные слова Turn this off so you can manually download updates and verify signatures.: Отключите это, чтобы вручную загружать обновления и проверять подписи. InvalidMnemonic: Ваши введённые слова недействительны NFT Menu: Меню NFT +Wallets will not be locked.: Кошельки не будут заблокированы. +Wallets will be automatically locked after 30 seconds of inactivity.: Кошельки будут автоматически заблокированы через 30 секунд бездействия. +Wallets will be automatically locked after 3 minutes of inactivity.: Кошельки будут автоматически заблокированы через 3 минуты бездействия. +Wallets will be automatically locked after 30 minutes of inactivity.: Кошельки будут автоматически заблокированы через 30 минут бездействия. +Auto-Lock: Автоблокировка +Never: Никогда +30 seconds: 30 секунд +3 minutes: 3 минуты +30 minutes: 30 минут +Unlock: Разблокировать +Biometric authentification failed: Ошибка биометрической аутентификации +Reset biometrics in security settings, or use a passcode.: Сбросьте биометрию в настройках безопасности или используйте пароль. diff --git a/src/i18n/th.yaml b/src/i18n/th.yaml index 9668cd74..804f1d55 100644 --- a/src/i18n/th.yaml +++ b/src/i18n/th.yaml @@ -433,6 +433,7 @@ Enabling biometric confirmation will reset the password.: การเปิด Biometric Registration: ลงทะเบียนด้วย Biometric Step 1 of 2. Registration: ขั้นตอน 1 จาก 2. ลงทะเบียน Step 2 of 2. Verification: ขั้นตอน 2 จาก 2. ยืนยันความถูกต้อง +Download Logs: ดาวน์โหลดบันทึก Biometric setup failed.: การตั้งค่าความปลอดภัยด้วย Biometric ล้มเหลว. Biometric confirmation failed.: การยืนยันด้วย Biometric ล้มเหลว. Failed to disable biometrics.: การปิดการใช้งาน Biometrics ล้มเหลว. @@ -675,3 +676,15 @@ Enter Secret Words: ป้อนคำลับ Turn this off so you can manually download updates and verify signatures.: ปิดสิ่งนี้เพื่อให้คุณสามารถดาวน์โหลดการอัปเดตด้วยตนเองและตรวจสอบลายเซ็น InvalidMnemonic: คำช่วยจำของคุณไม่ถูกต้อง NFT Menu: เมนู NFT +Wallets will not be locked.: กระเป๋าสตางค์จะไม่ถูกล็อค +Wallets will be automatically locked after 30 seconds of inactivity.: กระเป๋าสตางค์จะถูกล็อคโดยอัตโนมัติหลังจากไม่มีการใช้งาน 30 วินาที +Wallets will be automatically locked after 3 minutes of inactivity.: กระเป๋าสตางค์จะถูกล็อคโดยอัตโนมัติหลังจากไม่มีการใช้งาน 3 นาที +Wallets will be automatically locked after 30 minutes of inactivity.: กระเป๋าสตางค์จะถูกล็อคโดยอัตโนมัติหลังจากไม่มีการใช้งาน 30 นาที +Auto-Lock: การล็อคอัตโนมัติ +Never: ไม่เคย +30 seconds: 30 วินาที +3 minutes: 3 นาที +30 minutes: 30 นาที +Unlock: ปลดล็อก +Biometric authentification failed: การตรวจสอบอัตลักษณ์ทางชีวภาพล้มเหลว +Reset biometrics in security settings, or use a passcode.: รีเซ็ตการตรวจสอบอัตลักษณ์ทางชีวภาพในการตั้งค่าความปลอดภัย หรือใช้รหัสผ่าน diff --git a/src/i18n/tr.yaml b/src/i18n/tr.yaml index c84d5d47..1c8018b9 100644 --- a/src/i18n/tr.yaml +++ b/src/i18n/tr.yaml @@ -432,6 +432,7 @@ Enabling biometric confirmation will reset the password.: Biyometrik onayın etk Biometric Registration: Biyometrik kayıt Step 1 of 2. Registration: Adım 1/2. Kayıt Step 2 of 2. Verification: Adım 2/2. Doğrulama +Download Logs: Kayıtları İndir Biometric setup failed.: Biyometrik kurulum başarısız oldu. Biometric confirmation failed.: Biyometrik onaylama başarısız oldu. Failed to disable biometrics.: Biyometrik onay devre dışı bırakılamadı. @@ -672,3 +673,14 @@ Enter Secret Words: Gizli Kelimeleri Girin Turn this off so you can manually download updates and verify signatures.: Bunu kapatın, böylece güncellemeleri manuel olarak indirip imzaları doğrulayabilirsiniz. InvalidMnemonic: Mnemonik kelimeleriniz geçersiz NFT Menu: NFT Menüsü +Wallets will not be locked.: Cüzdanlar kilitlenmeyecek. +Wallets will be automatically locked after 30 seconds of inactivity.: Cüzdanlar 30 saniye hareketsizlikten sonra otomatik olarak kilitlenecek. +Wallets will be automatically locked after 3 minutes of inactivity.: Cüzdanlar 3 dakika hareketsizlikten sonra otomatik olarak kilitlenecek. +Wallets will be automatically locked after 30 minutes of inactivity.: Cüzdanlar 30 dakika hareketsizlikten sonra otomatik olarak kilitlenecek. +Auto-Lock: Otomatik Kilit +Never: Asla +30 seconds: 30 saniye +3 minutes: 3 dakika +30 minutes: 30 dakika +Biometric authentification failed: Biyometrik kimlik doğrulama başarısız oldu +Reset biometrics in security settings, or use a passcode.: Güvenlik ayarlarında biyometrik kimlik doğrulamayı sıfırlayın veya bir şifre kullanın. diff --git a/src/i18n/uk.yaml b/src/i18n/uk.yaml index c6c49713..32605710 100644 --- a/src/i18n/uk.yaml +++ b/src/i18n/uk.yaml @@ -439,6 +439,7 @@ Enabling biometric confirmation will reset the password.: Увімкнення Biometric Registration: Підключення біометрії Step 1 of 2. Registration: Крок 1 із 2. Реєстрація Step 2 of 2. Verification: Крок 2 з 2. Перевірка +Download Logs: Завантажити журнали Biometric setup failed.: Налаштувати біометрію не вдалося. Biometric confirmation failed.: Підтвердити біометрію не вдалося. Failed to disable biometrics.: Не вдалося відключити біометрію. @@ -677,3 +678,15 @@ Enter Secret Words: Введіть секретні слова Turn this off so you can manually download updates and verify signatures.: Вимкніть це, щоб ви могли вручну завантажувати оновлення та перевіряти підписи. InvalidMnemonic: Ваші мнемонічні слова недійсні NFT Menu: Меню NFT +Wallets will not be locked.: Гаманці не будуть заблоковані. +Wallets will be automatically locked after 30 seconds of inactivity.: Гаманці будуть автоматично заблоковані через 30 секунд бездіяльності. +Wallets will be automatically locked after 3 minutes of inactivity.: Гаманці будуть автоматично заблоковані через 3 хвилини бездіяльності. +Wallets will be automatically locked after 30 minutes of inactivity.: Гаманці будуть автоматично заблоковані через 30 хвилин бездіяльності. +Auto-Lock: Автоблокування +Never: Ніколи +30 seconds: 30 секунд +3 minutes: 3 хвилини +30 minutes: 30 хвилин +Unlock: Розблокувати +Biometric authentification failed: Біометрична аутентифікація не вдалася +Reset biometrics in security settings, or use a passcode.: Скиньте біометрію в налаштуваннях безпеки або використовуйте код доступу. diff --git a/src/i18n/zh-Hans.yaml b/src/i18n/zh-Hans.yaml index ce779445..359b67c8 100644 --- a/src/i18n/zh-Hans.yaml +++ b/src/i18n/zh-Hans.yaml @@ -422,6 +422,7 @@ Enabling biometric confirmation will reset the password.: 启用生物识别确 Biometric Registration: 生物识别注册 Step 1 of 2. Registration: 第 1 步(共 2 步):注册 Step 2 of 2. Verification: 第 2 步(共 2 步):验证 +Download Logs: 下载日志 Biometric setup failed.: 生物识别设置失败。 Biometric confirmation failed.: 生物识别确认失败。 Failed to disable biometrics.: 无法禁用生物识别。 @@ -659,3 +660,15 @@ Enter Secret Words: 输入密语 Turn this off so you can manually download updates and verify signatures.: 关闭此功能以便手动下载更新并验证签名。 InvalidMnemonic: 您的助记词无效 NFT Menu: NFT菜单 +Wallets will not be locked.: 钱包不会被锁定。 +Wallets will be automatically locked after 30 seconds of inactivity.: 钱包将在30秒不活动后自动锁定。 +Wallets will be automatically locked after 3 minutes of inactivity.: 钱包将在3分钟不活动后自动锁定。 +Wallets will be automatically locked after 30 minutes of inactivity.: 钱包将在30分钟不活动后自动锁定。 +Auto-Lock: 自动锁定 +Never: 从不 +30 seconds: 30秒 +3 minutes: 3分钟 +30 minutes: 30分钟 +Unlock: 解锁 +Biometric authentification failed: 生物识别认证失败 +Reset biometrics in security settings, or use a passcode.: 在安全设置中重置生物识别,或使用密码。 diff --git a/src/i18n/zh-Hant.yaml b/src/i18n/zh-Hant.yaml index 63571ee6..8f6420e4 100644 --- a/src/i18n/zh-Hant.yaml +++ b/src/i18n/zh-Hant.yaml @@ -422,6 +422,7 @@ Enabling biometric confirmation will reset the password.: 啟用生物辨識確 Biometric Registration: 生物辨識註冊 Step 1 of 2. Registration: 第 1 步(共 2 步):註冊 Step 2 of 2. Verification: 第 2 步(共 2 步):驗證 +Download Logs: 下載日誌 Biometric setup failed.: 生物辨識設定失敗。 Biometric confirmation failed.: 生物辨識確認失敗。 Failed to disable biometrics.: 無法禁用生物識別。 @@ -659,3 +660,15 @@ Enter Secret Words: 輸入密語 Turn this off so you can manually download updates and verify signatures.: 關閉此功能以手動下載更新並驗證簽名。 InvalidMnemonic: 您的助記詞無效 NFT Menu: NFT選單 +Wallets will not be locked.: 錢包不會被鎖定。 +Wallets will be automatically locked after 30 seconds of inactivity.: 錢包將在30秒不活動後自動鎖定。 +Wallets will be automatically locked after 3 minutes of inactivity.: 錢包將在3分鐘不活動後自動鎖定。 +Wallets will be automatically locked after 30 minutes of inactivity.: 錢包將在30分鐘不活動後自動鎖定。 +Auto-Lock: 自動鎖定 +Never: 從不 +30 seconds: 30秒 +3 minutes: 3分鐘 +30 minutes: 30分鐘 +Unlock: 解鎖 +Biometric authentification failed: 生物識別認證失敗 +Reset biometrics in security settings, or use a passcode.: 在安全設定中重置生物識別,或使用密碼。 diff --git a/src/util/createPostMessageInterface.ts b/src/util/createPostMessageInterface.ts index 3cbdb8e3..8d0a47c4 100644 --- a/src/util/createPostMessageInterface.ts +++ b/src/util/createPostMessageInterface.ts @@ -197,14 +197,14 @@ function isTransferable(obj: any) { function handleErrors(sendToOrigin: SendToOrigin) { self.onerror = (e) => { - // eslint-disable-next-line no-console - console.error(e); - sendToOrigin({ type: 'unhandledError', error: { message: e.error?.message || 'Uncaught exception in worker' } }); + const message = e.error?.message || 'Uncaught exception in worker'; + logDebugError(message, e.error); + sendToOrigin({ type: 'unhandledError', error: { message } }); }; self.addEventListener('unhandledrejection', (e) => { - // eslint-disable-next-line no-console - console.error(e); - sendToOrigin({ type: 'unhandledError', error: { message: e.reason?.message || 'Uncaught rejection in worker' } }); + const message = e.reason?.message || 'Uncaught exception in worker'; + logDebugError(message, e.reason); + sendToOrigin({ type: 'unhandledError', error: { message } }); }); } diff --git a/src/util/handleError.ts b/src/util/handleError.ts index 23c0ce66..3a3497e2 100644 --- a/src/util/handleError.ts +++ b/src/util/handleError.ts @@ -1,4 +1,5 @@ import { APP_ENV, DEBUG_ALERT_MSG } from '../config'; +import { logDebugError } from './logs'; import { throttle } from './schedulers'; const noop = () => { @@ -23,8 +24,7 @@ function handleErrorEvent(e: ErrorEvent | PromiseRejectionEvent) { } export function handleError(err: Error | string) { - // eslint-disable-next-line no-console - console.error(err); + logDebugError('Unhadled UI Error', err); const message = typeof err === 'string' ? err : err.message; const stack = typeof err === 'object' ? err.stack : undefined; diff --git a/src/util/logs.ts b/src/util/logs.ts index 918b6734..4369d399 100644 --- a/src/util/logs.ts +++ b/src/util/logs.ts @@ -1,6 +1,42 @@ import { DEBUG, DEBUG_API } from '../config'; import { omit } from './iteratees'; +interface Log { + message: string; + args: any[]; + timestamp: number; +} + +const MAX_LOG_LENGTH = 999; +const logs: Log[] = []; + +export function errorReplacer(_: string, value: any) { + if (value instanceof Error) { + return { + name: value.name, + message: value.message, + stack: value.stack, + }; + } + return value; +} + +export function addLog(log: Omit) { + if (logs.length > MAX_LOG_LENGTH) { + logs.shift(); + } + + logs.push({ + ...log, + args: log.args.map((arg) => JSON.stringify(arg, errorReplacer)), + timestamp: Date.now(), + }); +} + +export function getLogs() { + return logs; +} + export function logActionHandling(name: string, payload?: any) { if (!DEBUG_API) return; if (name === 'apiUpdate') { @@ -13,6 +49,7 @@ export function logActionHandling(name: string, payload?: any) { } export function logDebugError(message: string, ...args: any[]) { + addLog({ message, args }); if (DEBUG) { // eslint-disable-next-line no-console console.error(`[DEBUG][${message}]`, ...args); @@ -20,6 +57,7 @@ export function logDebugError(message: string, ...args: any[]) { } export function logDebug(message: any, ...args: any[]) { + addLog({ message, args }); if (DEBUG) { // eslint-disable-next-line no-console console.log(`[DEBUG] ${message}`, ...args);