Skip to content

Commit

Permalink
fix: address comments
Browse files Browse the repository at this point in the history
  • Loading branch information
iamacook committed Sep 14, 2023
1 parent a115c14 commit 12116b7
Show file tree
Hide file tree
Showing 13 changed files with 70 additions and 72 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"@safe-global/safe-core-sdk-utils": "^1.7.4",
"@safe-global/safe-deployments": "1.25.0",
"@safe-global/safe-ethers-lib": "^1.9.4",
"@safe-global/safe-gateway-typescript-sdk": "^3.11.0",
"@safe-global/safe-gateway-typescript-sdk": "^3.12.0",
"@safe-global/safe-modules-deployments": "^1.0.0",
"@safe-global/safe-react-components": "^2.0.6",
"@sentry/react": "^7.28.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,13 +261,15 @@ export const GlobalPushNotifications = (): ReactElement | null => {
return
}

// Although the (un-)registration functions will request permission,
// we manually change beforehand prevent multiple promises from throwing
const isGranted = await requestNotificationPermission()

if (!isGranted) {
return
}

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

const safesToRegister = getSafesToRegister(selectedSafes, currentNotifiedSafes)
if (safesToRegister) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ export const PushNotificationsBanner = ({ children }: { children: ReactElement }
const dismissBanner = useCallback(() => {
trackEvent(PUSH_NOTIFICATION_EVENTS.DISMISS_BANNER)

setDismissedBannerPerChain({
...dismissedBannerPerChain,
setDismissedBannerPerChain((prev) => ({
...prev,
[safe.chainId]: true,
})
}, [dismissedBannerPerChain, safe.chainId, setDismissedBannerPerChain])
}))
}, [safe.chainId, setDismissedBannerPerChain])

// Click outside to dismiss banner
useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
font-size: 12px;
width: var(--space-5);
height: 24px;
z-index: 9999999;
position: relative;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ describe('useNotificationPreferences', () => {
})
})

