Skip to content

Commit

Permalink
Merge branch 'dev' of github.com:safe-global/safe-wallet-web into nect
Browse files Browse the repository at this point in the history
  • Loading branch information
katspaugh committed Sep 28, 2023
2 parents ccb8b23 + e807c40 commit 242cf91
Show file tree
Hide file tree
Showing 19 changed files with 1,142 additions and 301 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@
"@web3-onboard/ledger": "2.3.2",
"@web3-onboard/trezor": "^2.4.2",
"@web3-onboard/walletconnect": "^2.4.5",
"blo": "^1.1.1",
"classnames": "^2.3.1",
"date-fns": "^2.29.2",
"ethereum-blockies-base64": "^1.0.2",
"ethers": "5.7.2",
"exponential-backoff": "^3.1.0",
"firebase": "^10.3.1",
Expand Down
12 changes: 9 additions & 3 deletions src/components/batch/BatchSidebar/BatchTxItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import { MethodDetails } from '@/components/transactions/TxDetails/TxData/Decode
import { TxDataRow } from '@/components/transactions/TxDetails/Summary/TxDataRow'
import { dateString } from '@/utils/formatters'
import { BATCH_EVENTS, trackEvent } from '@/services/analytics'
import { TransactionInfoType } from '@safe-global/safe-gateway-typescript-sdk'
import useABTesting from '@/services/tracking/useAbTesting'
import { AbTest } from '@/services/tracking/abTesting'

