Commit ed5902ad authored by lynn's avatar lynn Committed by GitHub

feat: second user model PR with wallet properties (#4206)

* init commit

* remove absolute value in date calc

* all the events are now logged properly plus changed native token address to NATIVE

* add documentation line

* remove unnecessary prop

* init

* init

* checkpoint

* checkpoint

* merge

* lint

* cleanup

* wallet user model stuff working as expected now

* add app loaded event and rest of user properties

* fix tests

* change token balances as per kyle rec

* refactor connected wallet state handling + rest of vm comments

* fix redux breaking, revert wallet from set to array
parent ec783fdb
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
* and logged. * and logged.
*/ */
export enum EventName { export enum EventName {
APP_LOADED = 'Application Loaded',
APPROVE_TOKEN_TXN_SUBMITTED = 'Approve Token Transaction Submitted', APPROVE_TOKEN_TXN_SUBMITTED = 'Approve Token Transaction Submitted',
CONNECT_WALLET_BUTTON_CLICKED = 'Connect Wallet Button Clicked', CONNECT_WALLET_BUTTON_CLICKED = 'Connect Wallet Button Clicked',
PAGE_VIEWED = 'Page Viewed', PAGE_VIEWED = 'Page Viewed',
...@@ -26,15 +27,39 @@ export enum EventName { ...@@ -26,15 +27,39 @@ export enum EventName {
} }
export enum CUSTOM_USER_PROPERTIES { export enum CUSTOM_USER_PROPERTIES {
WALLET_ADDRESS = 'wallet_address',
WALLET_TYPE = 'wallet_type',
USER_LAST_SEEN_DATE = 'user_last_seen_date',
USER_FIRST_SEEN_DATE = 'user_first_seen_date',
WALLET_CHAIN_IDS = 'all_wallet_chain_ids',
ALL_WALLET_ADDRESSES_CONNECTED = 'all_wallet_addresses_connected', ALL_WALLET_ADDRESSES_CONNECTED = 'all_wallet_addresses_connected',
SCREEN_RESOLUTION = 'screen_resolution', ALL_WALLET_CHAIN_IDS = 'all_wallet_chain_ids',
BROWSER = 'browser', BROWSER = 'browser',
LIGHT_MODE = 'light_mode', DARK_MODE = 'is_dark_mode',
SCREEN_RESOLUTION_HEIGHT = 'screen_resolution_height',
SCREEN_RESOLUTION_WIDTH = 'screen_resolution_width',
WALLET_ADDRESS = 'wallet_address',
WALLET_NATIVE_CURRENCY_BALANCE_USD = 'wallet_native_currency_balance_usd',
WALLET_TOKENS_ADDRESSES = 'wallet_tokens_addresses',
WALLET_TOKENS_SYMBOLS = 'wallet_tokens_symbols',
WALLET_TYPE = 'wallet_type',
}
export enum CUSTOM_USER_PROPERTY_SUFFIXES {
WALLET_TOKEN_AMOUNT_SUFFIX = '_token_amount',
}
export enum CUSTOM_USER_PROPERTY_PREFIXES {
WALLET_CHAIN_IDS_PREFIX = 'wallet_chain_ids_',
WALLET_FIRST_SEEN_DATE_PREFIX = 'first_seen_date_',
WALLET_LAST_SEEN_DATE_PREFIX = 'last_seen_date_',
}
export enum BROWSER {
FIREFOX = 'Mozilla Firefox',
SAMSUNG = 'Samsung Internet',
OPERA = 'Opera',
INTERNET_EXPLORER = 'Microsoft Internet Explorer',
EDGE = 'Microsoft Edge (Legacy)',
EDGE_CHROMIUM = 'Microsoft Edge (Chromium)',
CHROME = 'Google Chrome or Chromium',
SAFARI = 'Apple Safari',
UNKNOWN = 'unknown',
} }
export enum WALLET_CONNECTION_RESULT { export enum WALLET_CONNECTION_RESULT {
......
...@@ -44,6 +44,8 @@ export function sendAnalyticsEvent(eventName: string, eventProperties?: Record<s ...@@ -44,6 +44,8 @@ export function sendAnalyticsEvent(eventName: string, eventProperties?: Record<s
track(eventName, eventProperties) track(eventName, eventProperties)
} }
type Value = string | number | boolean | string[] | number[]
/** /**
* Class that exposes methods to mutate the User Model's properties in * Class that exposes methods to mutate the User Model's properties in
* Amplitude that represents the current session's user. * Amplitude that represents the current session's user.
...@@ -67,16 +69,16 @@ class UserModel { ...@@ -67,16 +69,16 @@ class UserModel {
identify(mutate(new Identify())) identify(mutate(new Identify()))
} }
set(key: string, value: string | number) { set(key: string, value: Value) {
this.call((event) => event.set(key, value)) this.call((event) => event.set(key, value))
} }
setOnce(key: string, value: string | number) { setOnce(key: string, value: Value) {
this.call((event) => event.setOnce(key, value)) this.call((event) => event.setOnce(key, value))
} }
add(key: string, value: string | number) { add(key: string, value: number) {
this.call((event) => event.add(key, typeof value === 'number' ? value : 0)) this.call((event) => event.add(key, value))
} }
postInsert(key: string, value: string | number) { postInsert(key: string, value: string | number) {
......
...@@ -108,10 +108,10 @@ export function CurrencySearch({ ...@@ -108,10 +108,10 @@ export function CurrencySearch({
}, [allTokens, debouncedQuery]) }, [allTokens, debouncedQuery])
const [balances, balancesIsLoading] = useAllTokenBalances() const [balances, balancesIsLoading] = useAllTokenBalances()
const sortedTokens: Token[] = useMemo(() => { const sortedTokens: Token[] = useMemo(
void balancesIsLoading // creates a new array once balances load to update hooks () => (!balancesIsLoading ? [...filteredTokens].sort(tokenComparator.bind(null, balances)) : []),
return [...filteredTokens].sort(tokenComparator.bind(null, balances)) [balances, filteredTokens, balancesIsLoading]
}, [balances, filteredTokens, balancesIsLoading]) )
const filteredSortedTokens = useSortTokensByQuery(debouncedQuery, sortedTokens) const filteredSortedTokens = useSortTokensByQuery(debouncedQuery, sortedTokens)
...@@ -126,7 +126,7 @@ export function CurrencySearch({ ...@@ -126,7 +126,7 @@ export function CurrencySearch({
const s = debouncedQuery.toLowerCase().trim() const s = debouncedQuery.toLowerCase().trim()
if (native.symbol?.toLowerCase()?.indexOf(s) !== -1) { if (native.symbol?.toLowerCase()?.indexOf(s) !== -1) {
// Always bump the native token to the top of the list. // Always bump the native token to the top of the list.
return native ? [native, ...filteredSortedTokens.filter((t) => !t.equals(native))] : filteredSortedTokens return [native, ...filteredSortedTokens.filter((t) => !t.equals(native))]
} }
return filteredSortedTokens return filteredSortedTokens
}, [debouncedQuery, native, filteredSortedTokens]) }, [debouncedQuery, native, filteredSortedTokens])
......
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import * as connectionUtils from 'connection/utils' import * as connectionUtils from 'connection/utils'
import JSBI from 'jsbi'
import { ApplicationModal } from 'state/application/reducer' import { ApplicationModal } from 'state/application/reducer'
import { nativeOnChain } from '../../constants/tokens'
import { render, screen } from '../../test-utils' import { render, screen } from '../../test-utils'
import WalletModal from './index' import WalletModal from './index'
...@@ -9,6 +12,11 @@ afterEach(() => { ...@@ -9,6 +12,11 @@ afterEach(() => {
jest.resetModules() jest.resetModules()
}) })
const currencyAmount = (token: Currency, amount: number) => CurrencyAmount.fromRawAmount(token, JSBI.BigInt(amount))
const mockEth = () => nativeOnChain(1)
const mockCurrencyAmount = currencyAmount(mockEth(), 1)
const UserAgentMock = jest.requireMock('utils/userAgent') const UserAgentMock = jest.requireMock('utils/userAgent')
jest.mock('utils/userAgent', () => ({ jest.mock('utils/userAgent', () => ({
isMobile: false, isMobile: false,
...@@ -23,6 +31,38 @@ jest.mock('.../../state/application/hooks', () => { ...@@ -23,6 +31,38 @@ jest.mock('.../../state/application/hooks', () => {
} }
}) })
jest.mock('hooks/useStablecoinPrice', () => {
return {
useStablecoinValue: (_currencyAmount: CurrencyAmount<Currency> | undefined | null) => {
return
},
}
})
jest.mock('state/connection/hooks', () => {
return {
useAllTokenBalances: () => {
return [{}, false]
},
}
})
jest.mock('../../hooks/Tokens', () => {
return {
useAllTokens: () => ({}),
}
})
jest.mock('lib/hooks/useCurrencyBalance', () => {
return {
useCurrencyBalances: (account?: string, currencies?: (Currency | undefined)[]) => {
return [mockCurrencyAmount]
},
}
})
jest.mock('lib/hooks/useNativeCurrency', () => () => mockEth)
jest.mock('@web3-react/core', () => { jest.mock('@web3-react/core', () => {
const web3React = jest.requireActual('@web3-react/core') const web3React = jest.requireActual('@web3-react/core')
return { return {
......
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { Connector } from '@web3-react/types' import { Connector } from '@web3-react/types'
import { sendAnalyticsEvent, user } from 'components/AmplitudeAnalytics' import { sendAnalyticsEvent, user } from 'components/AmplitudeAnalytics'
import { CUSTOM_USER_PROPERTIES, EventName, WALLET_CONNECTION_RESULT } from 'components/AmplitudeAnalytics/constants' import {
CUSTOM_USER_PROPERTIES,
CUSTOM_USER_PROPERTY_PREFIXES,
CUSTOM_USER_PROPERTY_SUFFIXES,
EventName,
WALLET_CONNECTION_RESULT,
} from 'components/AmplitudeAnalytics/constants'
import { formatToDecimal, getTokenAddress } from 'components/AmplitudeAnalytics/utils'
import { sendEvent } from 'components/analytics' import { sendEvent } from 'components/analytics'
import { AutoColumn } from 'components/Column' import { AutoColumn } from 'components/Column'
import { AutoRow } from 'components/Row' import { AutoRow } from 'components/Row'
import { ConnectionType } from 'connection' import { ConnectionType } from 'connection'
import { getConnection, getConnectionName, getIsCoinbaseWallet, getIsInjected, getIsMetaMask } from 'connection/utils' import { getConnection, getConnectionName, getIsCoinbaseWallet, getIsInjected, getIsMetaMask } from 'connection/utils'
import { useCallback, useEffect, useState } from 'react' import { useStablecoinValue } from 'hooks/useStablecoinPrice'
import { useCurrencyBalances } from 'lib/hooks/useCurrencyBalance'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { tokenComparator } from 'lib/hooks/useTokenList/sorting'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { ArrowLeft } from 'react-feather' import { ArrowLeft } from 'react-feather'
import { useAllTokenBalances } from 'state/connection/hooks'
import { updateConnectionError } from 'state/connection/reducer' import { updateConnectionError } from 'state/connection/reducer'
import { useAppDispatch, useAppSelector } from 'state/hooks' import { useAppDispatch, useAppSelector } from 'state/hooks'
import { updateSelectedWallet } from 'state/user/reducer' import { updateSelectedWallet } from 'state/user/reducer'
...@@ -18,6 +31,7 @@ import styled from 'styled-components/macro' ...@@ -18,6 +31,7 @@ import styled from 'styled-components/macro'
import { isMobile } from 'utils/userAgent' import { isMobile } from 'utils/userAgent'
import { ReactComponent as Close } from '../../assets/images/x.svg' import { ReactComponent as Close } from '../../assets/images/x.svg'
import { useAllTokens } from '../../hooks/Tokens'
import { useModalIsOpen, useToggleWalletModal } from '../../state/application/hooks' import { useModalIsOpen, useToggleWalletModal } from '../../state/application/hooks'
import { ApplicationModal } from '../../state/application/reducer' import { ApplicationModal } from '../../state/application/reducer'
import { ExternalLink, ThemedText } from '../../theme' import { ExternalLink, ThemedText } from '../../theme'
...@@ -114,25 +128,51 @@ const WALLET_VIEWS = { ...@@ -114,25 +128,51 @@ const WALLET_VIEWS = {
PENDING: 'pending', PENDING: 'pending',
} }
const sendAnalyticsWalletBalanceUserInfo = (
balances: (CurrencyAmount<Currency> | undefined)[],
nativeCurrencyBalanceUsd: number
) => {
const walletTokensSymbols: string[] = []
const walletTokensAddresses: string[] = []
balances.forEach((currencyAmount) => {
if (currencyAmount !== undefined) {
const tokenBalanceAmount = formatToDecimal(currencyAmount, currencyAmount.currency.decimals)
if (tokenBalanceAmount > 0) {
const tokenAddress = getTokenAddress(currencyAmount.currency)
walletTokensAddresses.push(getTokenAddress(currencyAmount.currency))
walletTokensSymbols.push(currencyAmount.currency.symbol ?? '')
const tokenPrefix = currencyAmount.currency.symbol ?? tokenAddress
user.set(`${tokenPrefix}${CUSTOM_USER_PROPERTY_SUFFIXES.WALLET_TOKEN_AMOUNT_SUFFIX}`, tokenBalanceAmount)
}
}
})
user.set(CUSTOM_USER_PROPERTIES.WALLET_NATIVE_CURRENCY_BALANCE_USD, nativeCurrencyBalanceUsd)
user.set(CUSTOM_USER_PROPERTIES.WALLET_TOKENS_ADDRESSES, walletTokensAddresses)
user.set(CUSTOM_USER_PROPERTIES.WALLET_TOKENS_SYMBOLS, walletTokensSymbols)
}
const sendAnalyticsEventAndUserInfo = ( const sendAnalyticsEventAndUserInfo = (
account: string, account: string,
walletType: string, walletType: string,
chainId: number | undefined, chainId: number | undefined,
isReconnect: boolean isReconnect: boolean
) => { ) => {
const currentDate = new Date().toISOString()
sendAnalyticsEvent(EventName.WALLET_CONNECT_TXN_COMPLETED, { sendAnalyticsEvent(EventName.WALLET_CONNECT_TXN_COMPLETED, {
result: WALLET_CONNECTION_RESULT.SUCCEEDED, result: WALLET_CONNECTION_RESULT.SUCCEEDED,
wallet_address: account, wallet_address: account,
wallet_type: walletType, wallet_type: walletType,
is_reconnect: isReconnect, is_reconnect: isReconnect,
}) })
const currentDate = new Date().toISOString()
user.set(CUSTOM_USER_PROPERTIES.WALLET_ADDRESS, account) user.set(CUSTOM_USER_PROPERTIES.WALLET_ADDRESS, account)
user.set(CUSTOM_USER_PROPERTIES.WALLET_TYPE, walletType) user.set(CUSTOM_USER_PROPERTIES.WALLET_TYPE, walletType)
if (chainId) user.postInsert(CUSTOM_USER_PROPERTIES.WALLET_CHAIN_IDS, chainId) if (chainId) {
user.postInsert(CUSTOM_USER_PROPERTIES.ALL_WALLET_CHAIN_IDS, chainId)
user.postInsert(`${CUSTOM_USER_PROPERTY_PREFIXES.WALLET_CHAIN_IDS_PREFIX}${account}`, chainId)
}
user.postInsert(CUSTOM_USER_PROPERTIES.ALL_WALLET_ADDRESSES_CONNECTED, account) user.postInsert(CUSTOM_USER_PROPERTIES.ALL_WALLET_ADDRESSES_CONNECTED, account)
user.setOnce(CUSTOM_USER_PROPERTIES.USER_FIRST_SEEN_DATE, currentDate) user.setOnce(`${CUSTOM_USER_PROPERTY_PREFIXES.WALLET_FIRST_SEEN_DATE_PREFIX}${account}`, currentDate)
user.set(CUSTOM_USER_PROPERTIES.USER_LAST_SEEN_DATE, currentDate) user.set(`${CUSTOM_USER_PROPERTY_PREFIXES.WALLET_LAST_SEEN_DATE_PREFIX}${account}`, currentDate)
} }
export default function WalletModal({ export default function WalletModal({
...@@ -146,10 +186,11 @@ export default function WalletModal({ ...@@ -146,10 +186,11 @@ export default function WalletModal({
}) { }) {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const { connector, account, chainId } = useWeb3React() const { connector, account, chainId } = useWeb3React()
const [connectedWallets, updateConnectedWallets] = useConnectedWallets() const [connectedWallets, addWalletToConnectedWallets] = useConnectedWallets()
const [walletView, setWalletView] = useState(WALLET_VIEWS.ACCOUNT) const [walletView, setWalletView] = useState(WALLET_VIEWS.ACCOUNT)
const [lastActiveWalletAddress, setLastActiveWalletAddress] = useState<string | undefined>(account) const [lastActiveWalletAddress, setLastActiveWalletAddress] = useState<string | undefined>(account)
const [shouldLogWalletBalances, setShouldLogWalletBalances] = useState(false)
const [pendingConnector, setPendingConnector] = useState<Connector | undefined>() const [pendingConnector, setPendingConnector] = useState<Connector | undefined>()
const pendingError = useAppSelector((state) => const pendingError = useAppSelector((state) =>
...@@ -159,6 +200,25 @@ export default function WalletModal({ ...@@ -159,6 +200,25 @@ export default function WalletModal({
const walletModalOpen = useModalIsOpen(ApplicationModal.WALLET) const walletModalOpen = useModalIsOpen(ApplicationModal.WALLET)
const toggleWalletModal = useToggleWalletModal() const toggleWalletModal = useToggleWalletModal()
const allTokens = useAllTokens()
const [tokenBalances, tokenBalancesIsLoading] = useAllTokenBalances()
const sortedTokens: Token[] = useMemo(
() => (!tokenBalancesIsLoading ? Object.values(allTokens).sort(tokenComparator.bind(null, tokenBalances)) : []),
[tokenBalances, allTokens, tokenBalancesIsLoading]
)
const native = useNativeCurrency()
const sortedTokensWithETH: Currency[] = useMemo(
() =>
// Always bump the native token to the top of the list.
native ? [native, ...sortedTokens.filter((t) => !t.equals(native))] : sortedTokens,
[native, sortedTokens]
)
const balances = useCurrencyBalances(account, sortedTokensWithETH)
const nativeBalance = balances.length > 0 ? balances[0] : null
const nativeCurrencyBalanceUsdValue = useStablecoinValue(nativeBalance)?.toFixed(2)
const openOptions = useCallback(() => { const openOptions = useCallback(() => {
setWalletView(WALLET_VIEWS.OPTIONS) setWalletView(WALLET_VIEWS.OPTIONS)
}, [setWalletView]) }, [setWalletView])
...@@ -180,18 +240,31 @@ export default function WalletModal({ ...@@ -180,18 +240,31 @@ export default function WalletModal({
useEffect(() => { useEffect(() => {
if (account && account !== lastActiveWalletAddress) { if (account && account !== lastActiveWalletAddress) {
const walletType = getConnectionName(getConnection(connector).type, getIsMetaMask()) const walletType = getConnectionName(getConnection(connector).type, getIsMetaMask())
const isReconnect =
if (
connectedWallets.filter((wallet) => wallet.account === account && wallet.walletType === walletType).length > 0 connectedWallets.filter((wallet) => wallet.account === account && wallet.walletType === walletType).length > 0
) { sendAnalyticsEventAndUserInfo(account, walletType, chainId, isReconnect)
sendAnalyticsEventAndUserInfo(account, walletType, chainId, true) setShouldLogWalletBalances(true)
} else { if (!isReconnect) addWalletToConnectedWallets({ account, walletType })
sendAnalyticsEventAndUserInfo(account, walletType, chainId, false)
updateConnectedWallets({ account, walletType })
}
} }
setLastActiveWalletAddress(account) setLastActiveWalletAddress(account)
}, [connectedWallets, updateConnectedWallets, lastActiveWalletAddress, account, connector, chainId]) }, [connectedWallets, addWalletToConnectedWallets, lastActiveWalletAddress, account, connector, chainId])
// Send wallet balance info once it becomes available.
useEffect(() => {
if (!tokenBalancesIsLoading && shouldLogWalletBalances && balances && nativeCurrencyBalanceUsdValue) {
const nativeCurrencyBalanceUsd =
native && nativeCurrencyBalanceUsdValue ? parseFloat(nativeCurrencyBalanceUsdValue) : 0
sendAnalyticsWalletBalanceUserInfo(balances, nativeCurrencyBalanceUsd)
setShouldLogWalletBalances(false)
}
}, [
balances,
nativeCurrencyBalanceUsdValue,
shouldLogWalletBalances,
setShouldLogWalletBalances,
tokenBalancesIsLoading,
native,
])
const tryActivation = useCallback( const tryActivation = useCallback(
async (connector: Connector) => { async (connector: Connector) => {
......
import { initializeAnalytics } from 'components/AmplitudeAnalytics' import { initializeAnalytics } from 'components/AmplitudeAnalytics'
import { PageName } from 'components/AmplitudeAnalytics/constants' import { sendAnalyticsEvent, user } from 'components/AmplitudeAnalytics'
import { CUSTOM_USER_PROPERTIES, EventName, PageName } from 'components/AmplitudeAnalytics/constants'
import { Trace } from 'components/AmplitudeAnalytics/Trace' import { Trace } from 'components/AmplitudeAnalytics/Trace'
import Loader from 'components/Loader' import Loader from 'components/Loader'
import TopLevelModals from 'components/TopLevelModals' import TopLevelModals from 'components/TopLevelModals'
...@@ -8,7 +9,9 @@ import ApeModeQueryParamReader from 'hooks/useApeModeQueryParamReader' ...@@ -8,7 +9,9 @@ import ApeModeQueryParamReader from 'hooks/useApeModeQueryParamReader'
import { lazy, Suspense } from 'react' import { lazy, Suspense } from 'react'
import { useEffect } from 'react' import { useEffect } from 'react'
import { Navigate, Route, Routes, useLocation } from 'react-router-dom' import { Navigate, Route, Routes, useLocation } from 'react-router-dom'
import { useIsDarkMode } from 'state/user/hooks'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { getBrowser } from 'utils/browser'
import { useAnalyticsReporter } from '../components/analytics' import { useAnalyticsReporter } from '../components/analytics'
import ErrorBoundary from '../components/ErrorBoundary' import ErrorBoundary from '../components/ErrorBoundary'
...@@ -86,6 +89,7 @@ export default function App() { ...@@ -86,6 +89,7 @@ export default function App() {
const { pathname } = useLocation() const { pathname } = useLocation()
const currentPage = getCurrentPageFromLocation(pathname) const currentPage = getCurrentPageFromLocation(pathname)
const isDarkMode = useIsDarkMode()
useAnalyticsReporter() useAnalyticsReporter()
initializeAnalytics() initializeAnalytics()
...@@ -94,6 +98,18 @@ export default function App() { ...@@ -94,6 +98,18 @@ export default function App() {
window.scrollTo(0, 0) window.scrollTo(0, 0)
}, [pathname]) }, [pathname])
useEffect(() => {
// TODO(zzmp): add web vitals event properties to app loaded event.
sendAnalyticsEvent(EventName.APP_LOADED)
user.set(CUSTOM_USER_PROPERTIES.BROWSER, getBrowser())
user.set(CUSTOM_USER_PROPERTIES.SCREEN_RESOLUTION_HEIGHT, window.screen.height)
user.set(CUSTOM_USER_PROPERTIES.SCREEN_RESOLUTION_WIDTH, window.screen.width)
}, [])
useEffect(() => {
user.set(CUSTOM_USER_PROPERTIES.DARK_MODE, isDarkMode)
}, [isDarkMode])
return ( return (
<ErrorBoundary> <ErrorBoundary>
<DarkModeQueryParamReader /> <DarkModeQueryParamReader />
......
...@@ -18,7 +18,10 @@ const walletsSlice = createSlice({ ...@@ -18,7 +18,10 @@ const walletsSlice = createSlice({
initialState, initialState,
reducers: { reducers: {
addConnectedWallet(state, { payload }) { addConnectedWallet(state, { payload }) {
state.connectedWallets = state.connectedWallets.concat(payload) const existsAlready = state.connectedWallets.find((wallet) => shallowEqual(payload, wallet))
if (!existsAlready) {
state.connectedWallets = state.connectedWallets.concat(payload)
}
}, },
removeConnectedWallet(state, { payload }) { removeConnectedWallet(state, { payload }) {
state.connectedWallets = state.connectedWallets.filter((wallet) => !shallowEqual(wallet, payload)) state.connectedWallets = state.connectedWallets.filter((wallet) => !shallowEqual(wallet, payload))
......
import { BROWSER } from 'components/AmplitudeAnalytics/constants'
// Get browser being used, code comes from: https://developer.mozilla.org/en-US/docs/Web/API/Window/navigator.
export function getBrowser(): string {
const sUsrAg = navigator.userAgent
// The order matters here, and this may report false positives for unlisted browsers.
if (sUsrAg.indexOf('Firefox') > -1) {
return BROWSER.FIREFOX
// "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0"
} else if (sUsrAg.indexOf('SamsungBrowser') > -1) {
return BROWSER.SAMSUNG
// "Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-G955F Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/9.4 Chrome/67.0.3396.87 Mobile Safari/537.36
} else if (sUsrAg.indexOf('Opera') > -1 || sUsrAg.indexOf('OPR') > -1) {
return BROWSER.OPERA
// "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 OPR/57.0.3098.106"
} else if (sUsrAg.indexOf('Trident') > -1) {
return BROWSER.INTERNET_EXPLORER
// "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; Zoom 3.6.0; wbx 1.0.0; rv:11.0) like Gecko"
} else if (sUsrAg.indexOf('Edge') > -1) {
return BROWSER.EDGE
// "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299"
} else if (sUsrAg.indexOf('Edg') > -1) {
return BROWSER.EDGE_CHROMIUM
// Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.64
} else if (sUsrAg.indexOf('Chrome') > -1) {
return BROWSER.CHROME
// "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/66.0.3359.181 Chrome/66.0.3359.181 Safari/537.36"
} else if (sUsrAg.indexOf('Safari') > -1) {
return BROWSER.SAFARI
// "Mozilla/5.0 (iPhone; CPU iPhone OS 11_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.0 Mobile/15E148 Safari/604.1 980x1306"
} else {
return BROWSER.UNKNOWN
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment