From 1d3601dc72d8045551d3b5848164a7c76f43486c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Fri, 26 Jan 2024 11:35:54 -0300 Subject: [PATCH 1/4] refactor: feature toggle using hathor unleash client --- src/sagas/featureToggle.js | 107 +++++-------------------------------- 1 file changed, 14 insertions(+), 93 deletions(-) diff --git a/src/sagas/featureToggle.js b/src/sagas/featureToggle.js index 688887d8b..653777d86 100644 --- a/src/sagas/featureToggle.js +++ b/src/sagas/featureToggle.js @@ -7,7 +7,7 @@ import { Platform } from 'react-native'; import VersionNumber from 'react-native-version-number'; -import { UnleashClient, EVENTS as UnleashEvents } from 'unleash-proxy-client'; +import UnleashClient from 'hathor-unleash-client'; import { get } from 'lodash'; import { @@ -16,14 +16,10 @@ import { call, delay, put, - cancelled, select, - race, - take, fork, spawn, } from 'redux-saga/effects'; -import { eventChannel } from 'redux-saga'; import { getUniqueId } from 'react-native-device-info'; import { setUnleashClient, @@ -40,7 +36,6 @@ import { } from '../constants'; import { disableFeaturesIfNeeded } from './helpers'; -const CONNECT_TIMEOUT = 10000; const MAX_RETRIES = 5; export function* handleInitFailed(currentRetry) { @@ -83,14 +78,6 @@ 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}`, - }); - const { appVersion } = VersionNumber; const options = { @@ -102,40 +89,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 +120,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,25 +136,8 @@ 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), - takeEvery(types.FEATURE_TOGGLE_UPDATE, handleToggleUpdate), ]); } From df80adae395923921c8bbea8982d0bef1b91f129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Mon, 29 Jan 2024 13:07:25 -0300 Subject: [PATCH 2/4] feat: handling feature updates --- src/sagas/featureToggle.js | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/sagas/featureToggle.js b/src/sagas/featureToggle.js index 653777d86..fbd023b07 100644 --- a/src/sagas/featureToggle.js +++ b/src/sagas/featureToggle.js @@ -11,7 +11,6 @@ import UnleashClient from 'hathor-unleash-client'; import { get } from 'lodash'; import { - takeEvery, all, call, delay, @@ -19,13 +18,14 @@ import { select, fork, spawn, + takeEvery, } from 'redux-saga/effects'; import { getUniqueId } from 'react-native-device-info'; import { + types, setUnleashClient, setFeatureToggles, featureToggleInitialized, - types, } from '../actions'; import { UNLEASH_URL, @@ -68,7 +68,10 @@ export function* fetchTogglesRoutine() { 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 === '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. @@ -77,6 +80,18 @@ export function* fetchTogglesRoutine() { } } +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; @@ -139,5 +154,6 @@ function mapFeatureToggles(toggles) { export function* saga() { yield all([ fork(monitorFeatureFlags), + takeEvery(types.FEATURE_TOGGLE_UPDATE, handleToggleUpdate), ]); } From 8018eb533300105cd10c8f63e2877a5ebdf569f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Thu, 1 Feb 2024 10:19:04 -0300 Subject: [PATCH 3/4] refactor: using FetchTogglesStatus enum --- src/sagas/featureToggle.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/sagas/featureToggle.js b/src/sagas/featureToggle.js index fbd023b07..a30ab2720 100644 --- a/src/sagas/featureToggle.js +++ b/src/sagas/featureToggle.js @@ -7,7 +7,7 @@ import { Platform } from 'react-native'; import VersionNumber from 'react-native-version-number'; -import UnleashClient from 'hathor-unleash-client'; +import UnleashClient, { FetchTogglesStatus } from 'hathor-unleash-client'; import { get } from 'lodash'; import { @@ -66,10 +66,8 @@ 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' const state = yield call(() => unleashClient.fetchToggles()); - if (state === 'Updated') { + if (state === FetchTogglesStatus.Updated) { yield put({ type: types.FEATURE_TOGGLE_UPDATE }); } } catch (e) { From 6c0c6738d8c8979ba2b2e66926e910c4cc0d7091 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Fri, 2 Feb 2024 19:21:15 -0300 Subject: [PATCH 4/4] chore: added @hathor/unleash-client as a dependency --- package-lock.json | 9 +++++++++ package.json | 1 + src/sagas/featureToggle.js | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) 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 a30ab2720..9300bf984 100644 --- a/src/sagas/featureToggle.js +++ b/src/sagas/featureToggle.js @@ -7,7 +7,7 @@ import { Platform } from 'react-native'; import VersionNumber from 'react-native-version-number'; -import UnleashClient, { FetchTogglesStatus } from 'hathor-unleash-client'; +import UnleashClient, { FetchTogglesStatus } from '@hathor/unleash-client'; import { get } from 'lodash'; import {