diff --git a/package.json b/package.json index 200d3381305..82d2de8758b 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,6 @@ "react": "17.0.2", "react-countup": "6.5.0", "react-dom": "17.0.2", - "react-ga": "3.3.0", "react-idle-timer": "5.7.2", "react-markdown": "7.1.0", "react-redux": "8.0.1", diff --git a/src/background/controller/wallet.ts b/src/background/controller/wallet.ts index 721d7178df5..94fbcc6b0c4 100644 --- a/src/background/controller/wallet.ts +++ b/src/background/controller/wallet.ts @@ -133,6 +133,7 @@ import Browser from 'webextension-polyfill'; import { hashSafeMessage } from '@safe-global/protocol-kit'; import { userGuideService } from '../service/userGuide'; import { metamaskModeService } from '../service/metamaskModeService'; +import { ga4 } from '@/utils/ga4'; const stashKeyrings: Record = {}; @@ -4876,6 +4877,10 @@ export class WalletController extends BaseController { action: 'Success Add Network', label: `${source}_${String(chain.id)}`, }); + + ga4.fireEvent('Add_CustomNetwork', { + event_category: 'Custom Network', + }); } return res; }; diff --git a/src/background/index.ts b/src/background/index.ts index 6c20043ede1..97df67ac880 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -54,6 +54,8 @@ import { isSameAddress } from './utils'; import rpcCache from './utils/rpcCache'; import { storage } from './webapi'; import { metamaskModeService } from './service/metamaskModeService'; +import { ga4 } from '@/utils/ga4'; +import { ALARMS_USER_ENABLE } from './utils/alarms'; Safe.adapter = fetchAdapter as any; @@ -116,9 +118,14 @@ async function restoreAppState() { syncChainService.roll(); transactionWatchService.roll(); transactionBroadcastWatchService.roll(); - startEnableUser(); walletController.syncMainnetChainList(); + // check if user has enabled the extension + chrome.alarms.create(ALARMS_USER_ENABLE, { + when: Date.now(), + periodInMinutes: 60, + }); + if (!keyringService.isBooted()) { userGuideService.init(); } @@ -171,6 +178,10 @@ restoreAppState(); action: 'Custom Network Status', value: customTestnetLength, }); + + ga4.fireEvent('Has Custom Network', { + event_category: 'Custom Network', + }); } const chains = preferenceService.getSavedChains(); matomoRequestEvent({ @@ -201,6 +212,10 @@ restoreAppState(); label: [group[0].action, group[0].label, group.length].join('|'), value: group.length, }); + + ga4.fireEvent(`${group[0].category}_${group[0].label}`, { + event_category: 'UserAddress', + }); }); preferenceService.updateSendLogTime(Date.now()); }; @@ -422,6 +437,10 @@ function startEnableUser() { category: 'User', action: 'enable', }); + + ga4.fireEvent('User_Enable', { + event_category: 'User Enable', + }); preferenceService.updateSendEnableTime(Date.now()); } @@ -434,3 +453,9 @@ async function onInstall() { await userGuideService.openUserGuide(); } } + +browser.alarms.onAlarm.addListener((alarm) => { + if (alarm.name === ALARMS_USER_ENABLE) { + startEnableUser(); + } +}); diff --git a/src/background/service/customTestnet.ts b/src/background/service/customTestnet.ts index ee5c0e5212a..2656f2195d3 100644 --- a/src/background/service/customTestnet.ts +++ b/src/background/service/customTestnet.ts @@ -20,6 +20,7 @@ import { http as axios } from '../utils/http'; import { matomoRequestEvent } from '@/utils/matomo-request'; import RPCService, { RPCServiceStore } from './rpc'; import { storage } from '../webapi'; +import { ga4 } from '@/utils/ga4'; const MAX_READ_CONTRACT_TIME = 8000; @@ -194,6 +195,10 @@ class CustomTestnetService { action: 'Custom Network Status', value: this.getList().length, }); + + ga4.fireEvent('Has_CustomNetwork', { + event_category: 'Custom Network', + }); } return this.store.customTestnet[chain.id]; }; @@ -211,6 +216,10 @@ class CustomTestnetService { action: 'Custom Network Status', value: this.getList().length, }); + + ga4.fireEvent('Has_CustomNetwork', { + event_category: 'Custom Network', + }); } }; diff --git a/src/background/utils/alarms.ts b/src/background/utils/alarms.ts index 5422d3cb09a..9a86f4a615b 100644 --- a/src/background/utils/alarms.ts +++ b/src/background/utils/alarms.ts @@ -1,2 +1,3 @@ export const ALARMS_SYNC_CHAINS = 'ALARMS_SYNC_CHAINS'; export const ALARMS_SYNC_METAMASK_DAPPS = 'ALARMS_SYNC_METAMASK_DAPPS'; +export const ALARMS_USER_ENABLE = 'ALARMS_USER_ENABLE'; diff --git a/src/ui/utils/sendPersonalMessage.ts b/src/ui/utils/sendPersonalMessage.ts index 03911062c00..5e3738aa834 100644 --- a/src/ui/utils/sendPersonalMessage.ts +++ b/src/ui/utils/sendPersonalMessage.ts @@ -3,6 +3,7 @@ import { WalletControllerType } from '@/ui/utils'; import { getKRCategoryByType } from '@/utils/transaction'; import eventBus from '@/eventBus'; import { matomoRequestEvent } from '@/utils/matomo-request'; +import { ga4 } from '@/utils/ga4'; // fail code export enum FailedCode { @@ -37,6 +38,17 @@ const report = async ({ ].join('|'), transport: 'beacon', }); + + if (action === 'createSignText') { + ga4.fireEvent('Init_SignText', { + event_category: 'SignText', + }); + } else if (action === 'startSignText') { + ga4.fireEvent('Submit_SignText', { + event_category: 'SignText', + }); + } + await wallet.reportStats(action, { type: currentAccount.brandName, category: getKRCategoryByType(currentAccount.type), diff --git a/src/ui/views/Approval/components/CoinbaseWaiting/index.tsx b/src/ui/views/Approval/components/CoinbaseWaiting/index.tsx index 354be862d76..43d425a1489 100644 --- a/src/ui/views/Approval/components/CoinbaseWaiting/index.tsx +++ b/src/ui/views/Approval/components/CoinbaseWaiting/index.tsx @@ -17,6 +17,7 @@ import { useSessionStatus } from '@/ui/component/WalletConnect/useSessionStatus' import { adjustV } from '@/ui/utils/gnosis'; import { findChain, findChainByEnum } from '@/utils/chain'; import { emitSignComponentAmounted } from '@/utils/signEvent'; +import { ga4 } from '@/utils/ga4'; interface ApprovalParams { address: string; @@ -175,6 +176,14 @@ const CoinbaseWaiting = ({ params }: { params: ApprovalParams }) => { action: 'Submit', label: chainInfo?.isTestnet ? 'Custom Network' : 'Integrated Network', }); + + ga4.fireEvent( + `Submit_${chainInfo?.isTestnet ? 'Custom' : 'Integrated'}`, + { + event_category: 'Transaction', + } + ); + isSignTriggered = true; } if (isText && !isSignTriggered) { diff --git a/src/ui/views/Approval/components/CommonWaiting.tsx b/src/ui/views/Approval/components/CommonWaiting.tsx index d57b6e36bea..f317be32282 100644 --- a/src/ui/views/Approval/components/CommonWaiting.tsx +++ b/src/ui/views/Approval/components/CommonWaiting.tsx @@ -22,6 +22,7 @@ import { adjustV } from '@/ui/utils/gnosis'; import { message } from 'antd'; import { findChain } from '@/utils/chain'; import { emitSignComponentAmounted } from '@/utils/signEvent'; +import { ga4 } from '@/utils/ga4'; interface ApprovalParams { address: string; @@ -191,6 +192,11 @@ export const CommonWaiting = ({ params }: { params: ApprovalParams }) => { action: 'Submit', label: chain?.isTestnet ? 'Custom Network' : 'Integrated Network', }); + + ga4.fireEvent(`Submit_${chain?.isTestnet ? 'Custom' : 'Integrated'}`, { + event_category: 'Transaction', + }); + setSignFinishedData({ data: sig, approvalId: approval.id, diff --git a/src/ui/views/Approval/components/ImKeyHardwareWaiting.tsx b/src/ui/views/Approval/components/ImKeyHardwareWaiting.tsx index 5303c71e919..b3bf06105ed 100644 --- a/src/ui/views/Approval/components/ImKeyHardwareWaiting.tsx +++ b/src/ui/views/Approval/components/ImKeyHardwareWaiting.tsx @@ -29,6 +29,7 @@ import { useImKeyStatus } from '@/ui/component/ConnectStatus/useImKeyStatus'; import * as Sentry from '@sentry/browser'; import { findChain } from '@/utils/chain'; import { emitSignComponentAmounted } from '@/utils/signEvent'; +import { ga4 } from '@/utils/ga4'; interface ApprovalParams { address: string; @@ -208,6 +209,10 @@ export const ImKeyHardwareWaiting = ({ label: chain?.isTestnet ? 'Custom Network' : 'Integrated Network', }); + ga4.fireEvent(`Submit_${chain?.isTestnet ? 'Custom' : 'Integrated'}`, { + event_category: 'Transaction', + }); + setSignFinishedData({ data: sig, approvalId: approval.id, diff --git a/src/ui/views/Approval/components/LedgerHardwareWaiting.tsx b/src/ui/views/Approval/components/LedgerHardwareWaiting.tsx index e61b6794125..f4ddc53a6fc 100644 --- a/src/ui/views/Approval/components/LedgerHardwareWaiting.tsx +++ b/src/ui/views/Approval/components/LedgerHardwareWaiting.tsx @@ -23,6 +23,7 @@ import { Props as ApprovalPopupContainerProps, } from './Popup/ApprovalPopupContainer'; import { isLedgerLockError } from '@/ui/utils/ledger'; +import { ga4 } from '@/utils/ga4'; interface ApprovalParams { address: string; @@ -198,6 +199,10 @@ const LedgerHardwareWaiting = ({ params }: { params: ApprovalParams }) => { label: chain?.isTestnet ? 'Custom Network' : 'Integrated Network', }); + ga4.fireEvent(`Submit_${chain?.isTestnet ? 'Custom' : 'Integrated'}`, { + event_category: 'Transaction', + }); + setSignFinishedData({ data: sig, approvalId: approval.id, diff --git a/src/ui/views/Approval/components/PrivatekeyWaiting.tsx b/src/ui/views/Approval/components/PrivatekeyWaiting.tsx index 862c05ff73a..02f63dae5f2 100644 --- a/src/ui/views/Approval/components/PrivatekeyWaiting.tsx +++ b/src/ui/views/Approval/components/PrivatekeyWaiting.tsx @@ -30,6 +30,7 @@ import { id } from 'ethers/lib/utils'; import { findChain } from '@/utils/chain'; import { emitSignComponentAmounted } from '@/utils/signEvent'; import { SafeClientTxStatus } from '@safe-global/sdk-starter-kit/dist/src/constants'; +import { ga4 } from '@/utils/ga4'; interface ApprovalParams { address: string; @@ -204,6 +205,11 @@ export const PrivatekeyWaiting = ({ params }: { params: ApprovalParams }) => { action: 'Submit', label: chain?.isTestnet ? 'Custom Network' : 'Integrated Network', }); + + ga4.fireEvent(`Submit_${chain?.isTestnet ? 'Custom' : 'Integrated'}`, { + event_category: 'Transaction', + }); + setSignFinishedData({ data: sig, approvalId: approval.id, diff --git a/src/ui/views/Approval/components/SignTestnetTx/index.tsx b/src/ui/views/Approval/components/SignTestnetTx/index.tsx index acf303f5d34..76282246b67 100644 --- a/src/ui/views/Approval/components/SignTestnetTx/index.tsx +++ b/src/ui/views/Approval/components/SignTestnetTx/index.tsx @@ -41,6 +41,7 @@ import { SignAdvancedSettings } from '../SignAdvancedSettings'; import clsx from 'clsx'; import { useGasAccountSign } from '@/ui/views/GasAccount/hooks'; import { Modal } from 'antd'; +import { ga4 } from '@/utils/ga4'; const checkGasAndNonce = ({ recommendGasLimitRatio, @@ -385,6 +386,10 @@ export const SignTestnetTx = ({ params, origin }: SignTxProps) => { action: 'init', label: chain?.isTestnet ? 'Custom Network' : 'Integrated Network', }); + ga4.fireEvent(`Init_${chain?.isTestnet ? 'Custom' : 'Integrated'}`, { + event_category: 'Transaction', + }); + if (currentAccount.type === KEYRING_TYPE.GnosisKeyring) { setIsGnosisAccount(true); } @@ -677,6 +682,11 @@ export const SignTestnetTx = ({ params, origin }: SignTxProps) => { action: 'Submit', label: chain?.isTestnet ? 'Custom Network' : 'Integrated Network', }); + + ga4.fireEvent(`Submit_${chain?.isTestnet ? 'Custom' : 'Integrated'}`, { + event_category: 'Transaction', + }); + resolveApproval({ ...transaction, nonce: realNonce || tx.nonce, diff --git a/src/ui/views/Approval/components/SignText.tsx b/src/ui/views/Approval/components/SignText.tsx index c495dcd7dd7..3489237161c 100644 --- a/src/ui/views/Approval/components/SignText.tsx +++ b/src/ui/views/Approval/components/SignText.tsx @@ -49,6 +49,7 @@ import { generateTypedData } from '@safe-global/protocol-kit'; import { useGetCurrentSafeInfo } from '../hooks/useGetCurrentSafeInfo'; import { useGetMessageHash } from '../hooks/useGetCurrentMessageHash'; import { useCheckCurrentSafeMessage } from '../hooks/useCheckCurrentSafeMessage'; +import { ga4 } from '@/utils/ga4'; interface SignTextProps { data: string[]; @@ -195,6 +196,17 @@ const SignText = ({ params }: { params: SignTextProps }) => { ].join('|'), transport: 'beacon', }); + + if (action === 'createSignText') { + ga4.fireEvent('Init_SignText', { + event_category: 'SignText', + }); + } else if (action === 'startSignText') { + ga4.fireEvent('Submit_SignText', { + event_category: 'SignText', + }); + } + await wallet.reportStats(action, { type: currentAccount.brandName, category: getKRCategoryByType(currentAccount.type), diff --git a/src/ui/views/Approval/components/SignTx.tsx b/src/ui/views/Approval/components/SignTx.tsx index 166dde0e8d4..0c6ef2b6b65 100644 --- a/src/ui/views/Approval/components/SignTx.tsx +++ b/src/ui/views/Approval/components/SignTx.tsx @@ -86,6 +86,7 @@ import { ActionRequireData, ParsedTransactionActionData, } from '@rabby-wallet/rabby-action'; +import { ga4 } from '@/utils/ga4'; interface BasicCoboArgusInfo { address: string; @@ -1222,6 +1223,11 @@ const SignTx = ({ params, origin }: SignTxProps) => { action: 'Submit', label: chain?.isTestnet ? 'Custom Network' : 'Integrated Network', }); + + ga4.fireEvent(`Submit_${chain?.isTestnet ? 'Custom' : 'Integrated'}`, { + event_category: 'Transaction', + }); + resolveApproval({ ...transaction, nonce: realNonce || tx.nonce, @@ -1611,6 +1617,10 @@ const SignTx = ({ params, origin }: SignTxProps) => { label: chain?.isTestnet ? 'Custom Network' : 'Integrated Network', }); + ga4.fireEvent(`Init_${chain?.isTestnet ? 'Custom' : 'Integrated'}`, { + event_category: 'Transaction', + }); + if (currentAccount.type === KEYRING_TYPE.GnosisKeyring) { setIsGnosisAccount(true); await getSafeInfo(); diff --git a/src/ui/views/Approval/components/SignTypedData.tsx b/src/ui/views/Approval/components/SignTypedData.tsx index 4835d1e3c39..24e2d74d151 100644 --- a/src/ui/views/Approval/components/SignTypedData.tsx +++ b/src/ui/views/Approval/components/SignTypedData.tsx @@ -56,6 +56,7 @@ import { useGetMessageHash } from '../hooks/useGetCurrentMessageHash'; import { useCheckCurrentSafeMessage } from '../hooks/useCheckCurrentSafeMessage'; import GnosisDrawer from './TxComponents/GnosisDrawer'; import { generateTypedData } from '@safe-global/protocol-kit'; +import { ga4 } from '@/utils/ga4'; interface SignTypedDataProps { method: string; @@ -340,6 +341,17 @@ const SignTypedData = ({ params }: { params: SignTypedDataProps }) => { ].join('|'), transport: 'beacon', }); + + if (action === 'createSignText') { + ga4.fireEvent('Init_SignText', { + event_category: 'SignText', + }); + } else if (action === 'startSignText') { + ga4.fireEvent('Submit_SignText', { + event_category: 'SignText', + }); + } + await wallet.reportStats(action, { type: currentAccount.brandName, category: getKRCategoryByType(currentAccount.type), diff --git a/src/ui/views/Approval/components/WatchAddressWaiting/index.tsx b/src/ui/views/Approval/components/WatchAddressWaiting/index.tsx index 564885f7af8..6cb042ad023 100644 --- a/src/ui/views/Approval/components/WatchAddressWaiting/index.tsx +++ b/src/ui/views/Approval/components/WatchAddressWaiting/index.tsx @@ -18,6 +18,7 @@ import { useSessionStatus } from '@/ui/component/WalletConnect/useSessionStatus' import { adjustV } from '@/ui/utils/gnosis'; import { findChain, findChainByEnum } from '@/utils/chain'; import { emitSignComponentAmounted } from '@/utils/signEvent'; +import { ga4 } from '@/utils/ga4'; interface ApprovalParams { address: string; @@ -255,6 +256,14 @@ const WatchAddressWaiting = ({ params }: { params: ApprovalParams }) => { ? 'Custom Network' : 'Integrated Network', }); + + ga4.fireEvent( + `Submit_${chainInfo?.isTestnet ? 'Custom' : 'Integrated'}`, + { + event_category: 'Transaction', + } + ); + isSignTriggered = true; } if (isText && !isSignTriggered) { diff --git a/src/ui/views/DappSearch/components/DappCard.tsx b/src/ui/views/DappSearch/components/DappCard.tsx index d93e7cba6d7..2a84eda1b3e 100644 --- a/src/ui/views/DappSearch/components/DappCard.tsx +++ b/src/ui/views/DappSearch/components/DappCard.tsx @@ -1,6 +1,7 @@ import FallbackImage from '@/ui/component/FallbackSiteLogo'; import ThemeIcon from '@/ui/component/ThemeMode/ThemeIcon'; import { openInTab } from '@/ui/utils'; +import { ga4 } from '@/utils/ga4'; import { matomoRequestEvent } from '@/utils/matomo-request'; import { BasicDappInfo } from '@rabby-wallet/rabby-api/dist/types'; import { Divider, Tooltip } from 'antd'; @@ -72,6 +73,13 @@ export const DappCard = ({ ? 'Dapps_Search_Open_Favorite' : 'Dapps_Search_Open', }); + + ga4.fireEvent( + size === 'small' ? 'Dapps_Search_Open_Favorite' : 'Dapps_Search_Open', + { + event_category: 'DappsSearch', + } + ); }} >
diff --git a/src/ui/views/DappSearch/index.tsx b/src/ui/views/DappSearch/index.tsx index 50c196b4481..2387513c2c7 100644 --- a/src/ui/views/DappSearch/index.tsx +++ b/src/ui/views/DappSearch/index.tsx @@ -23,6 +23,7 @@ import { matomoRequestEvent } from '@/utils/matomo-request'; import { ConnectedSite } from '@/background/service/permission'; import { useReloadPageOnCurrentAccountChanged } from '@/ui/hooks/backgroundState/useAccount'; import { ChainSelectorButton } from '../ApprovalManagePage/components/ChainSelectorButton'; +import { ga4 } from '@/utils/ga4'; const { Search } = Input; const SearchWrapper = styled.div` @@ -145,6 +146,10 @@ export const DappSearchPage = () => { action: 'Dapps_Search_Begin', label: debouncedSearchValue, }); + + ga4.fireEvent('Dapps_Search_Begin', { + event_category: 'DappsSearch', + }); } } const limit = d?.page?.limit || 30; @@ -204,6 +209,10 @@ export const DappSearchPage = () => { category: 'DappsSearch', action: 'Dapps_Search_Enter', }); + + ga4.fireEvent('Dapps_Search_Enter', { + event_category: 'DappsSearch', + }); }); useReloadPageOnCurrentAccountChanged(); diff --git a/src/ui/views/Dashboard/components/ChainAndSiteSelector/index.tsx b/src/ui/views/Dashboard/components/ChainAndSiteSelector/index.tsx index 957ae9315fc..eade4987e03 100644 --- a/src/ui/views/Dashboard/components/ChainAndSiteSelector/index.tsx +++ b/src/ui/views/Dashboard/components/ChainAndSiteSelector/index.tsx @@ -55,6 +55,7 @@ import { useTranslation } from 'react-i18next'; import ThemeIcon from '@/ui/component/ThemeMode/ThemeIcon'; import { EcologyPopup } from '../EcologyPopup'; import { appIsDev } from '@/utils/env'; +import { ga4 } from '@/utils/ga4'; export default ({ gnosisPendingCount, @@ -349,6 +350,11 @@ export default ({ action: 'clickEntry', label: item.eventKey, }); + + ga4.fireEvent(`Entry_${item.eventKey}`, { + event_category: 'Dashboard', + }); + item?.onClick(evt); }} className="direction pointer" diff --git a/src/ui/views/Dashboard/components/CurrentConnection/index.tsx b/src/ui/views/Dashboard/components/CurrentConnection/index.tsx index 0367dbb99fe..50c031ece76 100644 --- a/src/ui/views/Dashboard/components/CurrentConnection/index.tsx +++ b/src/ui/views/Dashboard/components/CurrentConnection/index.tsx @@ -15,6 +15,7 @@ import { getCurrentTab, useWallet } from 'ui/utils'; import { MetamaskModePopup } from '../MetamaskModePopup'; import { ReactComponent as RcIconMetamask } from 'ui/assets/metamask-mode-circle-cc.svg'; import './style.less'; +import { ga4 } from '@/utils/ga4'; interface CurrentConnectionProps { onChainChange?: (chain: CHAINS_ENUM) => void; @@ -181,6 +182,10 @@ export const CurrentConnection = memo((props: CurrentConnectionProps) => { action: 'Click', label: 'Change Chain', }); + + ga4.fireEvent('Click_ChangeChain', { + event_category: 'Front Page Click', + }); }} showRPCStatus /> diff --git a/src/ui/views/Dashboard/components/Settings/index.tsx b/src/ui/views/Dashboard/components/Settings/index.tsx index 5f7b2b17672..1e8c5c69a11 100644 --- a/src/ui/views/Dashboard/components/Settings/index.tsx +++ b/src/ui/views/Dashboard/components/Settings/index.tsx @@ -59,6 +59,7 @@ import FeedbackPopup from '../Feedback'; import { getChainList } from '@/utils/chain'; import { SvgIconCross } from '@/ui/assets'; import { sendPersonalMessage } from '@/ui/utils/sendPersonalMessage'; +import { ga4 } from '@/utils/ga4'; const useAutoLockOptions = () => { const { t } = useTranslation(); @@ -552,6 +553,11 @@ const SettingsInner = ({ action: 'clickToUse', label: 'Whitelist', }); + + ga4.fireEvent('More_Whitelist', { + event_category: 'Click More', + }); + reportSettings('Whitelist'); handleWhitelistEnableChange(checked); }; @@ -660,6 +666,11 @@ const SettingsInner = ({ action: 'clickToUse', label: 'Lock Wallet', }); + + ga4.fireEvent('More_LockWallet', { + event_category: 'Click More', + }); + reportSettings('Lock Wallet'); }, }, @@ -673,6 +684,11 @@ const SettingsInner = ({ action: 'clickToUse', label: 'Signature Record', }); + + ga4.fireEvent('More_SignatureRecord', { + event_category: 'Click More', + }); + reportSettings('Signature Record'); }, }, @@ -686,6 +702,11 @@ const SettingsInner = ({ action: 'clickToUse', label: 'Manage Address', }); + + ga4.fireEvent('More_ManageAddress', { + event_category: 'Click More', + }); + reportSettings('Manage Address'); }, }, @@ -705,6 +726,11 @@ const SettingsInner = ({ action: 'clickToUse', label: 'Search Dapps', }); + + ga4.fireEvent('More_SearchDapps', { + event_category: 'Click More', + }); + reportSettings('Search Dapps'); openInternalPageInTab('dapp-search'); }, @@ -719,6 +745,11 @@ const SettingsInner = ({ action: 'clickToUse', label: 'Connected Dapps', }); + + ga4.fireEvent('More_ConnectedDapps', { + event_category: 'Click More', + }); + reportSettings('Connected Dapps'); }, }, @@ -750,6 +781,11 @@ const SettingsInner = ({ action: 'clickToUse', label: 'Custom Testnet', }); + + ga4.fireEvent('More_CustomTestnet', { + event_category: 'Click More', + }); + reportSettings('Custom Testnet'); }, }, @@ -763,6 +799,11 @@ const SettingsInner = ({ action: 'clickToUse', label: 'Custom RPC', }); + + ga4.fireEvent('More_CustomRPC', { + event_category: 'Click More', + }); + reportSettings('Custom RPC'); }, }, @@ -775,6 +816,11 @@ const SettingsInner = ({ action: 'clickToUse', label: 'Current Language', }); + + ga4.fireEvent('More_CurrentLanguage', { + event_category: 'Click More', + }); + reportSettings('Current Language'); setIsShowLangModal(true); }, @@ -802,6 +848,11 @@ const SettingsInner = ({ action: 'clickToUse', label: 'Theme Mode', }); + + ga4.fireEvent('More_ThemeMode', { + event_category: 'Click More', + }); + reportSettings('Theme Mode'); setIsShowThemeModeModal(true); }, @@ -831,6 +882,11 @@ const SettingsInner = ({ action: 'clickToUse', label: 'MetaMask Mode Dapps', }); + + ga4.fireEvent('More_MetaMaskModeDapps', { + event_category: 'Click More', + }); + reportSettings('MetaMask Mode Dapps'); }, }, @@ -843,6 +899,11 @@ const SettingsInner = ({ action: 'clickToUse', label: 'Auto lock time', }); + + ga4.fireEvent('More_AutoLockTime', { + event_category: 'Click More', + }); + reportSettings('Auto lock time'); setIsShowAutoLockModal(true); }, @@ -870,6 +931,11 @@ const SettingsInner = ({ action: 'clickToUse', label: 'Reset Account', }); + + ga4.fireEvent('More_ResetAccount', { + event_category: 'Click More', + }); + setShowResetAccountModal(true); reportSettings('Reset Account'); }, @@ -962,6 +1028,11 @@ const SettingsInner = ({ action: 'clickToUse', label: 'feedback', }); + + ga4.fireEvent('More_Feedback', { + event_category: 'Click More', + }); + reportSettings('feedback'); openInTab('https://debank.com/hi/0a110032'); }, @@ -982,6 +1053,11 @@ const SettingsInner = ({ action: 'clickToUse', label: 'Current Version', }); + + ga4.fireEvent('More_CurrentVersion', { + event_category: 'Click More', + }); + reportSettings('Current Version'); }, rightIcon: ( @@ -1029,6 +1105,11 @@ const SettingsInner = ({ action: 'clickToUse', label: 'Supported Chains', }); + + ga4.fireEvent('More_SupportedChains', { + event_category: 'Click More', + }); + reportSettings('Supported Chains'); }, rightIcon: ( @@ -1062,6 +1143,11 @@ const SettingsInner = ({ action: 'clickToUse', label: 'Find us|Twitter', }); + + ga4.fireEvent('More_FindUsTwitter', { + event_category: 'Click More', + }); + reportSettings('twitter'); }} className="ml-12 group" @@ -1085,6 +1171,11 @@ const SettingsInner = ({ action: 'clickToUse', label: 'Find us|Discord', }); + + ga4.fireEvent('More_FindUsDiscord', { + event_category: 'Click More', + }); + reportSettings('discord'); }} className="ml-12 group" @@ -1111,11 +1202,6 @@ const SettingsInner = ({ } const lockWallet = async () => { - matomoRequestEvent({ - category: 'Setting', - action: 'clickToUse', - label: 'lockWallet', - }); reportSettings('lockWallet'); await wallet.lockWallet(); history.push('/unlock'); diff --git a/src/ui/views/Dashboard/index.tsx b/src/ui/views/Dashboard/index.tsx index 57d4132f3ed..9886bc464d2 100644 --- a/src/ui/views/Dashboard/index.tsx +++ b/src/ui/views/Dashboard/index.tsx @@ -55,6 +55,7 @@ import { useHomeBalanceViewOuterPrefetch } from './components/BalanceView/useHom import { EcologyPopup } from './components/EcologyPopup'; import { GasAccountDashBoardHeader } from '../GasAccount/components/DashBoardHeader'; import { useGnosisPendingCount } from '@/ui/hooks/useGnosisPendingCount'; +import { ga4 } from '@/utils/ga4'; const Dashboard = () => { const history = useHistory(); @@ -273,6 +274,11 @@ const Dashboard = () => { action: 'Click', label: 'Gas Account', }); + + ga4.fireEvent('Click_GasAccount', { + event_category: 'Front Page Click', + }); + history.push('/gas-account'); }; const { dashboardBalanceCacheInited } = useHomeBalanceViewOuterPrefetch( @@ -307,6 +313,11 @@ const Dashboard = () => { action: 'Click', label: 'Change Address', }); + + ga4.fireEvent('Click_ChangeAddress', { + event_category: 'Front Page Click', + }); + history.push('/switch-address'); }; @@ -386,6 +397,10 @@ const Dashboard = () => { currentAccount?.brandName, ].join('|'), }); + + ga4.fireEvent('Click_CopyAddress', { + event_category: 'Front Page Click', + }); }} /> diff --git a/src/ui/views/ImportSuccess/index.tsx b/src/ui/views/ImportSuccess/index.tsx index 1c17d5f90e5..a0401ea24de 100644 --- a/src/ui/views/ImportSuccess/index.tsx +++ b/src/ui/views/ImportSuccess/index.tsx @@ -21,6 +21,7 @@ import './index.less'; import { useMedia } from 'react-use'; import { connectStore, useRabbyDispatch } from '@/ui/store'; import { Chain } from '@debank/common'; +import { ga4 } from '@/utils/ga4'; const ImportSuccess = ({ isPopup = false }: { isPopup?: boolean }) => { const history = useHistory(); @@ -98,6 +99,10 @@ const ImportSuccess = ({ isPopup = false }: { isPopup?: boolean }) => { action: 'importAddress', label: accounts[0].type, }); + + ga4.fireEvent(`Import_${accounts[0].type}`, { + event_category: 'Import Address', + }); } dispatch.account.getCurrentAccountAsync(); diff --git a/src/ui/views/MainRoute.tsx b/src/ui/views/MainRoute.tsx index 0e4f9d1979b..747a261710f 100644 --- a/src/ui/views/MainRoute.tsx +++ b/src/ui/views/MainRoute.tsx @@ -72,6 +72,7 @@ import { ImportSeedPhrase } from './NewUserImport/ImportSeedPhrase'; import { NewUserImportHardware } from './NewUserImport/ImportHardWare'; import { KEYRING_CLASS } from '@/constant'; import { MetamaskModeDapps } from './MetamaskModeDapps'; +import { ga4 } from '@/utils/ga4'; declare global { interface Window { @@ -80,8 +81,13 @@ declare global { } const LogPageView = () => { + const path = window.location.hash.replace(/#/, ''); + + ga4.firePageViewEvent({ + pageLocation: path, + }); if (window._paq) { - window._paq.push(['setCustomUrl', window.location.hash.replace(/#/, '')]); + window._paq.push(['setCustomUrl', path]); window._paq.push(['trackPageView']); } @@ -103,6 +109,15 @@ const Main = () => { ? `popup|${hasOtherProvider ? 'hasMetaMask' : 'noMetaMask'}` : `request|${hasOtherProvider ? 'hasMetaMask' : 'noMetaMask'}`, }); + + ga4.fireEvent( + UIType.isPop + ? `Popup_${hasOtherProvider ? 'HasMM' : 'NoMM'}` + : `Request_${hasOtherProvider ? 'HasMM' : 'NoMM'}`, + { + event_category: 'User Active', + } + ); } })(); }, []); diff --git a/src/utils/ga4.ts b/src/utils/ga4.ts new file mode 100644 index 00000000000..1be923e06b4 --- /dev/null +++ b/src/utils/ga4.ts @@ -0,0 +1,145 @@ +import Browser from 'webextension-polyfill'; +import { appIsDev } from './env'; + +const GA_ENDPOINT = 'https://www.google-analytics.com/mp/collect'; +const GA_DEBUG_ENDPOINT = 'https://www.google-analytics.com/debug/mp/collect'; + +// Get via https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag#recommended_parameters_for_reports +const MEASUREMENT_ID = 'G-XDNGZ67KEW'; +const API_SECRET = '_Im8XzsSR1u_y9mMMyT48w'; +const DEFAULT_ENGAGEMENT_TIME_MSEC = 100; + +// Duration of inactivity after which a new session is created +const SESSION_EXPIRATION_IN_MIN = 30; + +class Analytics { + debug: boolean; + + constructor(debug = false) { + this.debug = debug; + } + + // Returns the client id, or creates a new one if one doesn't exist. + // Stores client id in local storage to keep the same client id as long as + // the extension is installed. + async getOrCreateClientId() { + let { clientId } = await Browser.storage.local.get('clientId'); + if (!clientId) { + // Generate a unique client ID, the actual value is not relevant + clientId = self.crypto.randomUUID(); + await Browser.storage.local.set({ clientId }); + } + return clientId; + } + + // Returns the current session id, or creates a new one if one doesn't exist or + // the previous one has expired. + async getOrCreateSessionId() { + // Use storage.session because it is only in memory + let { sessionData } = await Browser.storage.session.get('sessionData'); + const currentTimeInMs = Date.now(); + // Check if session exists and is still valid + if (sessionData && sessionData.timestamp) { + // Calculate how long ago the session was last updated + const durationInMin = (currentTimeInMs - sessionData.timestamp) / 60000; + // Check if last update lays past the session expiration threshold + if (durationInMin > SESSION_EXPIRATION_IN_MIN) { + // Clear old session id to start a new session + sessionData = null; + } else { + // Update timestamp to keep session alive + sessionData.timestamp = currentTimeInMs; + await Browser.storage.session.set({ sessionData }); + } + } + if (!sessionData) { + // Create and store a new session + sessionData = { + session_id: currentTimeInMs.toString(), + timestamp: currentTimeInMs.toString(), + }; + await Browser.storage.session.set({ sessionData }); + } + return sessionData.session_id; + } + + // Fires an event with optional params. Event names must only include letters and underscores. + async fireEvent( + name: string, + params: { + session_id?: string; + engagement_time_msec?: number; + event_category?: string; + [key: string]: any; + } = {} + ) { + // Configure session id and engagement time if not present, for more details see: + // https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag#recommended_parameters_for_reports + if (!params.session_id) { + params.session_id = await this.getOrCreateSessionId(); + } + if (!params.engagement_time_msec) { + params.engagement_time_msec = DEFAULT_ENGAGEMENT_TIME_MSEC; + } + + try { + const response = await fetch( + `${ + this.debug ? GA_DEBUG_ENDPOINT : GA_ENDPOINT + }?measurement_id=${MEASUREMENT_ID}&api_secret=${API_SECRET}`, + { + method: 'POST', + body: JSON.stringify({ + client_id: await this.getOrCreateClientId(), + events: [ + { + name, + params, + }, + ], + user_properties: { + Extension_Version: { + value: process.env.release || '0.0.0', + }, + }, + }), + } + ); + if (!this.debug) { + return; + } + console.log(await response.text()); + } catch (e) { + console.error('Google Analytics request failed with an exception', e); + } + } + + // Fire a page view event. + async firePageViewEvent({ + pageTitle, + pageLocation, + additionalParams = {}, + }: { + pageTitle?: string; + pageLocation: string; + additionalParams?: { [key: string]: any }; + }) { + return this.fireEvent('page_view', { + page_title: pageTitle, + page_location: pageLocation, + ...additionalParams, + }); + } + + // Fire an error event. + async fireErrorEvent(error, additionalParams = {}) { + // Note: 'error' is a reserved event name and cannot be used + // see https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference?client_type=gtag#reserved_names + return this.fireEvent('extension_error', { + ...error, + ...additionalParams, + }); + } +} + +export const ga4 = new Analytics(appIsDev); diff --git a/yarn.lock b/yarn.lock index cbf53b06627..252bbb9a540 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17664,11 +17664,6 @@ react-dom@^18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" -react-ga@3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-3.3.0.tgz#c91f407198adcb3b49e2bc5c12b3fe460039b3ca" - integrity sha512-o8RScHj6Lb8cwy3GMrVH6NJvL+y0zpJvKtc0+wmH7Bt23rszJmnqEQxRbyrqUzk9DTJIHoP42bfO5rswC9SWBQ== - react-i18next@13.0.3: version "13.0.3" resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-13.0.3.tgz#8eedc5c2ab57f4641540778cc7373c8ed891523c"