Skip to content

Commit

Permalink
fix: enable notifications per chain (#2584)
Browse files Browse the repository at this point in the history
* fix: enable notifications per chain

* fix: adjust text

* fix: "Safes" -> "Safe Accounts"

* fix: remove Safes on unsupported chains
  • Loading branch information
iamacook authored Oct 4, 2023
1 parent ca9892c commit 4bc563c
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from '@mui/material'
import { Fragment, useEffect, useMemo, useState } from 'react'
import type { ReactElement } from 'react'
import type { ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'

import EthHashInfo from '@/components/common/EthHashInfo'
import { sameAddress } from '@/utils/addresses'
Expand All @@ -37,7 +38,7 @@ import css from './styles.module.css'
// UI logic

// Convert data structure of added Safes
export const transformAddedSafes = (addedSafes: AddedSafesState): NotifiableSafes => {
export const _transformAddedSafes = (addedSafes: AddedSafesState): NotifiableSafes => {
return Object.entries(addedSafes).reduce<NotifiableSafes>((acc, [chainId, addedSafesOnChain]) => {
acc[chainId] = Object.keys(addedSafesOnChain)
return acc
Expand All @@ -62,12 +63,28 @@ export const _transformCurrentSubscribedSafes = (
}, {})
}

// Remove Safes that are not on a supported chain
export const _sanitizeNotifiableSafes = (
chains: Array<ChainInfo>,
notifiableSafes: NotifiableSafes,
): NotifiableSafes => {
return Object.entries(notifiableSafes).reduce<NotifiableSafes>((acc, [chainId, safeAddresses]) => {
const chain = chains.find((chain) => chain.chainId === chainId)

if (chain) {
acc[chainId] = safeAddresses
}

return acc
}, {})
}

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

if (!currentSubscriptions) {
return notifiableSafes
Expand Down Expand Up @@ -249,8 +266,9 @@ export const GlobalPushNotifications = (): ReactElement | null => {

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

const totalNotifiableSafes = useMemo(() => {
return _getTotalNotifiableSafes(notifiableSafes)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,28 @@
import { _getSafesToRegister } from '.'
import type { AddedSafesState } from '@/store/addedSafesSlice'
import type { AddedSafesOnChain } from '@/store/addedSafesSlice'
import type { PushNotificationPreferences } from '@/services/push-notifications/preferences'

describe('PushNotificationsBanner', () => {
describe('getSafesToRegister', () => {
it('should return all added safes if no preferences exist', () => {
const addedSafes = {
'1': {
'0x123': {},
'0x456': {},
},
'4': {
'0x789': {},
},
} as unknown as AddedSafesState
const addedSafesOnChain = {
'0x123': {},
'0x456': {},
} as unknown as AddedSafesOnChain
const allPreferences = undefined

const result = _getSafesToRegister(addedSafes, allPreferences)
const result = _getSafesToRegister('1', addedSafesOnChain, allPreferences)

expect(result).toEqual({
'1': ['0x123', '0x456'],
'4': ['0x789'],
})
})

it('should return only newly added safes if preferences exist', () => {
const addedSafes = {
'1': {
'0x123': {},
'0x456': {},
},
'4': {
'0x789': {},
},
} as unknown as AddedSafesState
const addedSafesOnChain = {
'0x123': {},
'0x456': {},
} as unknown as AddedSafesOnChain
const allPreferences = {
'1:0x123': {
safeAddress: '0x123',
Expand All @@ -45,23 +34,18 @@ describe('PushNotificationsBanner', () => {
},
} as unknown as PushNotificationPreferences

const result = _getSafesToRegister(addedSafes, allPreferences)
const result = _getSafesToRegister('1', addedSafesOnChain, allPreferences)

expect(result).toEqual({
'1': ['0x456'],
})
})

it('should return all added safes if no preferences match', () => {
const addedSafes = {
'1': {
'0x123': {},
'0x456': {},
},
'4': {
'0x789': {},
},
} as unknown as AddedSafesState
const addedSafesOnChain = {
'0x123': {},
'0x456': {},
} as unknown as AddedSafesOnChain
const allPreferences = {
'1:0x111': {
safeAddress: '0x111',
Expand All @@ -73,11 +57,10 @@ describe('PushNotificationsBanner', () => {
},
} as unknown as PushNotificationPreferences

const result = _getSafesToRegister(addedSafes, allPreferences)
const result = _getSafesToRegister('1', addedSafesOnChain, allPreferences)

expect(result).toEqual({
'1': ['0x123', '0x456'],
'4': ['0x789'],
})
})
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ import type { ReactElement } from 'react'
import { CustomTooltip } from '@/components/common/CustomTooltip'
import { AppRoutes } from '@/config/routes'
import { useAppSelector } from '@/store'
import { selectAllAddedSafes, selectTotalAdded } from '@/store/addedSafesSlice'
import { selectAddedSafes, selectAllAddedSafes, selectTotalAdded } from '@/store/addedSafesSlice'
import PushNotificationIcon from '@/public/images/notifications/push-notification.svg'
import useLocalStorage from '@/services/local-storage/useLocalStorage'
import { useNotificationRegistrations } from '../hooks/useNotificationRegistrations'
import { transformAddedSafes } from '../GlobalPushNotifications'
import { PUSH_NOTIFICATION_EVENTS } from '@/services/analytics/events/push-notifications'
import { trackEvent } from '@/services/analytics'
import useSafeInfo from '@/hooks/useSafeInfo'
Expand All @@ -21,9 +20,9 @@ import { useNotificationPreferences } from '../hooks/useNotificationPreferences'
import { sameAddress } from '@/utils/addresses'
import useOnboard from '@/hooks/wallets/useOnboard'
import { assertWalletChain } from '@/services/tx/tx-sender/sdk'
import { useHasFeature } from '@/hooks/useChains'
import { useCurrentChain, useHasFeature } from '@/hooks/useChains'
import { FEATURES } from '@/utils/chains'
import type { AddedSafesState } from '@/store/addedSafesSlice'
import type { AddedSafesOnChain } from '@/store/addedSafesSlice'
import type { PushNotificationPreferences } from '@/services/push-notifications/preferences'
import type { NotifiableSafes } from '../logic'

Expand Down Expand Up @@ -66,44 +65,39 @@ export const useDismissPushNotificationsBanner = () => {
}

export const _getSafesToRegister = (
addedSafes: AddedSafesState,
chainId: string,
addedSafesOnChain: AddedSafesOnChain,
allPreferences: PushNotificationPreferences | undefined,
) => {
// Regiser all added Safes
): NotifiableSafes => {
const addedSafeAddressesOnChain = Object.keys(addedSafesOnChain)

if (!allPreferences) {
return transformAddedSafes(addedSafes)
return { [chainId]: addedSafeAddressesOnChain }
}

// Only register Safes that are not already registered
return Object.entries(addedSafes).reduce<NotifiableSafes>((acc, [chainId, addedSafesOnChain]) => {
const addedSafeAddressesOnChain = Object.keys(addedSafesOnChain)
const notificationRegistrations = Object.values(allPreferences)

const newlyAddedSafes = addedSafeAddressesOnChain.filter((safeAddress) => {
return !notificationRegistrations.some(
(registration) => chainId === registration.chainId && sameAddress(registration.safeAddress, safeAddress),
)
})
const notificationRegistrations = Object.values(allPreferences)

if (newlyAddedSafes.length > 0) {
acc[chainId] = newlyAddedSafes
}
const newlyAddedSafes = addedSafeAddressesOnChain.filter((safeAddress) => {
return !notificationRegistrations.some(
(registration) => chainId === registration.chainId && sameAddress(registration.safeAddress, safeAddress),
)
})

return acc
}, {})
return { [chainId]: newlyAddedSafes }
}

export const PushNotificationsBanner = ({ children }: { children: ReactElement }): ReactElement => {
const isNotificationsEnabled = useHasFeature(FEATURES.PUSH_NOTIFICATIONS)
const addedSafes = useAppSelector(selectAllAddedSafes)
const chain = useCurrentChain()
const totalAddedSafes = useAppSelector(selectTotalAdded)
const { safe, safeAddress } = useSafeInfo()
const addedSafesOnChain = useAppSelector((state) => selectAddedSafes(state, safe.chainId))
const { query } = useRouter()
const onboard = useOnboard()

const { dismissPushNotificationBanner, isPushNotificationBannerDismissed } = useDismissPushNotificationsBanner()

const isSafeAdded = !!addedSafes?.[safe.chainId]?.[safeAddress]
const isSafeAdded = !!addedSafesOnChain?.[safeAddress]
const shouldShowBanner = isNotificationsEnabled && !isPushNotificationBannerDismissed && isSafeAdded

const { registerNotifications } = useNotificationRegistrations()
Expand All @@ -121,14 +115,14 @@ export const PushNotificationsBanner = ({ children }: { children: ReactElement }
}, [dismissBanner, shouldShowBanner])

const onEnableAll = async () => {
if (!onboard) {
if (!onboard || !addedSafesOnChain) {
return
}

trackEvent(PUSH_NOTIFICATION_EVENTS.ENABLE_ALL)

const allPreferences = getAllPreferences()
const safesToRegister = _getSafesToRegister(addedSafes, allPreferences)
const safesToRegister = _getSafesToRegister(safe.chainId, addedSafesOnChain, allPreferences)

try {
await assertWalletChain(onboard, safe.chainId)
Expand Down Expand Up @@ -168,8 +162,9 @@ export const PushNotificationsBanner = ({ children }: { children: ReactElement }
<SvgIcon component={CloseIcon} inheritViewBox color="border" fontSize="small" />
</IconButton>
<Typography mt={0.5} mb={1.5} variant="body2">
Get notified about pending signatures, incoming and outgoing transactions and more when Safe{`{Wallet}`}{' '}
is in the background or closed.
Get notified about pending signatures, incoming and outgoing transactions for all Safe Accounts on{' '}
{chain?.chainName} when Safe
{`{Wallet}`} is in the background or closed.
</Typography>
{/* Cannot wrap singular button as it causes style inconsistencies */}
<CheckWallet>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'

import {
transformAddedSafes,
_transformAddedSafes,
_mergeNotifiableSafes,
_transformCurrentSubscribedSafes,
_getTotalNotifiableSafes,
Expand All @@ -10,6 +12,7 @@ import {
_getSafesToRegister,
_getSafesToUnregister,
_shouldUnregisterDevice,
_sanitizeNotifiableSafes,
} from '../GlobalPushNotifications'
import type { AddedSafesState } from '@/store/addedSafesSlice'

Expand All @@ -31,7 +34,7 @@ describe('GlobalPushNotifications', () => {
'4': ['0x789'],
}

expect(transformAddedSafes(addedSafes)).toEqual(expectedNotifiableSafes)
expect(_transformAddedSafes(addedSafes)).toEqual(expectedNotifiableSafes)
})
})

Expand Down Expand Up @@ -71,7 +74,24 @@ describe('GlobalPushNotifications', () => {
},
} as unknown as AddedSafesState

expect(_mergeNotifiableSafes(addedSafes)).toEqual(transformAddedSafes(addedSafes))
expect(_mergeNotifiableSafes(addedSafes)).toEqual(_transformAddedSafes(addedSafes))
})
})

describe('sanitizeNotifiableSafes', () => {
it('should remove Safes that are not on a supported chain', () => {
const chains = [{ chainId: '1', name: 'Mainnet' }] as unknown as Array<ChainInfo>

const notifiableSafes = {
'1': ['0x123', '0x456'],
'4': ['0xabc'],
}

const expected = {
'1': ['0x123', '0x456'],
}

expect(_sanitizeNotifiableSafes(chains, notifiableSafes)).toEqual(expected)
})
})

Expand Down

0 comments on commit 4bc563c

Please sign in to comment.