diff --git a/package-lock.json b/package-lock.json index 78a9c79ff..c78b02124 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@fortawesome/free-regular-svg-icons": "6.4.0", "@fortawesome/free-solid-svg-icons": "6.4.0", "@fortawesome/react-native-fontawesome": "0.2.7", + "@hathor/unleash-client": "0.1.0", "@hathor/wallet-lib": "1.0.1", "@notifee/react-native": "5.7.0", "@react-native-async-storage/async-storage": "1.19.0", @@ -2540,6 +2541,14 @@ "@hapi/hoek": "^9.0.0" } }, + "node_modules/@hathor/unleash-client": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@hathor/unleash-client/-/unleash-client-0.1.0.tgz", + "integrity": "sha512-SR1JBQkegKMLNhU5yWYjHcZVC9EZ9kkDz/X5a2RHZsr+dhMic1oriqin3S8jjvIhmjn/uBZFlvzaTm7ll7h3mw==", + "engines": { + "node": ">=18" + } + }, "node_modules/@hathor/wallet-lib": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@hathor/wallet-lib/-/wallet-lib-1.0.1.tgz", diff --git a/package.json b/package.json index 019c133e5..a68ad985e 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@fortawesome/free-regular-svg-icons": "6.4.0", "@fortawesome/free-solid-svg-icons": "6.4.0", "@fortawesome/react-native-fontawesome": "0.2.7", + "@hathor/unleash-client": "0.1.0", "@hathor/wallet-lib": "1.0.1", "@notifee/react-native": "5.7.0", "@react-native-async-storage/async-storage": "1.19.0", diff --git a/src/sagas/featureToggle.js b/src/sagas/featureToggle.js index 688887d8b..9300bf984 100644 --- a/src/sagas/featureToggle.js +++ b/src/sagas/featureToggle.js @@ -7,29 +7,25 @@ import { Platform } from 'react-native'; import VersionNumber from 'react-native-version-number'; -import { UnleashClient, EVENTS as UnleashEvents } from 'unleash-proxy-client'; +import UnleashClient, { FetchTogglesStatus } from '@hathor/unleash-client'; import { get } from 'lodash'; import { - takeEvery, all, call, delay, put, - cancelled, select, - race, - take, fork, spawn, + takeEvery, } from 'redux-saga/effects'; -import { eventChannel } from 'redux-saga'; import { getUniqueId } from 'react-native-device-info'; import { + types, setUnleashClient, setFeatureToggles, featureToggleInitialized, - types, } from '../actions'; import { UNLEASH_URL, @@ -40,7 +36,6 @@ import { } from '../constants'; import { disableFeaturesIfNeeded } from './helpers'; -const CONNECT_TIMEOUT = 10000; const MAX_RETRIES = 5; export function* handleInitFailed(currentRetry) { @@ -71,9 +66,10 @@ export function* fetchTogglesRoutine() { const unleashClient = yield select((state) => state.unleashClient); try { - // This call always make unleash to emit the event 'UPDATE', - // which by its turn triggers the action 'FEATURE_TOGGLE_UPDATE' - yield call(() => unleashClient.fetchToggles()); + const state = yield call(() => unleashClient.fetchToggles()); + if (state === FetchTogglesStatus.Updated) { + yield put({ type: types.FEATURE_TOGGLE_UPDATE }); + } } catch (e) { // No need to do anything here as it will try again automatically in // UNLEASH_POLLING_INTERVAL. Just prevent it from crashing the saga. @@ -82,15 +78,19 @@ export function* fetchTogglesRoutine() { } } -export function* monitorFeatureFlags(currentRetry = 0) { - const unleashClient = new UnleashClient({ - url: UNLEASH_URL, - clientKey: UNLEASH_CLIENT_KEY, - refreshInterval: -1, - disableRefresh: true, // Disable it, we will handle it ourselves - appName: `wallet-mobile-${Platform.OS}`, - }); +export function* handleToggleUpdate() { + console.log('Handling feature toggle update'); + const unleashClient = yield select((state) => state.unleashClient); + const networkSettings = yield select((state) => state.networkSettings); + const toggles = unleashClient.getToggles(); + const featureToggles = disableFeaturesIfNeeded(networkSettings, mapFeatureToggles(toggles)); + + yield put(setFeatureToggles(featureToggles)); + yield put({ type: types.FEATURE_TOGGLE_UPDATED }); +} + +export function* monitorFeatureFlags(currentRetry = 0) { const { appVersion } = VersionNumber; const options = { @@ -102,40 +102,30 @@ export function* monitorFeatureFlags(currentRetry = 0) { }, }; + const unleashClient = new UnleashClient({ + url: UNLEASH_URL, + clientKey: UNLEASH_CLIENT_KEY, + refreshInterval: -1, + disableRefresh: true, // Disable it, we will handle it ourselves + appName: `wallet-mobile-${Platform.OS}`, + context: options, + }); + try { - yield call(() => unleashClient.updateContext(options)); yield put(setUnleashClient(unleashClient)); - // Listeners should be set before unleashClient.start so we don't miss - // updates - yield fork(setupUnleashListeners, unleashClient); - - // Start without awaiting it so we can listen for the - // READY event - unleashClient.start(); - - const { error, timeout } = yield race({ - error: take(types.FEATURE_TOGGLE_ERROR), - success: take(types.FEATURE_TOGGLE_READY), - timeout: delay(CONNECT_TIMEOUT), - }); - - if (error || timeout) { - throw new Error('Error or timeout while connecting to unleash proxy.'); - } + yield call(() => unleashClient.fetchToggles()); // Fork the routine to download toggles. yield fork(fetchTogglesRoutine); - // At this point, unleashClient.start() already fetched the toggles - const featureToggles = mapFeatureToggles(unleashClient.toggles); + // At this point, unleashClient.fetchToggles() already fetched the toggles + // (this will throw if it hasn't) + const featureToggles = mapFeatureToggles(unleashClient.getToggles()); yield put(setFeatureToggles(featureToggles)); yield put(featureToggleInitialized()); } catch (e) { - console.error('Error initializing unleash'); - unleashClient.stop(); - yield put(setUnleashClient(null)); // Wait 500ms before retrying @@ -143,45 +133,6 @@ export function* monitorFeatureFlags(currentRetry = 0) { // Spawn so it's detached from the current thread yield spawn(handleInitFailed, currentRetry); - } finally { - if (yield cancelled()) { - yield call(() => unleashClient.stop()); - } - } -} - -export function* setupUnleashListeners(unleashClient) { - const channel = eventChannel((emitter) => { - const l1 = () => emitter({ type: types.FEATURE_TOGGLE_UPDATE }); - const l2 = () => emitter({ type: types.FEATURE_TOGGLE_READY }); - const l3 = (err) => emitter({ type: types.FEATURE_TOGGLE_ERROR, data: err }); - - unleashClient.on(UnleashEvents.UPDATE, l1); - unleashClient.on(UnleashEvents.READY, l2); - unleashClient.on(UnleashEvents.ERROR, l3); - - return () => { - // XXX: This should be a cleanup but removeListener does not exist - // This will throw an error and it will interfere with other sagas - // Since it works without the cleanup i will leave this method empty - // until have determined the best cleanup approach - }; - }); - - try { - while (true) { - const message = yield take(channel); - - yield put({ - type: message.type, - payload: message.data, - }); - } - } finally { - if (yield cancelled()) { - // When we close the channel, it will remove the event listener - channel.close(); - } } } @@ -198,22 +149,6 @@ function mapFeatureToggles(toggles) { }, {}); } -export function* handleToggleUpdate() { - const unleashClient = yield select((state) => state.unleashClient); - const featureTogglesInitialized = yield select((state) => state.featureTogglesInitialized); - const networkSettings = yield select((state) => state.networkSettings); - - if (!unleashClient || !featureTogglesInitialized) { - return; - } - - const { toggles } = unleashClient; - const featureToggles = disableFeaturesIfNeeded(networkSettings, mapFeatureToggles(toggles)); - - yield put(setFeatureToggles(featureToggles)); - yield put({ type: types.FEATURE_TOGGLE_UPDATED }); -} - export function* saga() { yield all([ fork(monitorFeatureFlags),