type BatchTxItemProps = DraftBatchItem & {
id: string
Expand All @@ -30,6 +33,8 @@ const BatchTxItem = ({
dragging = false,
draggable = false,
}: BatchTxItemProps) => {
const shouldDisplayHumanDescription = useABTesting(AbTest.HUMAN_DESCRIPTION)

const txSummary = useMemo(
() => ({
timestamp,
Expand All @@ -55,6 +60,9 @@ const BatchTxItem = ({
const handleExpand = () => {
trackEvent(BATCH_EVENTS.BATCH_EXPAND_TX)
}
const displayInfo =
(!txDetails.txInfo.richDecodedInfo && txDetails.txInfo.type !== TransactionInfoType.TRANSFER) ||
!shouldDisplayHumanDescription

return (
<ListItem disablePadding sx={{ gap: 2, alignItems: 'flex-start' }}>
Expand All @@ -75,9 +83,7 @@ const BatchTxItem = ({

<TxType tx={txSummary} />

<Box flex={1}>
<TxInfo info={txDetails.txInfo} />
</Box>
<Box flex={1}>{displayInfo && <TxInfo info={txDetails.txInfo} />}</Box>

{onDelete && (
<>
Expand Down
6 changes: 3 additions & 3 deletions src/components/common/EthHashInfo/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import makeBlockie from 'ethereum-blockies-base64'
import { blo } from 'blo'
import type { ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'

import { act, fireEvent, render, waitFor } from '@/tests/test-utils'
Expand Down Expand Up @@ -179,7 +179,7 @@ describe('EthHashInfo', () => {

expect(container.querySelector('.icon')).toHaveAttribute(
'style',
`background-image: url(${makeBlockie(MOCK_SAFE_ADDRESS)}); width: 40px; height: 40px;`,
`background-image: url(${blo(MOCK_SAFE_ADDRESS)}); width: 40px; height: 40px;`,
)
})

Expand All @@ -188,7 +188,7 @@ describe('EthHashInfo', () => {

expect(container.querySelector('.icon')).toHaveAttribute(
'style',
`background-image: url(${makeBlockie(MOCK_SAFE_ADDRESS)}); width: 100px; height: 100px;`,
`background-image: url(${blo(MOCK_SAFE_ADDRESS)}); width: 100px; height: 100px;`,
)
})

Expand Down
4 changes: 2 additions & 2 deletions src/components/common/Identicon/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ReactElement, CSSProperties } from 'react'
import { useMemo } from 'react'
import makeBlockie from 'ethereum-blockies-base64'
import { blo } from 'blo'
import Skeleton from '@mui/material/Skeleton'

import css from './styles.module.css'
Expand All @@ -13,7 +13,7 @@ export interface IdenticonProps {
const Identicon = ({ address, size = 40 }: IdenticonProps): ReactElement => {
const style = useMemo<CSSProperties | null>(() => {
try {
const blockie = makeBlockie(address)
const blockie = blo(address as `0x${string}`)
return {
backgroundImage: `url(${blockie})`,
width: `${size}px`,
Expand Down
2 changes: 1 addition & 1 deletion src/components/safe-messages/MsgSummary/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const MsgSummary = ({ msg }: { msg: SafeMessage }): ReactElement => {
const isConfirmed = msg.status === SafeMessageStatus.CONFIRMED

return (
<Box className={classNames(txSummaryCss.gridContainer, txSummaryCss.columnTemplateWithoutNonce)}>
<Box className={classNames(txSummaryCss.gridContainer, txSummaryCss.columnTemplate)}>
<Box gridArea="type" className={txSummaryCss.columnWrap}>
<MsgType msg={msg} />
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ListItemButton,
ListItemIcon,
ListItemText,
CircularProgress,
} from '@mui/material'
import { Fragment, useEffect, useMemo, useState } from 'react'
import type { ReactElement } from 'react'
Expand Down Expand Up @@ -44,7 +45,9 @@ export const transformAddedSafes = (addedSafes: AddedSafesState): NotifiableSafe
}

// Convert data structure of currently notified Safes
const transformCurrentSubscribedSafes = (allPreferences?: PushNotificationPreferences): NotifiableSafes | undefined => {
export const _transformCurrentSubscribedSafes = (
allPreferences?: PushNotificationPreferences,
): NotifiableSafes | undefined => {
if (!allPreferences) {
return
}
Expand All @@ -60,7 +63,10 @@ const transformCurrentSubscribedSafes = (allPreferences?: PushNotificationPrefer
}

// Merges added Safes and currently notified Safes into a single data structure without duplicates
const mergeNotifiableSafes = (addedSafes: AddedSafesState, currentSubscriptions?: NotifiableSafes): NotifiableSafes => {
export const _mergeNotifiableSafes = (
addedSafes: AddedSafesState,
currentSubscriptions?: NotifiableSafes,
): NotifiableSafes => {
const notifiableSafes = transformAddedSafes(addedSafes)

if (!currentSubscriptions) {
Expand All @@ -78,13 +84,19 @@ const mergeNotifiableSafes = (addedSafes: AddedSafesState, currentSubscriptions?
return notifiableSafes
}

const getTotalNotifiableSafes = (notifiableSafes: NotifiableSafes): number => {
export const _getTotalNotifiableSafes = (notifiableSafes: NotifiableSafes): number => {
return Object.values(notifiableSafes).reduce((acc, safeAddresses) => {
return (acc += safeAddresses.length)
}, 0)
}

const areAllSafesSelected = (notifiableSafes: NotifiableSafes, selectedSafes: NotifiableSafes): boolean => {
export const _areAllSafesSelected = (notifiableSafes: NotifiableSafes, selectedSafes: NotifiableSafes): boolean => {
const entries = Object.entries(notifiableSafes)

if (entries.length === 0) {
return false
}

return Object.entries(notifiableSafes).every(([chainId, safeAddresses]) => {
const hasChain = Object.keys(selectedSafes).includes(chainId)
const hasEverySafe = safeAddresses?.every((safeAddress) => selectedSafes[chainId]?.includes(safeAddress))
Expand All @@ -93,13 +105,24 @@ const areAllSafesSelected = (notifiableSafes: NotifiableSafes, selectedSafes: No
}

// Total number of signatures required to register selected Safes
const getTotalSignaturesRequired = (selectedSafes: NotifiableSafes, currentNotifiedSafes?: NotifiableSafes): number => {
return Object.keys(selectedSafes).filter((chainId) => {
return !Object.keys(currentNotifiedSafes || {}).includes(chainId)
}).length
export const _getTotalSignaturesRequired = (
selectedSafes: NotifiableSafes,
currentNotifiedSafes?: NotifiableSafes,
): number => {
return Object.entries(selectedSafes)
.filter(([, safeAddresses]) => safeAddresses.length > 0)
.reduce((acc, [chainId, safeAddresses]) => {
const isNewChain = !currentNotifiedSafes?.[chainId]
const isNewSafe = safeAddresses.some((safeAddress) => !currentNotifiedSafes?.[chainId]?.includes(safeAddress))

if (isNewChain || isNewSafe) {
acc += 1
}
return acc
}, 0)
}

const shouldRegisterSelectedSafes = (
export const _shouldRegisterSelectedSafes = (
selectedSafes: NotifiableSafes,
currentNotifiedSafes?: NotifiableSafes,
): boolean => {
Expand All @@ -108,7 +131,10 @@ const shouldRegisterSelectedSafes = (
})
}

const shouldUnregsiterSelectedSafes = (selectedSafes: NotifiableSafes, currentNotifiedSafes?: NotifiableSafes) => {
export const _shouldUnregsiterSelectedSafes = (
selectedSafes: NotifiableSafes,
currentNotifiedSafes?: NotifiableSafes,
) => {
return Object.entries(currentNotifiedSafes || {}).some(([chainId, safeAddresses]) => {
return safeAddresses.some((safeAddress) => !selectedSafes[chainId]?.includes(safeAddress))
})
Expand All @@ -117,7 +143,7 @@ const shouldUnregsiterSelectedSafes = (selectedSafes: NotifiableSafes, currentNo
// onSave logic

// Safes that need to be registered with the service
const getSafesToRegister = (
export const _getSafesToRegister = (
selectedSafes: NotifiableSafes,
currentNotifiedSafes?: NotifiableSafes,
): NotifiableSafes | undefined => {
Expand All @@ -141,7 +167,7 @@ const getSafesToRegister = (
}

// Safes that need to be unregistered with the service
const getSafesToUnregister = (
export const _getSafesToUnregister = (
selectedSafes: NotifiableSafes,
currentNotifiedSafes?: NotifiableSafes,
): NotifiableSafes | undefined => {
Expand Down Expand Up @@ -171,7 +197,7 @@ const getSafesToUnregister = (
}

// Whether the device needs to be unregistered from the service
const shouldUnregisterDevice = (
export const _shouldUnregisterDevice = (
chainId: string,
safeAddresses: Array<string>,
currentNotifiedSafes?: NotifiableSafes,
Expand All @@ -192,6 +218,7 @@ const shouldUnregisterDevice = (
export const GlobalPushNotifications = (): ReactElement | null => {
const chains = useChains()
const addedSafes = useAppSelector(selectAllAddedSafes)
const [isLoading, setIsLoading] = useState(false)

const { dismissPushNotificationBanner } = useDismissPushNotificationsBanner()
const { getAllPreferences } = useNotificationPreferences()
Expand All @@ -204,7 +231,7 @@ export const GlobalPushNotifications = (): ReactElement | null => {
// Current Safes registered for notifications in indexedDB
const currentNotifiedSafes = useMemo(() => {
const allPreferences = getAllPreferences()
return transformCurrentSubscribedSafes(allPreferences)
return _transformCurrentSubscribedSafes(allPreferences)
}, [getAllPreferences])

// `currentNotifiedSafes` is initially undefined until indexedDB resolves
Expand All @@ -222,15 +249,15 @@ export const GlobalPushNotifications = (): ReactElement | null => {

// Merged added Safes and `currentNotifiedSafes` (in case subscriptions aren't added)
const notifiableSafes = useMemo(() => {
return mergeNotifiableSafes(addedSafes, currentNotifiedSafes)
return _mergeNotifiableSafes(addedSafes, currentNotifiedSafes)
}, [addedSafes, currentNotifiedSafes])

const totalNotifiableSafes = useMemo(() => {
return getTotalNotifiableSafes(notifiableSafes)
return _getTotalNotifiableSafes(notifiableSafes)
}, [notifiableSafes])

const isAllSelected = useMemo(() => {
return areAllSafesSelected(notifiableSafes, selectedSafes)
return _areAllSafesSelected(notifiableSafes, selectedSafes)
}, [notifiableSafes, selectedSafes])

const onSelectAll = () => {
Expand All @@ -249,13 +276,13 @@ export const GlobalPushNotifications = (): ReactElement | null => {
}

const totalSignaturesRequired = useMemo(() => {
return getTotalSignaturesRequired(selectedSafes, currentNotifiedSafes)
return _getTotalSignaturesRequired(selectedSafes, currentNotifiedSafes)
}, [currentNotifiedSafes, selectedSafes])

const canSave = useMemo(() => {
return (
shouldRegisterSelectedSafes(selectedSafes, currentNotifiedSafes) ||
shouldUnregsiterSelectedSafes(selectedSafes, currentNotifiedSafes)
_shouldRegisterSelectedSafes(selectedSafes, currentNotifiedSafes) ||
_shouldUnregsiterSelectedSafes(selectedSafes, currentNotifiedSafes)
)
}, [selectedSafes, currentNotifiedSafes])

Expand All @@ -264,17 +291,20 @@ export const GlobalPushNotifications = (): ReactElement | null => {
return
}

setIsLoading(true)

// Although the (un-)registration functions will request permission in getToken we manually
// check beforehand to prevent multiple promises in registrationPromises from throwing
const isGranted = await requestNotificationPermission()

if (!isGranted) {
setIsLoading(false)
return
}

const registrationPromises: Array<Promise<unknown>> = []

const safesToRegister = getSafesToRegister(selectedSafes, currentNotifiedSafes)
const safesToRegister = _getSafesToRegister(selectedSafes, currentNotifiedSafes)
if (safesToRegister) {
registrationPromises.push(registerNotifications(safesToRegister))

Expand All @@ -284,10 +314,10 @@ export const GlobalPushNotifications = (): ReactElement | null => {
})
}

const safesToUnregister = getSafesToUnregister(selectedSafes, currentNotifiedSafes)
const safesToUnregister = _getSafesToUnregister(selectedSafes, currentNotifiedSafes)
if (safesToUnregister) {
const unregistrationPromises = Object.entries(safesToUnregister).flatMap(([chainId, safeAddresses]) => {
if (shouldUnregisterDevice(chainId, safeAddresses, currentNotifiedSafes)) {
if (_shouldUnregisterDevice(chainId, safeAddresses, currentNotifiedSafes)) {
return unregisterDeviceNotifications(chainId)
}
return safeAddresses.map((safeAddress) => unregisterSafeNotifications(chainId, safeAddress))
Expand All @@ -299,6 +329,8 @@ export const GlobalPushNotifications = (): ReactElement | null => {
await Promise.all(registrationPromises)

trackEvent(PUSH_NOTIFICATION_EVENTS.SAVE_SETTINGS)

setIsLoading(false)
}

if (totalNotifiableSafes === 0) {
Expand All @@ -322,8 +354,8 @@ export const GlobalPushNotifications = (): ReactElement | null => {

<CheckWallet allowNonOwner>
{(isOk) => (
<Button variant="contained" disabled={!canSave || !isOk} onClick={onSave}>
Save
<Button variant="contained" disabled={!canSave || !isOk || isLoading} onClick={onSave}>
{isLoading ? <CircularProgress size={20} /> : 'Save'}
</Button>
)}
</CheckWallet>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,11 @@ export const PushNotificationsBanner = ({ children }: { children: ReactElement }
const allPreferences = getAllPreferences()
const safesToRegister = getSafesToRegister(addedSafes, allPreferences)

await assertWalletChain(onboard, safe.chainId)
try {
await assertWalletChain(onboard, safe.chainId)
} catch {
return
}

await registerNotifications(safesToRegister)

Expand Down
Loading

0 comments on commit 242cf91

Please sign in to comment.