it('should clearPreferences preferences, then hydrate the preferences state', async () => {
it('should delete all preferences, then hydrate the preferences state', async () => {
const chainId1 = '1'
const safeAddress1 = hexZeroPad('0x1', 20)
const safeAddress2 = hexZeroPad('0x1', 20)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,13 +302,13 @@ describe('useNotificationRegistrations', () => {
unregisterDeviceSpy.mockImplementation(() => Promise.resolve('Unregistration could not be completed.'))

const uuid = self.crypto.randomUUID()
const clearPreferencesMock = jest.fn()
const deleteAllPreferencesMock = jest.fn()

;(preferences.useNotificationPreferences as jest.Mock).mockImplementation(
() =>
({
uuid,
_clearPreferences: clearPreferencesMock,
_deleteAllPreferences: deleteAllPreferencesMock,
} as unknown as ReturnType<typeof preferences.useNotificationPreferences>),
)

Expand All @@ -318,20 +318,20 @@ describe('useNotificationRegistrations', () => {

expect(unregisterDeviceSpy).toHaveBeenCalledWith('1', uuid)

expect(clearPreferencesMock).not.toHaveBeenCalled()
expect(deleteAllPreferencesMock).not.toHaveBeenCalled()
})

it('does not clear preferences if unregistration throws', async () => {
unregisterDeviceSpy.mockImplementation(() => Promise.reject())

const uuid = self.crypto.randomUUID()
const clearPreferencesMock = jest.fn()
const deleteAllPreferencesMock = jest.fn()

;(preferences.useNotificationPreferences as jest.Mock).mockImplementation(
() =>
({
uuid,
_clearPreferences: clearPreferencesMock,
_deleteAllPreferences: deleteAllPreferencesMock,
} as unknown as ReturnType<typeof preferences.useNotificationPreferences>),
)

Expand All @@ -341,20 +341,20 @@ describe('useNotificationRegistrations', () => {

expect(unregisterDeviceSpy).toHaveBeenCalledWith('1', uuid)

expect(clearPreferencesMock).not.toHaveBeenCalledWith()
expect(deleteAllPreferencesMock).not.toHaveBeenCalledWith()
})

it('clears preferences if unregistration succeeds', async () => {
unregisterDeviceSpy.mockImplementation(() => Promise.resolve())

const uuid = self.crypto.randomUUID()
const clearPreferencesMock = jest.fn()
const deleteAllPreferencesMock = jest.fn()

;(preferences.useNotificationPreferences as jest.Mock).mockImplementation(
() =>
({
uuid,
_clearPreferences: clearPreferencesMock,
_deleteAllPreferences: deleteAllPreferencesMock,
} as unknown as ReturnType<typeof preferences.useNotificationPreferences>),
)

Expand All @@ -364,7 +364,7 @@ describe('useNotificationRegistrations', () => {

expect(unregisterDeviceSpy).toHaveBeenCalledWith('1', uuid)

expect(clearPreferencesMock).toHaveBeenCalled()
expect(deleteAllPreferencesMock).toHaveBeenCalled()
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const useNotificationPreferences = (): {
) => void
_createPreferences: (safesToRegister: NotifiableSafes) => void
_deletePreferences: (safesToUnregister: NotifiableSafes) => void
_clearPreferences: () => void
_deleteAllPreferences: () => void
} => {
// State
const uuid = useUuid()
Expand Down Expand Up @@ -195,7 +195,7 @@ export const useNotificationPreferences = (): {
}

// Delete all preferences store entries
const clearPreferences = () => {
const deleteAllPreferences = () => {
if (!preferencesStore) {
return
}
Expand All @@ -212,6 +212,6 @@ export const useNotificationPreferences = (): {
updatePreferences,
_createPreferences: createPreferences,
_deletePreferences: deletePreferences,
_clearPreferences: clearPreferences,
_deleteAllPreferences: deleteAllPreferences,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { logError } from '@/services/exceptions'
import ErrorCodes from '@/services/exceptions/ErrorCodes'
import type { NotifiableSafes } from '../logic'

const registrationFlow = async (registrationFn: Promise<void>, callback: () => void) => {
const registrationFlow = async (registrationFn: Promise<void>, callback: () => void): Promise<boolean> => {
let success = false

try {
Expand All @@ -27,16 +27,19 @@ const registrationFlow = async (registrationFn: Promise<void>, callback: () => v
if (success) {
callback()
}

return success
}

export const useNotificationRegistrations = (): {
registerNotifications: (safesToRegister: NotifiableSafes, withSignature?: boolean) => Promise<void>
unregisterSafeNotifications: (chainId: string, safeAddress: string) => Promise<void>
unregisterChainNotifications: (chainId: string) => Promise<void>
registerNotifications: (safesToRegister: NotifiableSafes, withSignature?: boolean) => Promise<boolean | undefined>
unregisterSafeNotifications: (chainId: string, safeAddress: string) => Promise<boolean | undefined>
unregisterChainNotifications: (chainId: string) => Promise<boolean | undefined>
} => {
const dispatch = useAppDispatch()
const web3 = useWeb3()

const { uuid, _createPreferences, _deletePreferences, _clearPreferences } = useNotificationPreferences()
const { uuid, _createPreferences, _deletePreferences, _deleteAllPreferences } = useNotificationPreferences()

const registerNotifications = async (safesToRegister: NotifiableSafes) => {
if (!uuid || !web3) {
Expand All @@ -53,7 +56,7 @@ export const useNotificationRegistrations = (): {
return registerDevice(payload)
}

await registrationFlow(register(), () => {
return registrationFlow(register(), () => {
_createPreferences(safesToRegister)

const totalRegistered = Object.values(safesToRegister).reduce(
Expand All @@ -80,7 +83,7 @@ export const useNotificationRegistrations = (): {

const unregisterSafeNotifications = async (chainId: string, safeAddress: string) => {
if (uuid) {
await registrationFlow(unregisterSafe(chainId, safeAddress, uuid), () => {
return registrationFlow(unregisterSafe(chainId, safeAddress, uuid), () => {
_deletePreferences({ [chainId]: [safeAddress] })
trackEvent(PUSH_NOTIFICATION_EVENTS.UNREGISTER_SAFE)
})
Expand All @@ -89,8 +92,8 @@ export const useNotificationRegistrations = (): {

const unregisterChainNotifications = async (chainId: string) => {
if (uuid) {
await registrationFlow(unregisterDevice(chainId, uuid), () => {
_clearPreferences()
return registrationFlow(unregisterDevice(chainId, uuid), () => {
_deleteAllPreferences()
trackEvent(PUSH_NOTIFICATION_EVENTS.UNREGISTER_DEVICE)
})
}
Expand Down
43 changes: 22 additions & 21 deletions src/components/settings/PushNotifications/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,19 @@ import { GlobalPushNotifications } from './GlobalPushNotifications'
import useIsSafeOwner from '@/hooks/useIsSafeOwner'
import { IS_DEV } from '@/config/constants'
import { useAppDispatch } from '@/store'
import { showNotification } from '@/store/notificationsSlice'
import { trackEvent } from '@/services/analytics'
import { PUSH_NOTIFICATION_EVENTS } from '@/services/analytics/events/push-notifications'
import { AppRoutes } from '@/config/routes'
import CheckWallet from '@/components/common/CheckWallet'
import { useIsMac } from '@/hooks/useIsMac'

import css from './styles.module.css'

export const PushNotifications = (): ReactElement => {
const dispatch = useAppDispatch()
const { safe, safeLoaded } = useSafeInfo()
const isOwner = useIsSafeOwner()
const isMac = useIsMac()

const { updatePreferences, getPreferences, getAllPreferences } = useNotificationPreferences()
const { unregisterSafeNotifications, unregisterChainNotifications, registerNotifications } =
Expand All @@ -45,7 +46,6 @@ export const PushNotifications = (): ReactElement => {
updatePreferences(safe.chainId, safe.address.value, newPreferences)
}

const isMac = typeof navigator !== 'undefined' && navigator.userAgent.includes('Mac')
const shouldShowMacHelper = isMac || IS_DEV

const handleOnChange = async () => {
Expand Down Expand Up @@ -216,28 +216,29 @@ export const PushNotifications = (): ReactElement => {
control={
<Checkbox
checked={preferences[WebhookType.CONFIRMATION_REQUEST]}
onChange={async (_, checked) => {
registerNotifications({
[safe.chainId]: [safe.address.value],
})
.then(() => {
setPreferences({
...preferences,
[WebhookType.CONFIRMATION_REQUEST]: checked,
})
onChange={(_, checked) => {
const updateConfirmationRequestPreferences = () => {
setPreferences({
...preferences,
[WebhookType.CONFIRMATION_REQUEST]: checked,
})

trackEvent({ ...PUSH_NOTIFICATION_EVENTS.TOGGLE_CONFIRMATION_REQUEST, label: checked })
trackEvent({ ...PUSH_NOTIFICATION_EVENTS.TOGGLE_CONFIRMATION_REQUEST, label: checked })
}

dispatch(
showNotification({
message:
'You will now receive notifications about confirmation requests for this Safe Account in your browser.',
variant: 'success',
groupKey: 'notifications',
}),
)
if (checked) {
registerNotifications({
[safe.chainId]: [safe.address.value],
})
.catch(() => null)
.then((registered) => {
if (registered) {
updateConfirmationRequestPreferences()
}
})
.catch(() => null)
} else {
updateConfirmationRequestPreferences()
}
}}
/>
}
Expand Down
12 changes: 1 addition & 11 deletions src/components/settings/PushNotifications/logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import type { RegisterNotificationsRequest } from '@safe-global/safe-gateway-typ
import type { Web3Provider } from '@ethersproject/providers'

import { FIREBASE_VAPID_KEY, initializeFirebaseApp } from '@/services/push-notifications/firebase'
import { trackEvent } from '@/services/analytics'
import { PUSH_NOTIFICATION_EVENTS } from '@/services/analytics/events/push-notifications'
import packageJson from '../../../../package.json'
import { logError } from '@/services/exceptions'
import ErrorCodes from '@/services/exceptions/ErrorCodes'
Expand All @@ -29,15 +27,7 @@ export const requestNotificationPermission = async (): Promise<boolean> => {
logError(ErrorCodes._400, e)
}

const isGranted = permission === 'granted'

trackEvent(isGranted ? PUSH_NOTIFICATION_EVENTS.GRANT_PERMISSION : PUSH_NOTIFICATION_EVENTS.REJECT_PERMISSION)

if (!isGranted) {
alert('You must allow notifications to register your device.')
}

return isGranted
return permission === 'granted'
}

const getSafeRegistrationSignature = ({
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useDecodeTx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const useDecodeTx = (tx?: SafeTransaction): AsyncResult<DecodedDataResponse> =>
const nativeTransfer = isEmptyData && !isRejection ? getNativeTransferData(tx?.data) : undefined

const [data = nativeTransfer, error, loading] = useAsync<DecodedDataResponse>(() => {
if (!encodedData || isEmptyData || !tx.data.to) return
if (!encodedData || isEmptyData) return
return getDecodedData(chainId, encodedData, tx.data.to)
}, [chainId, encodedData, isEmptyData, tx?.data.to])

Expand Down
13 changes: 13 additions & 0 deletions src/hooks/useIsMac.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useState, useEffect } from 'react'

export const useIsMac = (): boolean => {
const [isMac, setIsMac] = useState(false)

useEffect(() => {
if (typeof navigator !== 'undefined') {
setIsMac(navigator.userAgent.includes('Mac'))
}
}, [])

return isMac
}
10 changes: 0 additions & 10 deletions src/services/analytics/events/push-notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,6 @@ export const PUSH_NOTIFICATION_EVENTS = {
action: 'Click notification',
category,
},
// User granted notification permissions
GRANT_PERMISSION: {
action: 'Allow notifications',
category,
},
// User refused notification permissions
REJECT_PERMISSION: {
action: 'Reject notifications',
category,
},
// User registered Safe(s) for notifications
REGISTER_SAFES: {
action: 'Register Safe(s) notifications',
Expand Down

0 comments on commit 12116b7

Please sign in to comment.