Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove price-change push event subscription for deprecated currencies #4523

Merged
merged 11 commits into from
Oct 23, 2023
18 changes: 12 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
- changed: Banxa sell to use new widget
- changed: Replace deprecated memo handling with `EdgeMemo` in SendScene2
- changed: Re-enable Piratechain
- changed: Remove price-change push notification subscriptions for keys-only currencies
- fixed: Crash in react-native-linear-gradient when app built with Xcode 15

## 3.19.0
Expand Down Expand Up @@ -255,7 +256,7 @@
- changed: Disable Firebase AD ID
- changed: Upgrade to ESLint v8
- removed: Unused React-based partner plugins
- fixed: Rename "View Xpub address" to "Private View Key" for Pirate Chain and Zcash
- fixed: Rename "View Xpub address" to "Private View Key" for Pirate Chain and Zcash
- fixed: Private view key modal warning text for other currencies besides Monero
- fixed: Handle unhandled promises
- fixed: Wallet list row sync circle component recycling
Expand Down Expand Up @@ -350,7 +351,7 @@
- Fixed: Fix broken max-spend for zkSync
- Deprecate WalletConnect v1
- EVM/ALGO: Add parseWalletConnectV2Payload to parse out amounts from WalletConnect v2 payloads
ZEC: Update checkpoints
ZEC: Update checkpoints
- Upgrade edge-login-ui-rn to v2.3.3
- fixed: Modal close button covering modal submit buttons while Android keyboard is open
- fixed: Username availability check error would incorrectly show in some cases
Expand Down Expand Up @@ -850,6 +851,7 @@ ZEC: Update checkpoints
- changed: Make sensitive account & wallet properties, like keys, non-enumerable.
- changed: Use the pluginId as the wallet logging prefix, instead of the currency code.
- Upgrade edge-currency-accountbased to v0.22.4

- Convert library to React Native Module
- This package will automatically install itself using React Native autolinking and no longer requires Webpack for integration
- Plugins are broken out and can be loaded individually
Expand All @@ -876,6 +878,7 @@ ZEC: Update checkpoints
- EVM: Remove recursion from getMaxSpendable
- Replace remaining json-schema usage with cleaners
- Update checkpoint files

- Upgrade edge-currency-monero to v0.5.5
- Add getMaxSpendable
- Upgrade edge-core-js to v0.19.36
Expand Down Expand Up @@ -1330,7 +1333,7 @@ ZEC: Update checkpoints
- LetsExchange: Fix min amount currency display
- Upgrade edge-core-js to v0.19.23
- Upgrade edge-login-ui-rn v0.10.7
changed: Update forget account description text
changed: Update forget account description text
- changed: Update PIN description text
- changed: Add titles for resecure password/pin scenes
- changed: Add SKIP button for resecure password and pin scenes
Expand Down Expand Up @@ -1469,7 +1472,7 @@ ZEC: Update checkpoints
- Fix FTM network fees test
- Fix ftmInfo.js filename
- Add timeout to getSupportedCurrencies test to prevent hanging
Upgrade edge-currency-bitcoin to v4.9.23
Upgrade edge-currency-bitcoin to v4.9.23
- DASH: Recognize the Instantlock for incoming Dash transactions
- Work around Android PBKDF2 failures
- Upgrade edge-currency-monero to v0.4.1
Expand Down Expand Up @@ -2287,7 +2290,7 @@ Upgrade edge-currency-bitcoin to v4.9.23
- Upgrade to edge-core-js v0.17.29
- Upgrade to Webpack 5
- Upgrade edge-login-ui-rn to v0.9.0
- *Breaking change*: This release contains a breaking change that was not indicated in the minor version update:
- _Breaking change_: This release contains a breaking change that was not indicated in the minor version update:
- rn: Prompt for notification permissions to support security features
- rn: Update modal colors
- Upgrade edge-currency-monero to v0.2.10
Expand Down Expand Up @@ -2988,7 +2991,8 @@ Upgrade edge-currency-bitcoin to v4.9.23
- New transaction details screen
- Enhanced deeplinking capabilities
- Bug fixes and visual enhancements
- ***BREAKING CHANGE*** Upgrade edge-core-js to v0.17.0
- **_BREAKING CHANGE_** Upgrade edge-core-js to v0.17.0

