diff --git a/ci/test.yml b/ci/test.yml index 19611299dff..5c8616bec0f 100644 --- a/ci/test.yml +++ b/ci/test.yml @@ -18,6 +18,7 @@ - docker login $CI_DEPENDENCY_PROXY_SERVER -u $CI_DEPENDENCY_PROXY_USER -p $CI_DEPENDENCY_PROXY_PASSWORD script: - yarn install --pure-lockfile --cache-folder .yarn --prefer-offline + - yarn workspace @trezor/utils build:lib - docker-compose pull - docker-compose up -d ${CONTAINERS} - docker-compose run test-run @@ -128,6 +129,7 @@ rollout: COMPOSE_FILE: ./docker/docker-compose.transport-test.yml script: - yarn install --pure-lockfile --cache-folder .yarn --prefer-offline + - yarn workspace @trezor/utils build:lib - yarn workspace @trezor/transport build:lib - ./docker/docker-transport-test.sh after_script: @@ -148,6 +150,7 @@ transport nightly: stage: integration testing script: - yarn install --frozen-lockfile --cache-folder .yarn --prefer-offline + - yarn workspace @trezor/utils build:lib - yarn workspace @trezor/blockchain-link build:lib - yarn workspace @trezor/blockchain-link build:workers - yarn workspace @trezor/blockchain-link test:integration diff --git a/packages/blockchain-link/package.json b/packages/blockchain-link/package.json index bc9bb5c9be6..53bc58373e7 100644 --- a/packages/blockchain-link/package.json +++ b/packages/blockchain-link/package.json @@ -49,6 +49,7 @@ "worker-loader": "^3.0.8" }, "dependencies": { + "@trezor/utils": "1.0.0", "bignumber.js": "^9.0.1", "events": "^3.3.0", "ripple-lib": "1.10.0", diff --git a/packages/blockchain-link/src/index.ts b/packages/blockchain-link/src/index.ts index bb3d0c063c1..5f92d5265bb 100644 --- a/packages/blockchain-link/src/index.ts +++ b/packages/blockchain-link/src/index.ts @@ -1,7 +1,7 @@ import { EventEmitter } from 'events'; +import { createDeferred, Deferred } from '@trezor/utils/lib/createDeferred'; import { CustomError } from './constants/errors'; import { MESSAGES, RESPONSES } from './constants'; -import { create as createDeferred, Deferred } from './utils/deferred'; import type { BlockchainSettings } from './types'; import type * as ResponseTypes from './types/responses'; import type * as MessageTypes from './types/messages'; diff --git a/packages/blockchain-link/src/workers/blockbook/websocket.ts b/packages/blockchain-link/src/workers/blockbook/websocket.ts index 21d1664ade8..25e522f1246 100644 --- a/packages/blockchain-link/src/workers/blockbook/websocket.ts +++ b/packages/blockchain-link/src/workers/blockbook/websocket.ts @@ -1,7 +1,8 @@ import * as WebSocket from 'ws'; import { EventEmitter } from 'events'; +import { createDeferred, Deferred } from '@trezor/utils/lib/createDeferred'; + import { CustomError } from '../../constants/errors'; -import { create as createDeferred, Deferred } from '../../utils/deferred'; import type { BlockNotification, AddressNotification, diff --git a/packages/blockchain-link/src/workers/blockfrost/websocket.ts b/packages/blockchain-link/src/workers/blockfrost/websocket.ts index 4eade9d6978..c4ef44a99c4 100644 --- a/packages/blockchain-link/src/workers/blockfrost/websocket.ts +++ b/packages/blockchain-link/src/workers/blockfrost/websocket.ts @@ -1,7 +1,8 @@ import * as WebSocket from 'ws'; import { EventEmitter } from 'events'; +import { createDeferred, Deferred } from '@trezor/utils/lib/createDeferred'; + import { CustomError } from '../../constants/errors'; -import { create as createDeferred, Deferred } from '../../utils/deferred'; import type { Send, BlockContent, BlockfrostTransaction } from '../../types/blockfrost'; import type { AccountInfoParams, diff --git a/packages/integration-tests/websocket-client.js b/packages/integration-tests/websocket-client.js index 157851a321f..e3ec0886493 100644 --- a/packages/integration-tests/websocket-client.js +++ b/packages/integration-tests/websocket-client.js @@ -1,25 +1,9 @@ const WebSocket = require('ws'); const { EventEmitter } = require('events'); +const { createDeferred } = require('@trezor/utils'); const NOT_INITIALIZED = new Error('websocket_not_initialized'); -const createDeferred = id => { - let localResolve = t => () => {}; - let localReject = e => () => {}; - - const promise = new Promise((resolve, reject) => { - localResolve = resolve; - localReject = reject; - }); - - return { - id, - resolve: localResolve, - reject: localReject, - promise, - }; -}; - // Making the timeout high because the controller in trezor-user-env // must synchronously run actions on emulator and they may take a long time // (for example in case of Shamir backup) diff --git a/packages/suite-desktop/package.json b/packages/suite-desktop/package.json index b90cf06d588..9e5a465e34f 100644 --- a/packages/suite-desktop/package.json +++ b/packages/suite-desktop/package.json @@ -153,6 +153,7 @@ "afterSign": "scripts/notarize.js" }, "dependencies": { + "@trezor/utils": "*", "chalk": "^4.1.2", "electron-localshortcut": "^3.2.1", "electron-store": "^8.0.1", diff --git a/packages/suite-desktop/src-electron/libs/info.ts b/packages/suite-desktop/src-electron/libs/info.ts index abb7a1a9319..8832114b180 100644 --- a/packages/suite-desktop/src-electron/libs/info.ts +++ b/packages/suite-desktop/src-electron/libs/info.ts @@ -3,7 +3,7 @@ import si from 'systeminformation'; import { isDev } from '@suite-utils/build'; import { b2t } from '@desktop-electron/libs/utils'; -import { toHumanReadable } from '@suite-utils/file'; +import { bytesToHumanReadable } from '@trezor/utils'; export const buildInfo = () => { global.logger.info('build', [ @@ -41,6 +41,6 @@ export const computerInfo = async () => { `- Cores: ${cpu.processors}x${cpu.physicalCores}(+${cpu.cores - cpu.physicalCores}) @ ${ cpu.speed }GHz`, - `- RAM: ${toHumanReadable(mem.total)}`, + `- RAM: ${bytesToHumanReadable(mem.total)}`, ]); }; diff --git a/packages/suite-desktop/src-electron/modules/auto-updater.ts b/packages/suite-desktop/src-electron/modules/auto-updater.ts index 3cfcbfbed97..99832ef8b75 100644 --- a/packages/suite-desktop/src-electron/modules/auto-updater.ts +++ b/packages/suite-desktop/src-electron/modules/auto-updater.ts @@ -14,7 +14,7 @@ import { import { isDev } from '@suite-utils/build'; import { b2t } from '@desktop-electron/libs/utils'; import { verifySignature } from '@desktop-electron/libs/update-checker'; -import { toHumanReadable } from '@suite-utils/file'; +import { bytesToHumanReadable } from '@trezor/utils'; import { isEnabled } from '@suite-utils/features'; // Runtime flags @@ -142,9 +142,9 @@ const init = ({ mainWindow, store }: Dependencies) => { autoUpdater.on('download-progress', progressObj => { logger.debug( 'auto-updater', - `Downloading ${progressObj.percent}% (${toHumanReadable( + `Downloading ${progressObj.percent}% (${bytesToHumanReadable( progressObj.transferred, - )}/${toHumanReadable(progressObj.total)})`, + )}/${bytesToHumanReadable(progressObj.total)})`, ); mainWindow.webContents.send('update/downloading', { ...progressObj }); }); diff --git a/packages/suite-desktop/src/support/DesktopUpdater/Downloading.tsx b/packages/suite-desktop/src/support/DesktopUpdater/Downloading.tsx index 46bbfef4162..5615e4ff6c5 100644 --- a/packages/suite-desktop/src/support/DesktopUpdater/Downloading.tsx +++ b/packages/suite-desktop/src/support/DesktopUpdater/Downloading.tsx @@ -5,7 +5,7 @@ import { H2, variables } from '@trezor/components'; import { Translation, Modal } from '@suite-components'; import { Row } from './styles'; -import { toHumanReadable } from '@suite-utils/file'; +import { bytesToHumanReadable } from '@trezor/utils'; import { UpdateProgress } from '@suite-types/desktop'; const ModalHeadingWrapper = styled.div` @@ -76,9 +76,9 @@ const Downloading = ({ hideWindow, progress }: Props) => { - {toHumanReadable(progress?.transferred || 0)} + {bytesToHumanReadable(progress?.transferred || 0)} - /{toHumanReadable(progress?.total || 0)} + /{bytesToHumanReadable(progress?.total || 0)} )} diff --git a/packages/suite/package.json b/packages/suite/package.json index dacaf33ad01..00e3f60053f 100644 --- a/packages/suite/package.json +++ b/packages/suite/package.json @@ -26,6 +26,7 @@ "@trezor/components": "1.0.0", "@trezor/suite-data": "1.0.0", "@trezor/suite-storage": "1.0.0", + "@trezor/utils": "*", "bignumber.js": "^9.0.2", "date-fns": "^2.27.0", "dropbox": "^10.23.0", diff --git a/packages/suite/src/actions/backup/__tests__/backupActions.test.ts b/packages/suite/src/actions/backup/__tests__/backupActions.test.ts index a4d8af247cd..6a4e15ecf37 100644 --- a/packages/suite/src/actions/backup/__tests__/backupActions.test.ts +++ b/packages/suite/src/actions/backup/__tests__/backupActions.test.ts @@ -4,7 +4,7 @@ import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import { mergeObj } from '@suite-utils/mergeObj'; +import { mergeObject } from '@trezor/utils'; import { init } from '@suite-actions/trezorConnectActions'; import { SUITE } from '@suite-actions/constants'; import { BACKUP } from '@backup-actions/constants'; @@ -52,7 +52,7 @@ export const getInitialState = (override: any) => { devices: [], }; if (override) { - return mergeObj(defaults, override); + return mergeObject(defaults, override); } return defaults; }; diff --git a/packages/suite/src/actions/suite/__tests__/analyticsActions.test.ts b/packages/suite/src/actions/suite/__tests__/analyticsActions.test.ts index 4ec128ae61d..ed14893aee3 100644 --- a/packages/suite/src/actions/suite/__tests__/analyticsActions.test.ts +++ b/packages/suite/src/actions/suite/__tests__/analyticsActions.test.ts @@ -48,9 +48,6 @@ const oldWindowLocation = window.location; describe('Analytics Actions', () => { beforeAll(() => { - // eslint-disable-next-line global-require - require('@suite-utils/random'); - // @ts-ignore The operand of a 'delete' operator must be optional. delete window.location; diff --git a/packages/suite/src/actions/suite/metadataActions.ts b/packages/suite/src/actions/suite/metadataActions.ts index 830e8cf864d..bc5af557cab 100644 --- a/packages/suite/src/actions/suite/metadataActions.ts +++ b/packages/suite/src/actions/suite/metadataActions.ts @@ -1,6 +1,6 @@ import TrezorConnect from 'trezor-connect'; +import { createDeferred } from '@trezor/utils'; import { METADATA } from '@suite-actions/constants'; -import { createDeferred } from '@suite-utils/deferred'; import { Dispatch, GetState } from '@suite-types'; import { MetadataProviderType, diff --git a/packages/suite/src/actions/suite/modalActions.ts b/packages/suite/src/actions/suite/modalActions.ts index ac3e5793a74..3f415254138 100644 --- a/packages/suite/src/actions/suite/modalActions.ts +++ b/packages/suite/src/actions/suite/modalActions.ts @@ -1,8 +1,8 @@ import TrezorConnect, { UI } from 'trezor-connect'; +import { createDeferred, Deferred, DeferredResponse } from '@trezor/utils'; import { MODAL, SUITE } from '@suite-actions/constants'; import { Route, Dispatch, GetState, TrezorDevice } from '@suite-types'; import { Account, WalletAccountTransaction } from '@wallet-types'; -import { createDeferred, Deferred, DeferredResponse } from '@suite-utils/deferred'; export type UserContextPayload = | { diff --git a/packages/suite/src/components/suite/NotificationRenderer/renderers/UriSchemeRenderers.tsx b/packages/suite/src/components/suite/NotificationRenderer/renderers/UriSchemeRenderers.tsx index e5e00d19e13..948e803ef80 100644 --- a/packages/suite/src/components/suite/NotificationRenderer/renderers/UriSchemeRenderers.tsx +++ b/packages/suite/src/components/suite/NotificationRenderer/renderers/UriSchemeRenderers.tsx @@ -5,7 +5,7 @@ import * as protocolActions from '@suite-actions/protocolActions'; import { Translation } from '@suite-components'; import { CoinLogo } from '@trezor/components'; import { useActions, useSelector } from '@suite-hooks'; -import { capitalizeFirstLetter } from '@suite-utils/string'; +import { capitalizeFirstLetter } from '@trezor/utils'; import { PROTOCOL_TO_NETWORK } from '@suite-constants/protocol'; import ConditionalActionRenderer from './ConditionalActionRenderer'; diff --git a/packages/suite/src/components/suite/modals/AddAccount/index.tsx b/packages/suite/src/components/suite/modals/AddAccount/index.tsx index b239160a571..7a2ecfde463 100644 --- a/packages/suite/src/components/suite/modals/AddAccount/index.tsx +++ b/packages/suite/src/components/suite/modals/AddAccount/index.tsx @@ -9,7 +9,7 @@ import { useSelector, useActions } from '@suite-hooks'; import * as accountActions from '@wallet-actions/accountActions'; import * as walletSettingsActions from '@settings-actions/walletSettingsActions'; import * as routerActions from '@suite-actions/routerActions'; -import { partition } from '@suite-utils/array'; +import { arrayPartition } from '@trezor/utils'; import { AccountTypeSelect } from './components/AccountTypeSelect'; import { SelectNetwork } from './components/SelectNetwork'; @@ -68,12 +68,12 @@ const AddAccountModal = ({ device, onCancel, symbol, noRedirect }: Props) => { const selectedNetworkEnabled = !!selectedNetwork && enabledNetworksSymbols.includes(selectedNetwork.symbol); - const [enabledNetworks, disabledNetworks] = partition(internalNetworks, network => + const [enabledNetworks, disabledNetworks] = arrayPartition(internalNetworks, network => enabledNetworksSymbols.includes(network.symbol), ); const hasDisabledNetworks = !!disabledNetworks?.length; - const [disabledMainnetNetworks, disabledTestnetNetworks] = partition( + const [disabledMainnetNetworks, disabledTestnetNetworks] = arrayPartition( disabledNetworks, network => !network?.testnet, ); diff --git a/packages/suite/src/components/suite/modals/Passphrase/components/PassphraseTypeCard/index.tsx b/packages/suite/src/components/suite/modals/Passphrase/components/PassphraseTypeCard/index.tsx index 4a79a3dd57a..abd9368c842 100644 --- a/packages/suite/src/components/suite/modals/Passphrase/components/PassphraseTypeCard/index.tsx +++ b/packages/suite/src/components/suite/modals/Passphrase/components/PassphraseTypeCard/index.tsx @@ -6,7 +6,7 @@ import styled, { css } from 'styled-components'; import { Button, useTheme, variables, Input, Tooltip, Checkbox, Icon } from '@trezor/components'; import { Translation } from '@suite-components/Translation'; import { MAX_LENGTH } from '@suite-constants/inputs'; -import { countBytesInString } from '@suite-utils/string'; +import { countBytesInString } from '@trezor/utils'; import { OpenGuideFromTooltip } from '@guide-views'; import PasswordStrengthIndicator from '@suite-components/PasswordStrengthIndicator'; import { useTranslation } from '@suite-hooks'; diff --git a/packages/suite/src/components/suite/modals/TransactionDetail/components/ChangeFee/components/AffectedTransactions/index.tsx b/packages/suite/src/components/suite/modals/TransactionDetail/components/ChangeFee/components/AffectedTransactions/index.tsx index 8864cd6f7b7..c8f0b1a50ce 100644 --- a/packages/suite/src/components/suite/modals/TransactionDetail/components/ChangeFee/components/AffectedTransactions/index.tsx +++ b/packages/suite/src/components/suite/modals/TransactionDetail/components/ChangeFee/components/AffectedTransactions/index.tsx @@ -4,7 +4,7 @@ import { Icon, Button, useTheme, variables } from '@trezor/components'; import { FormattedCryptoAmount, Sign, Translation, FormattedDate } from '@suite-components'; import { useRbfContext } from '@wallet-hooks/useRbfForm'; import { useLayoutSize } from '@suite-hooks/useLayoutSize'; -import { truncateMiddle } from '@suite-utils/string'; +import { truncateMiddle } from '@trezor/utils'; import GreyCard from '../GreyCard'; import WarnHeader from '../WarnHeader'; diff --git a/packages/suite/src/components/suite/modals/confirm/CoinmarketBuyTerms/index.tsx b/packages/suite/src/components/suite/modals/confirm/CoinmarketBuyTerms/index.tsx index 7b51532c0e2..2341bdd43ae 100644 --- a/packages/suite/src/components/suite/modals/confirm/CoinmarketBuyTerms/index.tsx +++ b/packages/suite/src/components/suite/modals/confirm/CoinmarketBuyTerms/index.tsx @@ -2,7 +2,7 @@ import { Button, Icon, variables, Checkbox, H3 } from '@trezor/components'; import React, { useState } from 'react'; import { Translation, Modal } from '@suite-components'; import styled, { css } from 'styled-components'; -import { Deferred } from '@suite-utils/deferred'; +import type { Deferred } from '@trezor/utils'; const Text = styled.div<{ isLast?: boolean; isFirst?: boolean }>` padding: 20px 0; diff --git a/packages/suite/src/components/suite/modals/confirm/CoinmarketExchangeDexTerms/index.tsx b/packages/suite/src/components/suite/modals/confirm/CoinmarketExchangeDexTerms/index.tsx index 2e29b9f01c8..af453524a9a 100644 --- a/packages/suite/src/components/suite/modals/confirm/CoinmarketExchangeDexTerms/index.tsx +++ b/packages/suite/src/components/suite/modals/confirm/CoinmarketExchangeDexTerms/index.tsx @@ -2,7 +2,7 @@ import { Button, Icon, variables, Checkbox } from '@trezor/components'; import React, { useState } from 'react'; import { Translation, Modal } from '@suite-components'; import styled, { css } from 'styled-components'; -import { Deferred } from '@suite-utils/deferred'; +import type { Deferred } from '@trezor/utils'; const Text = styled.div<{ isLast?: boolean; isFirst?: boolean }>` padding: 20px 0; diff --git a/packages/suite/src/components/suite/modals/confirm/CoinmarketExchangeTerms/index.tsx b/packages/suite/src/components/suite/modals/confirm/CoinmarketExchangeTerms/index.tsx index 124353be97f..ed888c293aa 100644 --- a/packages/suite/src/components/suite/modals/confirm/CoinmarketExchangeTerms/index.tsx +++ b/packages/suite/src/components/suite/modals/confirm/CoinmarketExchangeTerms/index.tsx @@ -2,7 +2,7 @@ import { Button, Icon, variables, Checkbox, H3 } from '@trezor/components'; import React, { useState } from 'react'; import { Translation, Modal } from '@suite-components'; import styled, { css } from 'styled-components'; -import { Deferred } from '@suite-utils/deferred'; +import type { Deferred } from '@trezor/utils'; const Text = styled.div<{ isLast?: boolean; isFirst?: boolean }>` padding: 20px 0; diff --git a/packages/suite/src/components/suite/modals/confirm/CoinmarketSellTerms/index.tsx b/packages/suite/src/components/suite/modals/confirm/CoinmarketSellTerms/index.tsx index 01eaaa70a9b..5f415157a3e 100644 --- a/packages/suite/src/components/suite/modals/confirm/CoinmarketSellTerms/index.tsx +++ b/packages/suite/src/components/suite/modals/confirm/CoinmarketSellTerms/index.tsx @@ -2,7 +2,7 @@ import { Button, Icon, variables, Checkbox, H3 } from '@trezor/components'; import React, { useState } from 'react'; import { Translation, Modal } from '@suite-components'; import styled, { css } from 'styled-components'; -import { Deferred } from '@suite-utils/deferred'; +import type { Deferred } from '@trezor/utils'; const Text = styled.div<{ isLast?: boolean; isFirst?: boolean }>` padding: 20px 0; diff --git a/packages/suite/src/components/suite/modals/metadata/MetadataProvider/index.tsx b/packages/suite/src/components/suite/modals/metadata/MetadataProvider/index.tsx index 2ec1049a537..ec033dd1493 100644 --- a/packages/suite/src/components/suite/modals/metadata/MetadataProvider/index.tsx +++ b/packages/suite/src/components/suite/modals/metadata/MetadataProvider/index.tsx @@ -5,7 +5,7 @@ import { P, Button, variables } from '@trezor/components'; import { Translation, Modal } from '@suite-components'; import { useActions } from '@suite-hooks'; import * as metadataActions from '@suite-actions/metadataActions'; -import { Deferred } from '@suite-utils/deferred'; +import type { Deferred } from '@trezor/utils'; import { MetadataProviderType } from '@suite-types/metadata'; import { isEnabled } from '@suite-utils/features'; diff --git a/packages/suite/src/hooks/settings/backends/useBackendsForm.ts b/packages/suite/src/hooks/settings/backends/useBackendsForm.ts index 93db612d9ff..a7475f38a83 100644 --- a/packages/suite/src/hooks/settings/backends/useBackendsForm.ts +++ b/packages/suite/src/hooks/settings/backends/useBackendsForm.ts @@ -1,7 +1,7 @@ import { useState } from 'react'; import { useForm } from 'react-hook-form'; import { useActions, useSelector, useTranslation } from '@suite-hooks'; -import { isUrl } from '@suite-utils/validators'; +import { isUrl } from '@trezor/utils'; import { setBackend as setBackendAction } from '@settings-actions/walletSettingsActions'; import type { Network } from '@wallet-types'; import type { BackendType } from '@wallet-reducers/settingsReducer'; diff --git a/packages/suite/src/hooks/suite/useAsyncDebounce.ts b/packages/suite/src/hooks/suite/useAsyncDebounce.ts index b82f6607efd..fe7e7d9c026 100644 --- a/packages/suite/src/hooks/suite/useAsyncDebounce.ts +++ b/packages/suite/src/hooks/suite/useAsyncDebounce.ts @@ -1,5 +1,5 @@ import { useCallback, useRef } from 'react'; -import { createDeferred } from '@suite-utils/deferred'; +import { createDeferred } from '@trezor/utils'; type TimeoutType = ReturnType; // resolves to Timeout type in react-native, number otherwise diff --git a/packages/suite/src/hooks/wallet/sign-verify/useSignVerifyForm.ts b/packages/suite/src/hooks/wallet/sign-verify/useSignVerifyForm.ts index 4f5eb085975..a5ef0f83d58 100644 --- a/packages/suite/src/hooks/wallet/sign-verify/useSignVerifyForm.ts +++ b/packages/suite/src/hooks/wallet/sign-verify/useSignVerifyForm.ts @@ -3,7 +3,7 @@ import { useForm, useController } from 'react-hook-form'; import { useTranslation, useSelector, useActions } from '@suite-hooks'; import * as protocolActions from '@suite-actions/protocolActions'; import { isHex } from '@wallet-utils/ethUtils'; -import { isASCII } from '@suite-utils/validators'; +import { isAscii } from '@trezor/utils'; import { isAddressValid } from '@wallet-utils/validation'; import type { Account } from '@wallet-types'; import type { AoppState } from '@suite-reducers/protocolReducer'; @@ -114,7 +114,7 @@ export const useSignVerifyForm = (page: 'sign' | 'verify', account?: Account) => ? translationString('DATA_NOT_VALID_HEX') : undefined, ascii: (message: string) => - !formValues.hex && !isASCII(message) + !formValues.hex && !isAscii(message) ? translationString('TR_ASCII_ONLY') : undefined, }, diff --git a/packages/suite/src/reducers/suite/resizeReducer.ts b/packages/suite/src/reducers/suite/resizeReducer.ts index 65301c9daff..606481b8f01 100644 --- a/packages/suite/src/reducers/suite/resizeReducer.ts +++ b/packages/suite/src/reducers/suite/resizeReducer.ts @@ -1,15 +1,15 @@ import produce from 'immer'; import * as variables from '@trezor/components/lib/config/variables'; // can't import from index cause it would import all UI components import { RESIZE } from '@suite-actions/constants'; -import { getNumberFromPxString } from '@suite-utils/string'; +import { getNumberFromPixelString } from '@trezor/utils'; import { Action } from '@suite-types'; const sizes = { - UNAVAILABLE: getNumberFromPxString(variables.SCREEN_SIZE.UNAVAILABLE), - SMALL: getNumberFromPxString(variables.SCREEN_SIZE.SM), - MEDIUM: getNumberFromPxString(variables.SCREEN_SIZE.MD), - LARGE: getNumberFromPxString(variables.SCREEN_SIZE.LG), - XLARGE: getNumberFromPxString(variables.SCREEN_SIZE.XL), + UNAVAILABLE: getNumberFromPixelString(variables.SCREEN_SIZE.UNAVAILABLE), + SMALL: getNumberFromPixelString(variables.SCREEN_SIZE.SM), + MEDIUM: getNumberFromPixelString(variables.SCREEN_SIZE.MD), + LARGE: getNumberFromPixelString(variables.SCREEN_SIZE.LG), + XLARGE: getNumberFromPixelString(variables.SCREEN_SIZE.XL), }; const getSize = (screenWidth: number | null): State['size'] => { diff --git a/packages/suite/src/reducers/suite/suiteReducer.ts b/packages/suite/src/reducers/suite/suiteReducer.ts index bca69bfbf1b..a73ad42eee8 100644 --- a/packages/suite/src/reducers/suite/suiteReducer.ts +++ b/packages/suite/src/reducers/suite/suiteReducer.ts @@ -7,7 +7,7 @@ import { Action, TrezorDevice, Lock, SuiteThemeColors } from '@suite-types'; import type { Locale } from '@suite-config/languages'; import { isWeb, getWindowWidth } from '@suite-utils/env'; import { ensureLocale } from '@suite-utils/l10n'; -import { getNumberFromPxString } from '@suite-utils/string'; +import { getNumberFromPixelString } from '@trezor/utils'; export interface DebugModeOptions { invityAPIUrl?: string; @@ -79,7 +79,8 @@ const initialState: SuiteState = { taprootBannerClosed: false, securityStepsHidden: false, dashboardGraphHidden: false, - dashboardAssetsGridMode: getWindowWidth() < getNumberFromPxString(variables.SCREEN_SIZE.SM), + dashboardAssetsGridMode: + getWindowWidth() < getNumberFromPixelString(variables.SCREEN_SIZE.SM), }, settings: { theme: { diff --git a/packages/suite/src/reducers/wallet/discoveryReducer.ts b/packages/suite/src/reducers/wallet/discoveryReducer.ts index 5386bc5092a..a0be96fbadf 100644 --- a/packages/suite/src/reducers/wallet/discoveryReducer.ts +++ b/packages/suite/src/reducers/wallet/discoveryReducer.ts @@ -1,7 +1,7 @@ import produce from 'immer'; import { DISCOVERY } from '@wallet-actions/constants'; import { STORAGE } from '@suite-actions/constants'; -import { Deferred, createDeferred } from '@suite-utils/deferred'; +import { Deferred, createDeferred } from '@trezor/utils'; import { ObjectValues } from '@suite/types/utils'; import { Action as SuiteAction } from '@suite-types'; import { WalletAction, Network } from '@wallet-types'; diff --git a/packages/suite/src/services/suite/metadata/DropboxProvider.ts b/packages/suite/src/services/suite/metadata/DropboxProvider.ts index 49591c5c3b6..a1a417325f9 100644 --- a/packages/suite/src/services/suite/metadata/DropboxProvider.ts +++ b/packages/suite/src/services/suite/metadata/DropboxProvider.ts @@ -4,7 +4,7 @@ import type { users } from 'dropbox'; import { AbstractMetadataProvider } from '@suite-types/metadata'; import { extractCredentialsFromAuthorizationFlow, getOauthReceiverUrl } from '@suite-utils/oauth'; import { METADATA } from '@suite-actions/constants'; -import { getRandomId } from '@suite-utils/random'; +import { getWeakRandomId } from '@trezor/utils'; // this is incorrectly typed in dropbox @@ -54,7 +54,7 @@ class DropboxProvider extends AbstractMetadataProvider { const url = await this.auth.getAuthenticationUrl( redirectUrl, - getRandomId(10), + getWeakRandomId(10), 'code', 'offline', undefined, // If this parameter is omitted, the authorization page will request all scopes selected on the Permissions tab diff --git a/packages/suite/src/utils/suite/__tests__/random.test.ts b/packages/suite/src/utils/suite/__tests__/random.test.ts index 20dbfa1e4a2..a170b4ddba5 100644 --- a/packages/suite/src/utils/suite/__tests__/random.test.ts +++ b/packages/suite/src/utils/suite/__tests__/random.test.ts @@ -1,25 +1,6 @@ -import { getRandomId, getCodeChallenge } from '../random'; +import { getCodeChallenge } from '../random'; describe('random', () => { - describe('getRandomId', () => { - it('should return random id of fixed length', () => { - expect(getRandomId(12)).toHaveLength(12); - }); - it('should generate few results which should be unique', () => { - const ids: any = {}; - for (let i = 0; i < 10; i++) { - const random = getRandomId(10); - if (!ids[random]) { - ids[random] = 0; - } - ids[random]++; - } - Object.values(ids).forEach(id => { - expect(id).toEqual(1); - }); - }); - }); - describe('getCodeChallenge', () => { it('should match regexp [0-9a-zA-Z-._~], {43,128}', () => { expect(getCodeChallenge()).toMatch(/[0-9a-zA-Z\-._~]{128}/); diff --git a/packages/suite/src/utils/suite/__tests__/string.test.ts b/packages/suite/src/utils/suite/__tests__/string.test.ts deleted file mode 100644 index 97d155d73ad..00000000000 --- a/packages/suite/src/utils/suite/__tests__/string.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import * as stringUtils from '../string'; - -it('capitalizeFirstLetter', () => { - expect(stringUtils.capitalizeFirstLetter('god')).toBe('God'); - expect(stringUtils.capitalizeFirstLetter('dog')).toBe('Dog'); -}); - -it('countBytesInString', () => { - expect(stringUtils.countBytesInString('aaa')).toBe(3); - expect(stringUtils.countBytesInString('č')).toBe(2); - expect(stringUtils.countBytesInString('😀')).toBe(4); - expect(stringUtils.countBytesInString('+ěčřěžšýžřšý')).toBe(23); -}); - -it('getNumberFromPxString', () => { - expect(stringUtils.getNumberFromPxString('1px')).toBe(1); - expect(stringUtils.getNumberFromPxString('1')).toBe(1); -}); diff --git a/packages/suite/src/utils/suite/__tests__/validators.test.ts b/packages/suite/src/utils/suite/__tests__/validators.test.ts deleted file mode 100644 index 31a50979e94..00000000000 --- a/packages/suite/src/utils/suite/__tests__/validators.test.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { isEmail, isASCII, hasDecimals, hasUppercase, isNumber } from '../validators'; - -describe('utils/suite/validators', () => { - describe('isEmail', () => { - it('should return true for valid email', () => { - expect(isEmail('satoshi@nakamoto.io')).toEqual(true); - }); - - it('should return false for invalid email', () => { - const fooMails = ['', 'satoshi', 'satoshi@seznam', 'satoshi @foo.io']; - fooMails.forEach(mail => { - expect(isEmail(mail)).toEqual(false); - }); - }); - }); - - describe('isASCII', () => { - it('should return true for ASCII only string', () => { - expect(isASCII('this is only ascii')).toEqual(true); - }); - - it('should return true when called without parameter', () => { - expect(isASCII()).toEqual(true); - }); - - it('should return false strings with non ASCII chars', () => { - const fooStrings = ['¥', 'železniční přejezd']; - fooStrings.forEach(str => { - expect(isASCII(str)).toEqual(false); - }); - }); - }); - - it('hasDecimals', () => { - expect(hasDecimals('0', 18)).toBe(true); - expect(hasDecimals('0.0', 18)).toBe(true); - expect(hasDecimals('0.00000000', 18)).toBe(true); - expect(hasDecimals('0.00000001', 18)).toBe(true); - expect(hasDecimals('+0.0', 18)).toBe(false); - expect(hasDecimals('-0.0', 18)).toBe(false); - expect(hasDecimals('1', 18)).toBe(true); - expect(hasDecimals('+1', 18)).toBe(false); - expect(hasDecimals('+100000', 18)).toBe(false); - expect(hasDecimals('.', 18)).toBe(false); - expect(hasDecimals('-.1', 18)).toBe(false); - expect(hasDecimals('0.1', 18)).toBe(true); - expect(hasDecimals('0.12314841', 18)).toBe(true); - expect(hasDecimals('0.1381841848184814818391931933', 18)).toBe(false); // 28 decimals - expect(hasDecimals('0.100000000000000000', 18)).toBe(true); // 18s decimals - - expect(hasDecimals('100.', 18)).toBe(true); - expect(hasDecimals('.1', 18)).toBe(false); - expect(hasDecimals('.000000001', 18)).toBe(false); - expect(hasDecimals('.13134818481481841', 18)).toBe(false); - - expect(hasDecimals('001.12314841', 18)).toBe(false); - expect(hasDecimals('83819319391491949941', 18)).toBe(true); - expect(hasDecimals('-83819319391491949941', 18)).toBe(false); - expect(hasDecimals('+0.131831848184', 18)).toBe(false); - expect(hasDecimals('0.127373193981774718318371831731761626162613', 18)).toBe(false); - - expect(hasDecimals('0.131831848184a', 18)).toBe(false); - expect(hasDecimals('100a', 18)).toBe(false); - expect(hasDecimals('.100a', 18)).toBe(false); - expect(hasDecimals('a.100', 18)).toBe(false); - expect(hasDecimals('abc', 18)).toBe(false); - expect(hasDecimals('1abc0', 18)).toBe(false); - }); - - it('hasDecimals decimals=0', () => { - expect(hasDecimals('0', 0)).toBe(true); - expect(hasDecimals('0.1', 0)).toBe(false); - expect(hasDecimals('0.12345', 0)).toBe(false); - expect(hasDecimals('1', 0)).toBe(true); - expect(hasDecimals('1.1', 0)).toBe(false); - expect(hasDecimals('1000000', 0)).toBe(true); - expect(hasDecimals('-1000000', 0)).toBe(false); - expect(hasDecimals('.0', 0)).toBe(false); - expect(hasDecimals('0.', 0)).toBe(false); - expect(hasDecimals('.', 0)).toBe(false); - }); - - it('hasUppercase', () => { - expect(hasUppercase('0')).toBe(false); - expect(hasUppercase('abc')).toBe(false); - expect(hasUppercase('abcD')).toBe(true); - expect(hasUppercase('Abcd')).toBe(true); - expect(hasUppercase('aBcd')).toBe(true); - expect(hasUppercase('123abc123')).toBe(false); - expect(hasUppercase('0x123abc456')).toBe(false); - expect(hasUppercase('0x123aBc456')).toBe(true); - }); - - it('isNumber', () => { - expect(isNumber('0')).toBe(true); - expect(isNumber('0.0')).toBe(true); - expect(isNumber('0.00000000')).toBe(true); - expect(isNumber('0.00000001')).toBe(true); - expect(isNumber('+0.0')).toBe(false); - expect(isNumber('-0.0')).toBe(false); - expect(isNumber('1')).toBe(true); - expect(isNumber('+1')).toBe(false); - expect(isNumber('+100000')).toBe(false); - expect(isNumber('.')).toBe(false); - expect(isNumber('')).toBe(false); - expect(isNumber(' ')).toBe(false); - expect(isNumber('-.1')).toBe(false); - expect(isNumber('0.1')).toBe(true); - expect(isNumber('0.12314841')).toBe(true); - expect(isNumber('0.1381841848184814818391931933')).toBe(true); // 28 decimals - expect(isNumber('0.100000000000000000')).toBe(true); // 18s decimals - - expect(isNumber('100.')).toBe(true); - expect(isNumber('.1')).toBe(false); - expect(isNumber('.000000001')).toBe(false); - expect(isNumber('.13134818481481841')).toBe(false); - - expect(isNumber('001.12314841')).toBe(false); - expect(isNumber('83819319391491949941')).toBe(true); - expect(isNumber('-83819319391491949941')).toBe(false); - expect(isNumber('+0.131831848184')).toBe(false); - - expect(isNumber('0.131831848184a')).toBe(false); - expect(isNumber('100a')).toBe(false); - expect(isNumber('.100a')).toBe(false); - expect(isNumber('a.100')).toBe(false); - expect(isNumber('abc')).toBe(false); - expect(isNumber('1abc0')).toBe(false); - }); -}); diff --git a/packages/suite/src/utils/suite/deferred.ts b/packages/suite/src/utils/suite/deferred.ts deleted file mode 100644 index 820904dd302..00000000000 --- a/packages/suite/src/utils/suite/deferred.ts +++ /dev/null @@ -1,28 +0,0 @@ -export interface Deferred { - data?: Data; - promise: Promise; - resolve: (t: Response) => void; - reject: (e: Error) => void; -} - -// unwrap promise response from Deferred -export type DeferredResponse = D extends Deferred ? R : never; - -export const createDeferred = ( - data?: Data, -): Deferred => { - let localResolve: (t: Response) => void = () => {}; - let localReject: (e: Error) => void = () => {}; - - const promise: Promise = new Promise((resolve, reject) => { - localResolve = resolve; - localReject = reject; - }); - - return { - data, - resolve: localResolve, - reject: localReject, - promise, - }; -}; diff --git a/packages/suite/src/utils/suite/oauth.ts b/packages/suite/src/utils/suite/oauth.ts index 8587cb5e152..80f0ea8f362 100644 --- a/packages/suite/src/utils/suite/oauth.ts +++ b/packages/suite/src/utils/suite/oauth.ts @@ -2,7 +2,7 @@ import { getPrefixedURL } from '@suite-utils/router'; import { METADATA } from '@suite-actions/constants'; -import { Deferred, createDeferred } from '@suite-utils/deferred'; +import { Deferred, createDeferred } from '@trezor/utils'; import { urlHashParams, urlSearchParams } from '@suite-utils/metadata'; /** diff --git a/packages/suite/src/utils/suite/random.ts b/packages/suite/src/utils/suite/random.ts index 7cbc3799eaf..24e74263ead 100644 --- a/packages/suite/src/utils/suite/random.ts +++ b/packages/suite/src/utils/suite/random.ts @@ -1,20 +1,9 @@ -// todo: rework to something cryptographically stronger, probably use random bytes from crypto node module? -export const getRandomId = (length: number) => { - let id = ''; - const list = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < length; i++) { - id += list.charAt(Math.floor(Math.random() * list.length)); - } - return id; -}; +import { getWeakRandomId } from '@trezor/utils'; -export const getAnalyticsRandomId = () => getRandomId(10); +export const getAnalyticsRandomId = () => getWeakRandomId(10); /** * Generate code_challenge for Oauth2 * Authorization code with PKCE flow */ -export const getCodeChallenge = () => getRandomId(128); - -export const getRandomNumberInRange = (min: number, max: number) => - Math.floor(Math.random() * (max - min + 1)) + min; +export const getCodeChallenge = () => getWeakRandomId(128); diff --git a/packages/suite/src/utils/suite/validators.ts b/packages/suite/src/utils/suite/validators.ts deleted file mode 100644 index 0e4db4013a7..00000000000 --- a/packages/suite/src/utils/suite/validators.ts +++ /dev/null @@ -1,44 +0,0 @@ -// RFC 5322 - http://emailregex.com/ -const EMAIL_REGEX = - /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; -// Simple URL regex -const URL_REGEX = - /^(http|ws)s?:\/\/[a-z0-9]([a-z0-9.-]+)?(:[0-9]{1,5})?((\/)?(([a-z0-9-_])+(\/)?)+)$/i; - -export function isEmail(value?: string): boolean { - if (!value) return false; - return EMAIL_REGEX.test(value); -} - -export const isUrl = (value: string): boolean => URL_REGEX.test(value); - -export function isASCII(value?: string): boolean { - if (!value) return true; - return /^[\x00-\x7F]*$/.test(value); // eslint-disable-line -} - -export const hasUppercase = (value: string) => { - const UPPERCASE_RE = new RegExp('^(.*[A-Z].*)$'); - return UPPERCASE_RE.test(value); -}; - -export const isAbs = (value: string) => { - const ABS_RE = new RegExp('^[0-9]+$'); - return ABS_RE.test(value); -}; - -export const hasDecimals = (value: string, decimals: number) => { - if (decimals === 0) { - return isAbs(value); - } - - const ETH_DECIMALS_RE = new RegExp( - `^(0|0\\.([0-9]{0,${decimals}})?|[1-9][0-9]*\\.?([0-9]{0,${decimals}})?)$`, - ); - return ETH_DECIMALS_RE.test(value); -}; - -export const isNumber = (value: string) => { - const NUMBER_RE = new RegExp(`^(0|0\\.([0-9]+)?|[1-9][0-9]*\\.?([0-9]+)?)$`); - return NUMBER_RE.test(value); -}; diff --git a/packages/suite/src/utils/wallet/ethUtils.ts b/packages/suite/src/utils/wallet/ethUtils.ts index d1f200148c5..eedac91b411 100644 --- a/packages/suite/src/utils/wallet/ethUtils.ts +++ b/packages/suite/src/utils/wallet/ethUtils.ts @@ -1,4 +1,5 @@ import * as EthereumjsUtil from 'ethereumjs-util'; +import { hasUppercaseLetter } from '@trezor/utils'; import BigNumber from 'bignumber.js'; export const decimalToHex = (dec: number): string => new BigNumber(dec).toString(16); @@ -24,14 +25,13 @@ export const strip = (str: string): string => { }; export const validateAddress = (address: string): string | null => { - const hasUpperCase = new RegExp('^(.*[A-Z].*)$'); if (address.length < 1) { return 'Address is not set'; } if (!EthereumjsUtil.isValidAddress(address)) { return 'Address is not valid'; } - if (address.match(hasUpperCase) && !EthereumjsUtil.isValidChecksumAddress(address)) { + if (hasUppercaseLetter(address) && !EthereumjsUtil.isValidChecksumAddress(address)) { return 'Address is not a valid checksum'; } return null; diff --git a/packages/suite/src/utils/wallet/promiseUtils.ts b/packages/suite/src/utils/wallet/promiseUtils.ts deleted file mode 100644 index 1fd353af2ab..00000000000 --- a/packages/suite/src/utils/wallet/promiseUtils.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const resolveAfter = (msec: number, ...params: any[]): Promise => - new Promise(resolve => { - setTimeout(resolve, msec, ...params); - }); diff --git a/packages/suite/src/views/settings/index.tsx b/packages/suite/src/views/settings/index.tsx index 8d20e124173..ed6c2c20d9a 100644 --- a/packages/suite/src/views/settings/index.tsx +++ b/packages/suite/src/views/settings/index.tsx @@ -17,7 +17,7 @@ import { import { FIAT } from '@suite-config'; import { useAnalytics, useDevice, useSelector, useActions } from '@suite-hooks'; import { Button, Tooltip, Switch, Link } from '@trezor/components'; -import { capitalizeFirstLetter } from '@suite-utils/string'; +import { capitalizeFirstLetter } from '@trezor/utils'; import * as suiteActions from '@suite-actions/suiteActions'; import * as walletSettingsActions from '@settings-actions/walletSettingsActions'; diff --git a/packages/suite/src/views/wallet/transactions/components/TransactionList/components/NoSearchResults/index.tsx b/packages/suite/src/views/wallet/transactions/components/TransactionList/components/NoSearchResults/index.tsx index 0377eedeb5e..5d505b77ef3 100644 --- a/packages/suite/src/views/wallet/transactions/components/TransactionList/components/NoSearchResults/index.tsx +++ b/packages/suite/src/views/wallet/transactions/components/TransactionList/components/NoSearchResults/index.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import styled from 'styled-components'; import { Card, variables } from '@trezor/components'; import { Translation } from '@suite-components'; -import { getRandomNumberInRange } from '@suite-utils/random'; +import { getRandomNumberInRange } from '@trezor/utils'; const NoResults = styled(Card)` display: flex; diff --git a/packages/transport/src/lowlevel/sharedConnectionWorker.ts b/packages/transport/src/lowlevel/sharedConnectionWorker.ts index e8f5424e08c..e890f56ccec 100644 --- a/packages/transport/src/lowlevel/sharedConnectionWorker.ts +++ b/packages/transport/src/lowlevel/sharedConnectionWorker.ts @@ -6,9 +6,7 @@ // about intent to acquire/release and then send another message when that is done. // Other windows then can acquire/release -// @ts-ignore -import { create as createDefered } from '../utils/defered'; -import type { Deferred } from '../utils/defered'; +import { createDeferred, Deferred } from '@trezor/utils'; import type { TrezorDeviceInfoDebug } from './sharedPlugin'; import type { MessageFromSharedWorker, MessageToSharedWorker } from './withSharedConnections'; @@ -34,7 +32,7 @@ let waitPromise: Promise = Promise.resolve(); type PortObject = { postMessage: (message: Object) => void }; function startLock(): void { - const newLock = createDefered(); + const newLock = createDeferred(); lock = newLock; setTimeout(() => newLock.reject(new Error(`Timed out`)), 10 * 1000); } diff --git a/packages/transport/src/lowlevel/withSharedConnections.ts b/packages/transport/src/lowlevel/withSharedConnections.ts index 6e8d3e05502..ff171f6fe8f 100644 --- a/packages/transport/src/lowlevel/withSharedConnections.ts +++ b/packages/transport/src/lowlevel/withSharedConnections.ts @@ -1,12 +1,12 @@ // @ts-nocheck -import { create as createDefered, resolveTimeoutPromise } from '../utils/defered'; +import { createDeferred, Deferred } from '@trezor/utils'; +import { resolveTimeoutPromise } from '../utils/defered'; import { parseConfigure } from './protobuf/messages'; import { buildAndSend } from './send'; import { receiveAndParse } from './receive'; import type { LowlevelTransportSharedPlugin, TrezorDeviceInfoDebug } from './sharedPlugin'; -import type { Deferred } from '../utils/defered'; import type { MessageFromTrezor, TrezorDeviceInfoWithSession, AcquireInput } from '../types'; import { postModuleMessage } from './sharedConnectionWorker'; @@ -253,9 +253,9 @@ export default class LowlevelTransportWithSharedConnections { const session: string = messBack2.number; if (debugLink) { - this.deferedDebugOnRelease[session] = createDefered(); + this.deferedDebugOnRelease[session] = createDeferred(); } else { - this.deferedNormalOnRelease[session] = createDefered(); + this.deferedNormalOnRelease[session] = createDeferred(); } return session; } @@ -430,7 +430,7 @@ export default class LowlevelTransportWithSharedConnections { this.latestId++; const id = this.latestId; - this.defereds[id] = createDefered(); + this.defereds[id] = createDeferred(); // when shared worker is not loaded as a shared loader, use it as a module instead if (this.sharedWorker != null) { diff --git a/packages/transport/src/utils/defered.ts b/packages/transport/src/utils/defered.ts index 3cbbb9b441e..46b34223177 100644 --- a/packages/transport/src/utils/defered.ts +++ b/packages/transport/src/utils/defered.ts @@ -1,30 +1,4 @@ -export type Deferred = { - promise: Promise; - resolve: (t: T) => void; - reject: (e: Error) => void; - rejectingPromise: Promise; -}; - -export function create(): Deferred { - let localResolve: (t: T) => void = () => {}; - let localReject: (e?: Error) => void = () => {}; - - const promise = new Promise((resolve, reject) => { - localResolve = resolve; - localReject = reject; - }); - const rejectingPromise = promise.then(() => { - throw new Error(`Promise is always rejecting`); - }); - rejectingPromise.catch(() => {}); - - return { - resolve: localResolve, - reject: localReject, - promise, - rejectingPromise, - }; -} +// todo: move to @trezor/utils. probably "resolveAfter"? export function resolveTimeoutPromise(delay: number, result: T): Promise { return new Promise(resolve => { @@ -33,11 +7,3 @@ export function resolveTimeoutPromise(delay: number, result: T): Promise { }, delay); }); } - -export function rejectTimeoutPromise(delay: number, error: Error): Promise { - return new Promise((_resolve, reject) => { - setTimeout(() => { - reject(error); - }, delay); - }); -} diff --git a/packages/utils/.eslintrc.js b/packages/utils/.eslintrc.js new file mode 100644 index 00000000000..ee73f4a0b50 --- /dev/null +++ b/packages/utils/.eslintrc.js @@ -0,0 +1,6 @@ +module.exports = { + rules: { + 'no-console': 'warn', + 'import/no-default-export': 'error', + }, +}; diff --git a/packages/utils/LICENSE.md b/packages/utils/LICENSE.md new file mode 100644 index 00000000000..1bebd9dc53b --- /dev/null +++ b/packages/utils/LICENSE.md @@ -0,0 +1,50 @@ +# TREZOR REFERENCE SOURCE LICENSE (T-RSL) + +This license governs use of the accompanying software. If you use the software, +you accept this license. If you do not accept the license, do not use the +software. + +## 1. Definitions + +The terms "reproduce," "reproduction" and "distribution" have the same meaning +here as under U.S. copyright law. + +"You" means the licensee of the software. + +"Your company" means the company you worked for when you downloaded the +software. + +"Reference use" means use of the software within your company as a reference, +in read only form, for the sole purposes of debugging your products, +maintaining your products, or enhancing the interoperability of your products +with the software, and specifically excludes the right to distribute the +software outside of your company. + +"Licensed patents" means any Licensor patent claims which read directly on the +software as distributed by the Licensor under this license. + +## 2. Grant of Rights + +(A) Copyright Grant - Subject to the terms of this license, the Licensor grants +you a non-transferable, non-exclusive, worldwide, royalty-free copyright +license to reproduce the software for reference use. + +(B) Patent Grant - Subject to the terms of this license, the Licensor grants +you a non-transferable, non-exclusive, worldwide, royalty-free patent license +under licensed patents for reference use. + +## 3. Limitations + +(A) No Trademark License - This license does not grant you any rights to use +the Licensor's name, logo, or trademarks. + +(B) If you begin patent litigation against the Licensor over patents that you +think may apply to the software (including a cross-claim or counterclaim in +a lawsuit), your license to the software ends automatically. + +(C) The software is licensed "as-is." You bear the risk of using it. The +Licensor gives no express warranties, guarantees or conditions. You may have +additional consumer rights under your local laws which this license cannot +change. To the extent permitted under your local laws, the Licensor excludes +the implied warranties of merchantability, fitness for a particular purpose and +non-infringement. diff --git a/packages/utils/README.md b/packages/utils/README.md new file mode 100644 index 00000000000..63007ad8ffd --- /dev/null +++ b/packages/utils/README.md @@ -0,0 +1,3 @@ +# Trezor utils library (@trezor/utils) + +A collection of typescript utils that are intended to be used across trezor-suite monorepo. \ No newline at end of file diff --git a/packages/utils/jest.config.js b/packages/utils/jest.config.js new file mode 100644 index 00000000000..4dd314555ea --- /dev/null +++ b/packages/utils/jest.config.js @@ -0,0 +1,17 @@ +module.exports = { + rootDir: './', + globals: { + 'ts-jest': { + tsconfig: 'tsconfig.lib.json', + }, + }, + moduleFileExtensions: ['js', 'ts'], + testMatch: ['**/*.test.ts'], + coverageDirectory: './coverage/', + collectCoverage: true, + collectCoverageFrom: ['**/src/**/*.ts'], + modulePathIgnorePatterns: ['node_modules'], + transform: { + '^.+\\.ts$': 'ts-jest', + }, +}; diff --git a/packages/utils/package.json b/packages/utils/package.json new file mode 100644 index 00000000000..9e2ee70ab9e --- /dev/null +++ b/packages/utils/package.json @@ -0,0 +1,25 @@ +{ + "name": "@trezor/utils", + "version": "1.0.0", + "author": "Trezor ", + "homepage": "https://github.com/trezor/trezor-suite/packages/utils", + "description": "A collection of typescript utils that are intended to be used across trezor-suite monorepo.", + "license": "SEE LICENSE IN LICENSE.md", + "repository": { + "type": "git", + "url": "git://github.com/trezor/trezor-suite.git" + }, + "bugs": { + "url": "https://github.com/trezor/trezor-suite/issues" + }, + "main": "./lib/index.js", + "files": ["lib/"], + "types": "lib/index.d.ts", + "typings": "lib/index.d.ts", + "scripts": { + "lint": "eslint '**/*{.ts,.tsx}'", + "test:unit": "jest --verbose -c jest.config.js", + "type-check": "tsc --project tsconfig.json", + "build:lib": "rimraf lib && tsc --p ./tsconfig.lib.json" + } +} diff --git a/packages/suite/src/utils/suite/array.ts b/packages/utils/src/arrayPartition.ts similarity index 78% rename from packages/suite/src/utils/suite/array.ts rename to packages/utils/src/arrayPartition.ts index 75f1656bc99..fe735f3405a 100644 --- a/packages/suite/src/utils/suite/array.ts +++ b/packages/utils/src/arrayPartition.ts @@ -4,10 +4,9 @@ * @param condition Condition for inclusion in the first part. * @returns Array of two arrays - the items in the first array satisfy the condition and the rest is in the second array. Preserving original order. */ -export function partition(array: T[], condition: (elem: T) => boolean): [T[], T[]] { - return array.reduce( +export const arrayPartition = (array: T[], condition: (elem: T) => boolean): [T[], T[]] => + array.reduce( ([pass, fail], elem) => condition(elem) ? [[...pass, elem], fail] : [pass, [...fail, elem]], [[], []] as [T[], T[]], ); -} diff --git a/packages/suite/src/utils/suite/file.ts b/packages/utils/src/bytesToHumanReadable.ts similarity index 58% rename from packages/suite/src/utils/suite/file.ts rename to packages/utils/src/bytesToHumanReadable.ts index 8ba243032bc..b5be25b7c2c 100644 --- a/packages/suite/src/utils/suite/file.ts +++ b/packages/utils/src/bytesToHumanReadable.ts @@ -1,6 +1,11 @@ const units = ['B', 'KB', 'MB', 'GB', 'TB']; -export const toHumanReadable = (bytes: number): string => { +/** + * + * @param bytes amount fo bytes + * @returns String with the human redable size of bytes + */ +export const bytesToHumanReadable = (bytes: number): string => { let size = Math.abs(bytes); let i = 0; diff --git a/packages/utils/src/capitalizeFirstLetter.ts b/packages/utils/src/capitalizeFirstLetter.ts new file mode 100644 index 00000000000..ba4a60be0e1 --- /dev/null +++ b/packages/utils/src/capitalizeFirstLetter.ts @@ -0,0 +1 @@ +export const capitalizeFirstLetter = (s: string) => s.charAt(0).toUpperCase() + s.slice(1); diff --git a/packages/utils/src/countBytesInString.ts b/packages/utils/src/countBytesInString.ts new file mode 100644 index 00000000000..a750924b08c --- /dev/null +++ b/packages/utils/src/countBytesInString.ts @@ -0,0 +1 @@ +export const countBytesInString = (input: string) => encodeURI(input).split(/%..|./).length - 1; diff --git a/packages/blockchain-link/src/utils/deferred.ts b/packages/utils/src/createDeferred.ts similarity index 60% rename from packages/blockchain-link/src/utils/deferred.ts rename to packages/utils/src/createDeferred.ts index 440cd6a4058..33ac848c83b 100644 --- a/packages/blockchain-link/src/utils/deferred.ts +++ b/packages/utils/src/createDeferred.ts @@ -1,8 +1,5 @@ -export function create(id: number | string): Deferred { - // intentionally ignore below lines in test coverage, they will be overridden in promise creation - /* istanbul ignore next */ +export const createDeferred = (id?: P) => { let localResolve: (t: T) => void = () => {}; - /* istanbul ignore next */ let localReject: (e?: Error) => void = () => {}; const promise: Promise = new Promise((resolve, reject) => { @@ -16,11 +13,14 @@ export function create(id: number | string): Deferred { reject: localReject, promise, }; -} +}; -export interface Deferred { - id: number | string; +export interface Deferred { + id: P; promise: Promise; resolve: (t: T) => void; reject: (e: Error) => void; } + +// unwrap promise response from Deferred +export type DeferredResponse = D extends Deferred ? R : never; diff --git a/packages/utils/src/getNumberFromPixelString.ts b/packages/utils/src/getNumberFromPixelString.ts new file mode 100644 index 00000000000..cf11f31566f --- /dev/null +++ b/packages/utils/src/getNumberFromPixelString.ts @@ -0,0 +1,2 @@ +export const getNumberFromPixelString = (size: string): number => + parseInt(size.replace('px', ''), 10); diff --git a/packages/utils/src/getRandomNumberInRange.ts b/packages/utils/src/getRandomNumberInRange.ts new file mode 100644 index 00000000000..045e0789027 --- /dev/null +++ b/packages/utils/src/getRandomNumberInRange.ts @@ -0,0 +1,2 @@ +export const getRandomNumberInRange = (min: number, max: number) => + Math.floor(Math.random() * (max - min + 1)) + min; diff --git a/packages/utils/src/getWeakRandomId.ts b/packages/utils/src/getWeakRandomId.ts new file mode 100644 index 00000000000..e8b000799d4 --- /dev/null +++ b/packages/utils/src/getWeakRandomId.ts @@ -0,0 +1,8 @@ +export const getWeakRandomId = (length: number) => { + let id = ''; + const list = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < length; i++) { + id += list.charAt(Math.floor(Math.random() * list.length)); + } + return id; +}; diff --git a/packages/utils/src/hasUppercaseLetter.ts b/packages/utils/src/hasUppercaseLetter.ts new file mode 100644 index 00000000000..c743ad8fe2e --- /dev/null +++ b/packages/utils/src/hasUppercaseLetter.ts @@ -0,0 +1,3 @@ +const HAS_UPPERCASE_LATER_REGEXP = new RegExp('^(.*[A-Z].*)$'); + +export const hasUppercaseLetter = (value: string) => HAS_UPPERCASE_LATER_REGEXP.test(value); diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts new file mode 100644 index 00000000000..8ccb6f3e9fb --- /dev/null +++ b/packages/utils/src/index.ts @@ -0,0 +1,13 @@ +export * from './createDeferred'; +export * from './arrayPartition'; +export * from './bytesToHumanReadable'; +export * from './mergeObject'; +export * from './getWeakRandomId'; +export * from './getRandomNumberInRange'; +export * from './capitalizeFirstLetter'; +export * from './countBytesInString'; +export * from './truncateMiddle'; +export * from './getNumberFromPixelString'; +export * from './isUrl'; +export * from './hasUppercaseLetter'; +export * from './isAscii'; diff --git a/packages/utils/src/isAscii.ts b/packages/utils/src/isAscii.ts new file mode 100644 index 00000000000..8b3e3b23d70 --- /dev/null +++ b/packages/utils/src/isAscii.ts @@ -0,0 +1,4 @@ +export function isAscii(value?: string): boolean { + if (!value) return true; + return /^[\x00-\x7F]*$/.test(value); // eslint-disable-line no-control-regex +} diff --git a/packages/utils/src/isUrl.ts b/packages/utils/src/isUrl.ts new file mode 100644 index 00000000000..afe462c4142 --- /dev/null +++ b/packages/utils/src/isUrl.ts @@ -0,0 +1,4 @@ +const URL_REGEXP = + /^(http|ws)s?:\/\/[a-z0-9]([a-z0-9.-]+)?(:[0-9]{1,5})?((\/)?(([a-z0-9-_])+(\/)?)+)$/i; + +export const isUrl = (value: string): boolean => URL_REGEXP.test(value); diff --git a/packages/suite/src/utils/suite/mergeObj.ts b/packages/utils/src/mergeObject.ts similarity index 75% rename from packages/suite/src/utils/suite/mergeObj.ts rename to packages/utils/src/mergeObject.ts index d101d33a99a..b781927814f 100644 --- a/packages/suite/src/utils/suite/mergeObj.ts +++ b/packages/utils/src/mergeObject.ts @@ -1,10 +1,10 @@ -export const mergeObj = ( +export const mergeObject = ( target: Record, source: Record | string | boolean | number, ) => { Object.keys(source).forEach(key => { if (source instanceof Object && source[key] instanceof Object) { - Object.assign(source[key], mergeObj(target[key], source[key])); + Object.assign(source[key], mergeObject(target[key], source[key])); } }); // Join `target` and modified `source` diff --git a/packages/suite/src/utils/suite/string.ts b/packages/utils/src/truncateMiddle.ts similarity index 50% rename from packages/suite/src/utils/suite/string.ts rename to packages/utils/src/truncateMiddle.ts index 019aa989290..6ac3b3e4028 100644 --- a/packages/suite/src/utils/suite/string.ts +++ b/packages/utils/src/truncateMiddle.ts @@ -1,9 +1,3 @@ -export const capitalizeFirstLetter = (s: string) => s.charAt(0).toUpperCase() + s.slice(1); - -export const countBytesInString = (input: string) => encodeURI(input).split(/%..|./).length - 1; - -export const getNumberFromPxString = (size: string): number => parseInt(size.replace('px', ''), 10); - export const truncateMiddle = (text: string, startChars: number, endChars: number) => { if (text.length <= startChars + endChars) return text; const start = text.substring(0, startChars); diff --git a/packages/suite/src/utils/suite/__tests__/array.test.ts b/packages/utils/tests/arrayPartition.test.ts similarity index 84% rename from packages/suite/src/utils/suite/__tests__/array.test.ts rename to packages/utils/tests/arrayPartition.test.ts index 3f1a4b99bcb..9156f3f4d20 100644 --- a/packages/suite/src/utils/suite/__tests__/array.test.ts +++ b/packages/utils/tests/arrayPartition.test.ts @@ -1,6 +1,6 @@ -import { partition } from '../array'; +import { arrayPartition } from '../src/arrayPartition'; -describe('Array utils', () => { +describe('arrayPartition', () => { describe('partition of the array by condition', () => { it('partition array of objects', () => { const arrayOfObjects = [ @@ -10,7 +10,7 @@ describe('Array utils', () => { { value: true, name: 'd' }, { value: false, name: 'e' }, ]; - const partitionedObjects = partition(arrayOfObjects, element => element.value); + const partitionedObjects = arrayPartition(arrayOfObjects, element => element.value); const [truthy, falsy] = partitionedObjects; expect(arrayOfObjects.length).toEqual(truthy.length + falsy.length); expect(partitionedObjects).toStrictEqual([ @@ -28,7 +28,7 @@ describe('Array utils', () => { it('partition array of numbers', () => { const arrayOfNumbers = [3, 1, 4, 5, 2, 1, 2]; - const partitionedNumbers = partition(arrayOfNumbers, element => element < 3); + const partitionedNumbers = arrayPartition(arrayOfNumbers, element => element < 3); const [lessThanThree, fromThree] = partitionedNumbers; expect(arrayOfNumbers.length).toEqual(lessThanThree.length + fromThree.length); expect(partitionedNumbers).toStrictEqual([ @@ -39,7 +39,7 @@ describe('Array utils', () => { it('partition array of strings', () => { const arrayOfStrings = ['a', 'b', 'c', 'd', 'e', 'a']; - const partitionedStrings = partition( + const partitionedStrings = arrayPartition( arrayOfStrings, element => element === 'a' || element === 'b', ); diff --git a/packages/utils/tests/capitalizeFirstLetter.test.ts b/packages/utils/tests/capitalizeFirstLetter.test.ts new file mode 100644 index 00000000000..7d34b4be0e4 --- /dev/null +++ b/packages/utils/tests/capitalizeFirstLetter.test.ts @@ -0,0 +1,6 @@ +import { capitalizeFirstLetter } from '../src/capitalizeFirstLetter'; + +it('capitalizeFirstLetter', () => { + expect(capitalizeFirstLetter('god')).toBe('God'); + expect(capitalizeFirstLetter('dog')).toBe('Dog'); +}); diff --git a/packages/utils/tests/countBytesInString.test.ts b/packages/utils/tests/countBytesInString.test.ts new file mode 100644 index 00000000000..5bba01c9be4 --- /dev/null +++ b/packages/utils/tests/countBytesInString.test.ts @@ -0,0 +1,8 @@ +import { countBytesInString } from '../src/countBytesInString'; + +it('countBytesInString', () => { + expect(countBytesInString('aaa')).toBe(3); + expect(countBytesInString('č')).toBe(2); + expect(countBytesInString('😀')).toBe(4); + expect(countBytesInString('+ěčřěžšýžřšý')).toBe(23); +}); diff --git a/packages/utils/tests/getNumberFromPixelString.test.ts b/packages/utils/tests/getNumberFromPixelString.test.ts new file mode 100644 index 00000000000..b76275a6789 --- /dev/null +++ b/packages/utils/tests/getNumberFromPixelString.test.ts @@ -0,0 +1,6 @@ +import { getNumberFromPixelString } from '../src/getNumberFromPixelString'; + +it('getNumberFromPixelString', () => { + expect(getNumberFromPixelString('1px')).toBe(1); + expect(getNumberFromPixelString('1')).toBe(1); +}); diff --git a/packages/utils/tests/getWeakRandomId.test.ts b/packages/utils/tests/getWeakRandomId.test.ts new file mode 100644 index 00000000000..cd50e377035 --- /dev/null +++ b/packages/utils/tests/getWeakRandomId.test.ts @@ -0,0 +1,22 @@ +import { getWeakRandomId } from '../src/getWeakRandomId'; + +describe('random', () => { + describe('getWeakRandomId', () => { + it('should return random id of fixed length', () => { + expect(getWeakRandomId(12)).toHaveLength(12); + }); + it('should generate few results which should be unique', () => { + const ids: any = {}; + for (let i = 0; i < 10; i++) { + const random = getWeakRandomId(10); + if (!ids[random]) { + ids[random] = 0; + } + ids[random]++; + } + Object.values(ids).forEach(id => { + expect(id).toEqual(1); + }); + }); + }); +}); diff --git a/packages/utils/tests/hasUppercaseLetter.test.ts b/packages/utils/tests/hasUppercaseLetter.test.ts new file mode 100644 index 00000000000..10a19d5fbb4 --- /dev/null +++ b/packages/utils/tests/hasUppercaseLetter.test.ts @@ -0,0 +1,14 @@ +import { hasUppercaseLetter } from '../src/hasUppercaseLetter'; + +describe('hasUppercaseLetter', () => { + it('hasUppercaseLetter', () => { + expect(hasUppercaseLetter('0')).toBe(false); + expect(hasUppercaseLetter('abc')).toBe(false); + expect(hasUppercaseLetter('abcD')).toBe(true); + expect(hasUppercaseLetter('Abcd')).toBe(true); + expect(hasUppercaseLetter('aBcd')).toBe(true); + expect(hasUppercaseLetter('123abc123')).toBe(false); + expect(hasUppercaseLetter('0x123abc456')).toBe(false); + expect(hasUppercaseLetter('0x123aBc456')).toBe(true); + }); +}); diff --git a/packages/utils/tests/isAscii.test.ts b/packages/utils/tests/isAscii.test.ts new file mode 100644 index 00000000000..50af1f16982 --- /dev/null +++ b/packages/utils/tests/isAscii.test.ts @@ -0,0 +1,18 @@ +import { isAscii } from '../src/isAscii'; + +describe('isAscii', () => { + describe('isAscii', () => { + it('should return true for ASCII only string', () => { + expect(isAscii('this is only ascii')).toEqual(true); + }); + + it('should return true when called without parameter', () => { + expect(isAscii()).toEqual(true); + }); + + it('should return false strings with non ASCII chars', () => { + expect(isAscii('¥')).toEqual(false); + expect(isAscii('železniční přejezd')).toEqual(false); + }); + }); +}); diff --git a/packages/suite/src/utils/suite/__tests__/mergeObj.test.ts b/packages/utils/tests/mergeObject.test.ts similarity index 74% rename from packages/suite/src/utils/suite/__tests__/mergeObj.test.ts rename to packages/utils/tests/mergeObject.test.ts index ddd92e92d5f..a1e2ab838bc 100644 --- a/packages/suite/src/utils/suite/__tests__/mergeObj.test.ts +++ b/packages/utils/tests/mergeObject.test.ts @@ -1,6 +1,6 @@ -import { mergeObj } from '../mergeObj'; +import { mergeObject } from '../src/mergeObject'; -describe('mergeObj', () => { +describe('mergeObject', () => { it('should deep merge two objects', () => { const target = { a: 1, @@ -22,6 +22,6 @@ describe('mergeObj', () => { a: 1, }, }; - expect(mergeObj(target, source)).toEqual(expected); + expect(mergeObject(target, source)).toEqual(expected); }); }); diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json new file mode 100644 index 00000000000..04d111db55f --- /dev/null +++ b/packages/utils/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.lib.json", + "compilerOptions": { + "noEmit": true, + }, + "include": ["**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/packages/utils/tsconfig.lib.json b/packages/utils/tsconfig.lib.json new file mode 100644 index 00000000000..d95047d17df --- /dev/null +++ b/packages/utils/tsconfig.lib.json @@ -0,0 +1,22 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "module": "commonjs", + "target": "esnext", + "outDir": "./lib", + "strict": true, + "declaration": true, + "removeComments": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noErrorTruncation": true, + "types": ["jest", "node"] + }, + "include": ["./src/index.ts", "./src/global.d.ts"], + "exclude": [ + "node_modules", + "**/__tests__/**", + "**/__fixtures__/**", + "**/*.test.ts" + ] +}