diff --git a/packages/mobile/src/exchange/reducer.ts b/packages/mobile/src/exchange/reducer.ts index c0df171aab2..69fa2d756e2 100644 --- a/packages/mobile/src/exchange/reducer.ts +++ b/packages/mobile/src/exchange/reducer.ts @@ -3,7 +3,7 @@ import { Actions, ActionTypes } from 'src/exchange/actions' import { getRehydratePayload, REHYDRATE, RehydrateAction } from 'src/redux/persist-helper' import { RootState } from 'src/redux/reducers' -export const MAX_HISTORY_RETENTION = 30 * 24 * 3600 * 1000 // (ms) ~ 180 days +export const MAX_HISTORY_RETENTION = 30 * 24 * 3600 * 1000 // (ms) ~ 30 days export const ADDRESS_LENGTH = 42 export interface ExchangeRatePair { diff --git a/packages/mobile/src/firebase/saga.ts b/packages/mobile/src/firebase/saga.ts index e34fe928352..89b442a3607 100644 --- a/packages/mobile/src/firebase/saga.ts +++ b/packages/mobile/src/firebase/saga.ts @@ -100,47 +100,61 @@ function celoGoldExchangeRateHistoryChannel(lastTimeUpdated: number) { } const now = Date.now() + // timestamp + 1 is used because .startAt is inclusive + const startAt = Math.max(lastTimeUpdated + 1, now - MAX_HISTORY_RETENTION) return eventChannel((emit: any) => { - const emitter = (snapshot: FirebaseDatabaseTypes.DataSnapshot) => { + const singleItemEmitter = (snapshot: FirebaseDatabaseTypes.DataSnapshot) => { + emit([snapshot.val()]) + } + const listenForNewElements = (newElementsStartAt: number) => { + firebase + .database() + .ref(`${EXCHANGE_RATES}/cGLD/cUSD`) + .orderByChild('timestamp') + .startAt(newElementsStartAt) + .on('child_added', singleItemEmitter, errorCallback) + } + const fullListEmitter = (snapshot: FirebaseDatabaseTypes.DataSnapshot) => { const result: ExchangeRate[] = [] snapshot.forEach((childSnapshot: FirebaseDatabaseTypes.DataSnapshot) => { result.push(childSnapshot.val()) return undefined }) - emit(result) + if (result.length) { + emit(result) + listenForNewElements(result[result.length - 1].timestamp + 1) + } else { + listenForNewElements(startAt) + } } - // timestamp + 1 is used because .startAt is inclusive - const startAt = lastTimeUpdated + 1 || now - MAX_HISTORY_RETENTION - - const onValueChange = firebase + firebase .database() .ref(`${EXCHANGE_RATES}/cGLD/cUSD`) .orderByChild('timestamp') .startAt(startAt) - .on(VALUE_CHANGE_HOOK, emitter, errorCallback) + .once(VALUE_CHANGE_HOOK, fullListEmitter, errorCallback) + .catch((error) => { + Logger.error(TAG, 'Error while fetching exchange rates', error) + }) - const cancel = () => { + return () => { firebase .database() .ref(`${EXCHANGE_RATES}/cGLD/cUSD`) - .orderByChild('timestamp') - .startAt(startAt) - .off(VALUE_CHANGE_HOOK, onValueChange) + .off() } - - return cancel }) } export function* subscribeToCeloGoldExchangeRateHistory() { yield call(waitForFirebaseAuth) const history = yield select(exchangeHistorySelector) - const chan = yield call(celoGoldExchangeRateHistoryChannel, history.lastTimeUpdated) + const channel = yield call(celoGoldExchangeRateHistoryChannel, history.lastTimeUpdated) try { while (true) { - const exchangeRates = yield take(chan) + const exchangeRates = yield take(channel) const now = getRemoteTime() yield put(updateCeloGoldExchangeRateHistory(exchangeRates, now)) } @@ -148,7 +162,7 @@ export function* subscribeToCeloGoldExchangeRateHistory() { Logger.error(`${TAG}@subscribeToCeloGoldExchangeRateHistory`, error) } finally { if (yield cancelled()) { - chan.close() + channel.close() } } } diff --git a/packages/mobile/src/redux/migrations.test.ts b/packages/mobile/src/redux/migrations.test.ts index bc0120c8197..026d8d61202 100644 --- a/packages/mobile/src/redux/migrations.test.ts +++ b/packages/mobile/src/redux/migrations.test.ts @@ -1,4 +1,5 @@ import { DEFAULT_DAILY_PAYMENT_LIMIT_CUSD } from 'src/config' +import { initialState as exchangeInitialState } from 'src/exchange/reducer' import { CicoProviderNames } from 'src/fiatExchanges/reducer' import { migrations } from 'src/redux/migrations' import { v0Schema, v1Schema, v2Schema, v7Schema, v8Schema, vNeg1Schema } from 'test/schemas' @@ -177,4 +178,25 @@ describe('Redux persist migrations', () => { expect(migratedSchema.app.bitfyUrl).toBeUndefined() expect(migratedSchema.app.flowBtcUrl).toBeUndefined() }) + it('works for v11 to v12', () => { + const appStub = 'dont delete please' + const exchangeStub = 'also dont delete please' + const stub = { + exchange: { + otherExchangeProps: exchangeStub, + history: { + celoGoldExchangeRates: [1, 2, 3], + aggregatedExchangeRates: [4, 5, 6], + granularity: 0, + range: 0, + lastTimeUpdated: 100, + }, + }, + app: appStub, + } + const migratedSchema = migrations[12](stub) + expect(migratedSchema.app).toEqual(appStub) + expect(migratedSchema.exchange.otherExchangeProps).toEqual(exchangeStub) + expect(migratedSchema.exchange.history).toEqual(exchangeInitialState.history) + }) }) diff --git a/packages/mobile/src/redux/migrations.ts b/packages/mobile/src/redux/migrations.ts index fff3bff0a10..df797d123f7 100644 --- a/packages/mobile/src/redux/migrations.ts +++ b/packages/mobile/src/redux/migrations.ts @@ -1,5 +1,6 @@ import _ from 'lodash' import { DEFAULT_DAILY_PAYMENT_LIMIT_CUSD } from 'src/config' +import { initialState as exchangeInitialState } from 'src/exchange/reducer' import { providersDisplayInfo } from 'src/fiatExchanges/reducer' import { AddressToDisplayNameType } from 'src/identity/reducer' @@ -166,4 +167,14 @@ export const migrations = { app: _.omit(state.app, 'pontoEnabled', 'kotaniEnabled', 'bitfyUrl', 'flowBtcUrl'), } }, + 12: (state: any) => { + // Removing the exchange rate history because it's very likely that it's repeated a bunch of times. + return { + ...state, + exchange: { + ...state.exchange, + history: { ...exchangeInitialState.history }, + }, + } + }, } diff --git a/packages/mobile/src/redux/store.ts b/packages/mobile/src/redux/store.ts index 14b51e5fde2..ed12bc914d9 100644 --- a/packages/mobile/src/redux/store.ts +++ b/packages/mobile/src/redux/store.ts @@ -18,7 +18,7 @@ let lastEventTime = Date.now() const persistConfig: any = { key: 'root', - version: 11, // default is -1, increment as we make migrations + version: 12, // default is -1, increment as we make migrations keyPrefix: `reduxStore-`, // the redux-persist default is `persist:` which doesn't work with some file systems. storage: FSStorage(), blacklist: ['geth', 'networkInfo', 'alert', 'fees', 'recipients', 'imports'],