- This release also renames all `pluginName` instances to `pluginId`. This affects all plugin types, but the core contains compatibility code so old currency plugins continue working (but not for rate or swap plugins, which are easier to just upgrade).
- Breaking changes to the swap API:
- Return a new `EdgeSwapResult` structure from `EdgeSwapQuote.approve`. This now contains the `destinationAddress` and `orderId` that used to exist on the `EdgeSwapQuote` type.
Expand All @@ -3005,11 +3009,13 @@ Upgrade edge-currency-bitcoin to v4.9.23
- Remove deprecated `EdgeIo.WebSocket`.

- Upgrade edge-exchange-plugins to v0.10.2

- Add Switchain swap plugin.
- Pass promo codes to Changelly, ChangeNow, and Godex.
- Fix ChangeNow on Android & add better logging.

- Upgrade edge-currency-accountbased to v0.7.2

- Add cleaners v0.2.0 type checking
- Fix duplicate FIO address after registration
- Reprioritize EOS Hyperion nodes to resolve transaction history view issue
Expand Down
110 changes: 65 additions & 45 deletions src/actions/NotificationActions.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import messaging from '@react-native-firebase/messaging'
import { asMaybe } from 'cleaners'
import { EdgeCurrencyInfo } from 'edge-core-js'
import { EdgeContext, EdgeCurrencyInfo } from 'edge-core-js'
import { getUniqueId } from 'react-native-device-info'
import { base64 } from 'rfc4648'
import { sprintf } from 'sprintf-js'

import { asDevicePayload, DeviceUpdatePayload, NewPushEvent } from '../controllers/action-queue/types/pushApiTypes'
import { SPECIAL_CURRENCY_INFO } from '../constants/WalletAndCurrencyConstants'
import { asDevicePayload, DevicePayload, DeviceUpdatePayload, NewPushEvent } from '../controllers/action-queue/types/pushApiTypes'
import { asPriceChangeTrigger } from '../controllers/action-queue/types/pushCleaners'
import { PriceChangeTrigger } from '../controllers/action-queue/types/pushTypes'
import { ENV } from '../env'
Expand Down Expand Up @@ -33,7 +34,7 @@ export function registerNotificationsV2(changeFiat: boolean = false): ThunkActio
return async (dispatch, getState) => {
const state = getState()
const { defaultIsoFiat } = state.ui.settings
let v2Settings: ReturnType<typeof asDevicePayload> = {
let serverSettings: DevicePayload = {
loginIds: [],
events: [],
ignoreMarketing: false,
Expand All @@ -59,32 +60,43 @@ export function registerNotificationsV2(changeFiat: boolean = false): ThunkActio
}
const response = await fetchPush('v2/device/', opts)

v2Settings = asDevicePayload(await response.text())
serverSettings = asDevicePayload(await response.text())

const currencyWallets = state.core.account.currencyWallets
const activeCurrencyInfos = getActiveWalletCurrencyInfos(currencyWallets)

const createEvents: NewPushEvent[] = []
const removeEvents: string[] = []

if (v2Settings.events.length !== 0) {
if (serverSettings.events.length !== 0) {
// v2 settings exist already, see if we need to add new ones
const missingInfos: { [pluginId: string]: EdgeCurrencyInfo } = {}
for (const currencyInfo of activeCurrencyInfos) {
if (
!v2Settings.events.some(event => {
if (event.trigger.type === 'price-change' && event.trigger.pluginId === currencyInfo.pluginId) {
// An event for this plugin exists already we need to check if the user is changing the default fiat currency
if (changeFiat && !event.trigger.currencyPair.includes(defaultIsoFiat)) return false
return true
} else {
return false
}
})
// Must not be deprecated
!SPECIAL_CURRENCY_INFO[currencyInfo.pluginId].keysOnlyMode &&
// Must not already be present with current fiat setting
!serverSettings.events.some(
event =>
event.trigger.type === 'price-change' &&
event.trigger.pluginId === currencyInfo.pluginId &&
(!changeFiat || event.trigger.currencyPair.includes(defaultIsoFiat))
)
) {
missingInfos[currencyInfo.pluginId] = currencyInfo
// Add new push event
createEvents.push(newPriceChangeEvent(currencyInfo, defaultIsoFiat, true, true))
}
}

// See if we need to remove any deprecated currencies (keys-only)
for (const event of serverSettings.events) {
const { trigger } = event
if (trigger.type === 'price-change') {
const currencyInfo = activeCurrencyInfos.find(currencyInfo => currencyInfo.pluginId === trigger.pluginId)
if (currencyInfo != null && SPECIAL_CURRENCY_INFO[currencyInfo.pluginId].keysOnlyMode) {
removeEvents.push(event.eventId)
}
}
}
Object.keys(missingInfos).forEach(pluginId => createEvents.push(newPriceChangeEvent(missingInfos[pluginId], defaultIsoFiat, true, true)))
} else {
// No v2 settings exist so let's check v1
const userId = state.core.account.rootLoginId
Expand Down Expand Up @@ -125,8 +137,8 @@ export function registerNotificationsV2(changeFiat: boolean = false): ThunkActio
}
}

if (createEvents.length > 0) {
v2Settings = await dispatch(setDeviceSettings({ createEvents }))
if (createEvents.length > 0 || removeEvents.length > 0) {
serverSettings = await updateServerSettings(state.core.context, { createEvents, removeEvents })
}
} catch (e: any) {
// If this fails we don't need to bother the user just log and move on.
Expand All @@ -135,12 +147,22 @@ export function registerNotificationsV2(changeFiat: boolean = false): ThunkActio

dispatch({
type: 'NOTIFICATION_SETTINGS_UPDATE',
data: serverSettingsToNotificationSettings(v2Settings)
data: serverSettingsToNotificationSettings(serverSettings)
})
}
}

export const serverSettingsToNotificationSettings = (serverSettings: ReturnType<typeof asDevicePayload>): NotificationSettings => {
export function updateNotificationSettings(data: DeviceUpdatePayload): ThunkAction<Promise<void>> {
return async (dispatch, getState) => {
const state = getState()
dispatch({
type: 'NOTIFICATION_SETTINGS_UPDATE',
data: serverSettingsToNotificationSettings(await updateServerSettings(state.core.context, data))
})
}
}

const serverSettingsToNotificationSettings = (serverSettings: DevicePayload): NotificationSettings => {
const data: NotificationSettings = {
ignoreMarketing: serverSettings.ignoreMarketing,
ignorePriceChanges: serverSettings.ignorePriceChanges,
Expand All @@ -163,32 +185,30 @@ export const serverSettingsToNotificationSettings = (serverSettings: ReturnType<
return data
}

export function setDeviceSettings(data: DeviceUpdatePayload): ThunkAction<Promise<ReturnType<typeof asDevicePayload>>> {
return async (dispatch, getState) => {
const state = getState()

const deviceToken = await messaging()
.getToken()
.catch(() => '')

const body = {
apiKey: ENV.AIRBITZ_API_KEY,
deviceId: state.core.context.clientId,
deviceToken,
data: { ...data, loginIds: state.core.context.localUsers.map(row => base64.stringify(base58.parse(row.loginId))) }
}
const opts = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
}
async function updateServerSettings(context: EdgeContext, data: DeviceUpdatePayload): Promise<DevicePayload> {
const deviceId = context.clientId
const loginIds = context.localUsers.map(row => base64.stringify(base58.parse(row.loginId)))
const deviceToken = await messaging()
.getToken()
.catch(() => '')

const body = {
apiKey: ENV.AIRBITZ_API_KEY,
deviceId,
deviceToken,
data: { ...data, loginIds }
}
const opts = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
}

const response = await fetchPush('v2/device/update/', opts)
const response = await fetchPush('v2/device/update/', opts)

return asDevicePayload(await response.text())
}
return asDevicePayload(await response.text())
}

export const newPriceChangeEvent = (
Expand Down
71 changes: 27 additions & 44 deletions src/components/scenes/CurrencyNotificationScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import * as React from 'react'
import { ScrollView } from 'react-native'
import { sprintf } from 'sprintf-js'

import { newPriceChangeEvent, serverSettingsToNotificationSettings, setDeviceSettings } from '../../actions/NotificationActions'
import { NewPushEvent } from '../../controllers/action-queue/types/pushApiTypes'
import { newPriceChangeEvent, updateNotificationSettings } from '../../actions/NotificationActions'
import { useHandler } from '../../hooks/useHandler'
import { lstrings } from '../../locales/strings'
import { RootState } from '../../reducers/RootReducer'
Expand All @@ -24,52 +23,36 @@ export const CurrencyNotificationScene = (props: Props) => {
const defaultIsoFiat = useSelector((state: RootState) => state.ui.settings.defaultIsoFiat)
const settings = useSelector((state: RootState) => state.notificationSettings)

const toggleHourlySetting = useHandler(async () => {
const newEvent = newPriceChangeEvent(currencyInfo, defaultIsoFiat, !settings.plugins[pluginId].hourlyChange, !!settings.plugins[pluginId].dailyChange)
await updateSettings(newEvent)
})
const updateSettings = (settingChange: 'hourly' | 'daily') => async () => {
const hourly = settingChange === 'hourly' ? !settings.plugins[pluginId].hourlyChange : !!settings.plugins[pluginId].hourlyChange
const daily = settingChange === 'daily' ? !settings.plugins[pluginId].dailyChange : !!settings.plugins[pluginId].dailyChange
const event = newPriceChangeEvent(currencyInfo, defaultIsoFiat, hourly, daily)
try {
await dispatch(updateNotificationSettings({ createEvents: [event] }))
} catch (e: any) {
showError(`Failed to reach notification server: ${e}`)
}
}

const toggleDailySetting = useHandler(async () => {
const newEvent = newPriceChangeEvent(currencyInfo, defaultIsoFiat, !!settings.plugins[pluginId].hourlyChange, !settings.plugins[pluginId].dailyChange)
await updateSettings(newEvent)
})

const updateSettings = React.useCallback(
async (event: NewPushEvent) => {
try {
const newSettings = await dispatch(setDeviceSettings({ createEvents: [event] }))
dispatch({
type: 'NOTIFICATION_SETTINGS_UPDATE',
data: serverSettingsToNotificationSettings(newSettings)
})
} catch (e: any) {
showError(`Failed to reach notification server: ${e}`)
}
},
[dispatch]
)

const rows = React.useMemo(
() => [
<SettingsSwitchRow
key="hourly"
label={sprintf(lstrings.settings_currency_notifications_percent_change_hour, 3)}
value={settings.plugins[pluginId].hourlyChange != null}
onPress={toggleHourlySetting}
/>,
<SettingsSwitchRow
key="daily"
label={sprintf(lstrings.settings_currency_notifications_percent_change_hours, 10, 24)}
value={settings.plugins[pluginId].dailyChange != null}
onPress={toggleDailySetting}
/>
],
[pluginId, settings, toggleDailySetting, toggleHourlySetting]
)
const handleHourlyPress = useHandler(updateSettings('hourly'))
const handleDailyPress = useHandler(updateSettings('daily'))

return (
<SceneWrapper background="theme" hasTabs={false}>
<ScrollView>{rows}</ScrollView>
<ScrollView>
<SettingsSwitchRow
key="hourly"
label={sprintf(lstrings.settings_currency_notifications_percent_change_hour, 3)}
value={settings.plugins[pluginId].hourlyChange != null}
onPress={handleHourlyPress}
/>
<SettingsSwitchRow
key="daily"
label={sprintf(lstrings.settings_currency_notifications_percent_change_hours, 10, 24)}
value={settings.plugins[pluginId].dailyChange != null}
onPress={handleDailyPress}
/>
</ScrollView>
</SceneWrapper>
)
}
8 changes: 2 additions & 6 deletions src/components/scenes/LoginScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { BlurView } from 'rn-id-blurview'

import { showSendLogsModal } from '../../actions/LogActions'
import { initializeAccount, logoutRequest } from '../../actions/LoginActions'
import { serverSettingsToNotificationSettings, setDeviceSettings } from '../../actions/NotificationActions'
import { updateNotificationSettings } from '../../actions/NotificationActions'
import { cacheStyles, Theme, useTheme } from '../../components/services/ThemeContext'
import { ENV } from '../../env'
import { ExperimentConfig, getExperimentConfig } from '../../experimentConfig'
Expand Down Expand Up @@ -166,11 +166,7 @@ export function LoginSceneComponent(props: Props) {

if (notificationPermissionsInfo) {
try {
const newSettings = await dispatch(setDeviceSettings(notificationPermissionsInfo.notificationOptIns))
dispatch({
type: 'NOTIFICATION_SETTINGS_UPDATE',
data: serverSettingsToNotificationSettings(newSettings)
})
await dispatch(updateNotificationSettings(notificationPermissionsInfo.notificationOptIns))
} catch (e) {
trackError(e, 'LoginScene:onLogin:setDeviceSettings')
console.error(e)
Expand Down
Loading
Loading